home.md 7.7 KB

前后端数据交互和任务分发

根据您的需求,我将为您设计一个结合动态参数加载、数据库安全查询和任务分发的系统方案,以下是具体实现步骤: 一、分层架构设计 mermaid Copy graph TD

A[前端] -->|1. 获取任务模板| B(FastAPI)
B -->|2. 参数验证| C[数据库]
A -->|3. 提交任务参数| B
B -->|4. 生成实际参数| C
B -->|5. 触发任务| D[Celery]
D -->|6. 存储结果| E[Result Backend]
E -->|7. 状态查询| A

二、核心实现代码

  1. 后端任务模板系统 (FastAPI) 自动注册所有表的CRUD路由

    # pip install fastapi sqlalchemy databases[sqlite] fastapi-autocrud
    # main.py
    from fastapi import FastAPI
    from sqlalchemy.ext.automap import automap_base
    from sqlalchemy.orm import Session
    from sqlalchemy import create_engine
    from fastapi_autocrud import AutoCrud
    
    app = FastAPI()
    engine = create_engine("sqlite:///your.db")
    Base = automap_base()
    Base.prepare(engine, reflect=True)
    
    # 自动注册所有表的CRUD路由
    @app.on_event("startup")
    def init_routes():
    for table_name in Base.classes.keys():
        model = getattr(Base.classes, table_name)
        AutoCrud(app=app, engine=engine, model=model, prefix=f"/api/{table_name}")
    

app/services/query_builder.py

from sqlalchemy import text from sqlalchemy.orm import Session

class DynamicQueryBuilder:

def __init__(self, db: Session):
    self.db = db

def resolve_parameters(self, task_type: str, user_filters: dict) -> dict:
    """将用户过滤条件转换为实际数据"""
    template = get_task_template(task_type)
    resolved_params = {}

    for condition in template.filter_options:
        raw_sql = condition.value.format(**user_filters)
        result = self.db.execute(text(raw_sql)).fetchall()
        resolved_params[condition.field] = [row[0] for row in result]

    return resolved_params
  1. 前端动态表单组件 (Vue3) vue Copy
  2. <!-- 动态生成过滤条件 -->
    <el-form-item 
      v-for="filter in template?.filter_options"
      :key="filter.field"
      :label="filter.field"
    >
      <el-autocomplete
        v-model="formData.filters[filter.field]"
        :fetch-suggestions="searchKeywords"
        placeholder="输入过滤条件"
      />
    </el-form-item>
    
    <!-- 任务参数输入 -->
    <el-form-item
      v-for="(param, name) in template?.parameters"
      :key="name"
      :label="name"
    >
      <el-input 
        v-model="formData.parameters[name]" 
        :type="param.type"
      />
    </el-form-item>
    
    <el-button @click="submit">提交任务</el-button>
    

    worker 架构

    当前项目采用 vue3(语法糖) + element-plus + fastapi + pywebview 架构做的一个本地桌面应用。虽然前后端都是在同一台机器上运行,但是考虑到未来可能或做成网页服务,因此代码仍遵循前后端分离。 我们姑且把前后端应用程序当成管理程序,通过 Fastapi / 路径来挂载前端编译好的静态文件,通过 /api 路径来处理前端的交互, 例如:

    • 管理程序 Server: localhost:5835
    • / index.html /assets/* 前端静态文件
    • /api/proxy/proxies 查询和管理代理池
    • /api/worker/ 管理和查询 worker 状态。 /api/worker/browser_config 修改浏览器路径,用于 worker 的浏览器配置

    worker 使用 celery 挂载了任务:

    from celery import Celery
    from worker.celery import celeryconfig 
    
    app = Celery(
        'search_worker',
        include=[
            'worker.celery.tasks',  # 取消注释原始任务模块
            'worker.celery.crawl_tasks',
            'worker.celery.async_tasks',
            'worker.celery.html_convert_tasks'  # 注册新的 Pandoc 任务模块
        ]
    )
    app.config_from_object(celeryconfig)
    

    当用户想导入任务,是在前端中请求 /api/worker/status 得到 worker endpoint 地址 localhost:8003 ,然后前端将文件上传到 worker 的 localhost:8003/upload ,也可以 localhost:8003/tasks/statistics 查看导入状态,也可以 localhost:8003/browser/test 运行单个浏览器测试。

    或者前端点击运行按钮,

    配置浏览器

    当前项目采用 vue3(语法糖) + element-plus + fastapi + pywebview 架构做的一个本地桌面应用。虽然前后端都是在同一台机器上运行,但是考虑到未来可能或做成网页服务,因此代码仍遵循前后端分离。

    由于浏览器无法获取完整本地路径,因此前端代码中,“修改浏览器路径”我想改为输入框的形式来粘贴路径。尽管前后端都是在同一台机器上运行,但是只能如此了,帮我新增前端点击修改后弹出对话框,可以输入文本然后发送到后端。 推送路径后应该在后端检查路径是否正确,才能保存并返回信息。

    用 element-plus 的组件来修改代码。

    数据模型

    vue 文件的变量似乎很混乱,能否重构这些变量。 我的要求是接收来自 const backendBaseUrl = import.meta.env.VITE_API_BASE_URL || '' 的状态,然后得到 worker 的endpoint 地址,得到 WORKER_SERVICE_URL 地址。因为 worker 可能有很多个微服务,所以应该是动态的。只有 VITE_API_BASE_URL 是固定的。

    我看到许多变量特别零散,不能用数据模型统一集中管理吗?像后端代码所管理的那样

    缓存改为全局

    能否将 store 改为全局变量,而不是使用本地缓存。我思路是这样的,每次启动后,全局变量都会初始请求有关的 URL 和数据,保存在全局会话中,除非重启或者刷新页面。 也就是说,不要每次点击路由都请求一次,

    首页显示代理

    假如代理类型是 poll,那么应该显示名称为 代理池,代理池状态 2/3 。表示 reachable 有2个,total 有3个。 假如代理类型是 system , 那么应该显示名称为 系统代理,启用状态 。

    selectedProxy poll
    sys_proxy_cache {"timestamp":1740148480397,"data":{"sys_open":true,"proxy_server":"127.0.0.1:1881"}}
    proxies {"timestamp":1740148515594,"data":[{"name":"🇭🇰香港1号","port":9660,"reachable":false,"file_path":"g:\\code\\upwork\\zhang_crawl_bio\\download\\proxy_pool\\temp\\9660.yaml","mgr_url":"https://yacd.metacubex.one/?hostname=127.0.0.1&port=9661&secret=#/proxies","process_info":null,"ping":{}},{"name":"🇯🇵亚马逊日本2","port":9662,"reachable":true,"file_path":"g:\\code\\upwork\\zhang_crawl_bio\\download\\proxy_pool\\temp\\9662.yaml","mgr_url":"https://yacd.metacubex.one/?hostname=127.0.0.1&port=9663&secret=#/proxies","process_info":{"log_file":"G:\\code\\upwork\\zhang_crawl_bio\\ui\\backend\\output\\logs\\process_mgr\\mimo_9662.log","pid":32176,"start_time":1740148439.2248976},"ping":{}},{"name":"🇳🇱荷兰专线","port":9664,"reachable":true,"file_path":"g:\\code\\upwork\\zhang_crawl_bio\\download\\proxy_pool\\temp\\9664.yaml","mgr_url":"https://yacd.metacubex.one/?hostname=127.0.0.1&port=9665&secret=#/proxies","process_info":{"log_file":"G:\\code\\upwork\\zhang_crawl_bio\\ui\\backend\\output\\logs\\process_mgr\\mimo_9664.log","pid":41188,"start_time":1740148341.276417},"ping":{}}]}