Vastai-ConnectHub/docs/connecthub_功能与部署指南.md

22 KiB
Raw Blame History

ConnectHub 功能与部署指南

本文件面向开发与运维,汇总 ConnectHub 当前仓库 已实现的全部功能feature,并给出 开发→上线docker compose 手动)→备份/回滚/排障 的全流程指南。

约定:本文所有“定位”均以本仓库当前代码为准;extensions/ 下内容被定义为 标准示例(可复制改造),不等同于平台核心能力。


1. 项目概览

1.1 项目定位

ConnectHub 是一个轻量级企业集成中间件统一管理多系统集成任务Job提供定时调度、执行监控与“一键重试/立即运行”。

1.2 组件与架构(文字版)

  • FastAPI + SQLAdmin:提供管理台(任务/日志)与简单健康检查
  • PostgreSQL:持久化任务定义与执行日志
  • RedisCelery broker/backend
  • Celery worker:执行任务
  • Celery beat:每分钟 tick 调度器,按 cron 触发任务

1.3 入口与检查点

  • Adminhttp://<host>:8000/admin(由 docker-compose.yml 暴露 8000:8000
  • 健康检查GET /health
    • 入口:app/main.py
    • 返回:{"ok": true, "name": "<app_name>"}

2. Feature 全量清单(按模块分组)

本章只列举“仓库当前代码已实现”的能力,并给出源码定位。

2.1 Web/API 与 Admin 管理台SQLAdmin

  • FastAPI 应用初始化

    • 定位app/main.py
    • 行为
      • 初始化日志系统(见 2.7
      • 确保 /data 与可选日志目录存在
      • 确保 Fernet key 已生成并持久化(见 2.6
      • 确保 DB schema见 2.8
  • Job任务管理

    • 模型定位app/db/models.pyJob
    • Admin 视图定位app/admin/views.pyJobAdmin
    • 字段含义
      • id:任务 ID创建必填可在表单中编辑主键
      • enabled:是否启用(调度只触发启用任务)
      • cron_exprcron 表达式Asia/Shanghai 时区语义)
      • handler_path:处理器路径(插件类定位,见 2.4
      • public_cfg明文配置JSON object
      • secret_cfg密文配置DB 存 Fernet token编辑页不回显
      • last_run_at:最近一次被调度触发的时间(防重复)
    • 保存校验(重要)
      • id 必填(创建时)
      • handler_path 必须可 import且类必须继承 BaseJob
      • cron_expr 必须可被 croniter 解析
      • public_cfg 必须是 合法 JSON 对象dict
      • secret_cfg 创建时必须是 合法 JSON 对象dict,并会加密落库;编辑时留空表示不更新,填入则会校验并加密覆盖
    • 管理动作actions
      • 立即运行Run Now创建 RUNNING 的 JobLog 并异步执行
      • 查看日志:跳转到 JobLog 列表并按 job_id 搜索
      • 停用任务(保留日志)
      • 清理日志(保留任务)
      • 删除任务(含日志)
  • JobLog任务日志管理

    • 模型定位app/db/models.pyJobLogJobStatus
    • Admin 视图定位app/admin/views.pyJobLogAdmin
    • 展示与能力
      • 只读(不可创建/编辑/删除)
      • 列表支持按 job_id 搜索
      • 详情展示:
        • snapshot_params(快照参数)
        • message(包含基础消息 + warnings 摘要,可能截断)
        • traceback(异常堆栈)
        • run_log(运行日志,可能截断)
        • celery_task_id / attempt / started_at / finished_at
  • Admin 路由Retry / Run Now

    • 定位app/admin/routes.py
    • 能力
      • POST /admin/joblogs/{log_id}/retry:基于历史 snapshot_params 创建新 RUNNING JobLog 并重跑
        • 保护:当原日志状态为 RUNNING 时禁止重试
        • 标记:在 snapshot.meta 中写入 trigger=retry
      • POST /admin/jobs/{job_id}/run:创建 RUNNING JobLog 并按当前 DB Job 定义执行

2.2 调度系统Cron + 防重复)

  • Beat 定时 tick

    • 定位app/tasks/celery_app.pybeat_schedule 每 60 秒)
    • 触发任务名connecthub.dispatcher.tick
  • 调度器逻辑

    • 定位app/tasks/dispatcher.py
    • 行为
      • 只读取 enabled=True 的 Jobsapp/db/crud.py:list_enabled_jobs
      • 使用 croniter 判断“当前分钟是否应触发”(时区 Asia/Shanghai
      • 使用 last_run_at 防止同一分钟重复触发(对 naive datetime 做时区解释)
      • 触发执行:execute_job.delay(job_id=job.id)

2.3 执行引擎(通用 execute_job

  • 任务执行入口

    • 定位app/tasks/execute.pyCelery taskconnecthub.execute_job
    • 两种入口模式
      • job_id:从 DB 读取 handler_path/public_cfg/secret_cfg
      • snapshot_params:按快照执行(用于 Retry
  • JobLog 生命周期

    • 定位app/tasks/execute.py + app/db/crud.py
    • 行为
      • 若未显式传入 log_id,会尽力先创建一条 RUNNING 记录;若创建失败则降级(结束时再创建最终记录)
      • 执行结束后更新该条 JobLog状态SUCCESS/FAILURE、message、traceback、run_log、attempt、finished_at 等
  • 运行日志捕获与截断

    • 定位app/core/log_capture.py
    • 策略
      • 捕获 root logger 输出写入 run_log
      • 以字节数限制(默认 200KB截断并追加标记
    • message 合成策略
      • 定位app/tasks/execute.py
      • 从 run_log 提取 WARNING 行并追加到 message并做总长度保护50k 字符上限)

2.4 插件机制handler_path 动态加载)

  • BaseJob 规范

    • 定位app/jobs/base.py
    • 要求:插件 Job 必须实现 run(params, secrets)
      • params:来自 Job.public_cfg(明文)
      • secrets:来自 Job.secret_cfg 解密后的明文(仅内存)
  • handler_path 解析与加载

    • 定位app/plugins/manager.py
    • 支持格式
      • pkg.mod:ClassName(推荐)
      • pkg.mod.ClassName
    • 约束
      • 类必须存在且是 BaseJob 子类,否则保存/加载失败

2.5 集成客户端(统一 HTTP 调用层)

  • 致远 OASeeyonClient

    • 定位app/integrations/seeyon.py
    • 能力
      • POST /seeyon/rest/token 获取 tokenid
      • 业务请求自动携带 token header
      • 遇到 401 或响应包含 Invalid token:自动刷新 token 并重试一次
      • CAP4 无流程表单导出:POST /seeyon/rest/cap4/form/soap/export
  • 滴滴DidiClient

    • 定位app/integrations/didi.py
    • 能力
      • POST /river/Auth/authorize 获取 access_token 并缓存(考虑 skew
      • 生成签名 sign当前实现为 MD5
      • 遇到 401清空 token重新 authorize 后重试一次
      • 关键 API
        • 公司主体查询:GET /river/LegalEntity/get
        • 员工明细:GET /river/Member/detail
        • 员工修改:POST /river/Member/edit

2.6 配置与安全public_cfg / secret_cfg 与 Fernet

  • 配置来源

    • 定位app/core/config.pySettings.env 读取)
    • 关键变量(默认值见代码与 README.md
      • DATA_DIR(默认 /data
      • DB_URL
      • REDIS_URL
      • FERNET_KEY_PATH(默认 /data/fernet.key
      • LOG_DIR(默认 /data/logs,可为空关闭文件日志)
  • secret_cfg 加密存储与解密执行

    • 定位app/security/fernet.py
    • 行为
      • Admin 保存 secret_cfg 时加密落库Fernet token
      • Worker 执行时解密为 dict仅在内存中传给 Job
      • Fernet key 在启动时自动生成并写入 FERNET_KEY_PATH(见 app/main.py
    • 重要约束(上线必读)
      • 正式环境必须持久化并保留同一个 Fernet key 文件,否则历史 secret_cfg 将无法解密。
    • 常见脏数据兼容/报错
      • token 被引号包裹、混入空白/换行会被清理
      • token 被截断会报 “looks truncated”需重新保存 secret_cfg 重新加密

2.7 日志与可观测

  • 全局日志初始化

    • 定位app/core/logging.py
    • 行为
      • stdout 输出INFO
      • LOG_DIR 不为空:写入滚动文件 connecthub.log10MB * 5
  • 任务级日志run_log

    • 定位app/core/log_capture.py + app/tasks/execute.py
    • 行为:捕获执行期间日志写入 JobLog.run_log,超限截断

2.8 数据库与 schema 自升级(轻量)

  • DB Engine/Session

    • 定位app/db/engine.py
    • 行为:按 DB_URL 创建 enginesqlite 兼容 check_same_thread=False
  • schema 确保与轻量自升级

    • 定位app/db/schema.py
    • 行为
      • create_all 初次建表
      • job_logs.run_log 列不存在:ALTER TABLE 补列
      • status 存在约束且不允许 RUNNING做兼容迁移SQLite 重建表 / Postgres 调整 CHECK

2.9 运维脚本与容器化形态

  • docker compose 服务组成

    • 定位docker-compose.yml
    • 服务:redis / postgres / backend / worker / beat
    • 持久化:
      • ./data/pgdata → postgres 数据目录
      • ./data → 容器 /data(包含 fernet.key、logs 等)
  • 开发 overlay

    • 定位docker-compose.dev.yml
    • 行为:
      • backenduvicorn --reload
      • worker/beatwatchfiles 监听 app/extensions/ 变更自动重启
  • 管理脚本

    • 定位connecthub.sh
    • 能力build/start/restart/stop + dev-build/dev-start/... + logs可 follow、可 tail、可指定 service
  • 镜像构建

    • 定位docker/Dockerfile
    • 行为:pip install . 后复制 app/extensions/ 到镜像内 /app

3. 标准示例extensions

本章内容来自 extensions/,定位为 标准示例/模板:用于展示“如何写 Job、如何组织集成调用、如何记录日志与处理错误”。上线时可保留也可按业务需要替换/扩展。

3.1 示例文件与入口

  • 示例文件extensions/sync_oa_to_didi/job.py
  • handler_path 填写示例(用于 Admin 创建 Job
    • extensions.sync_oa_to_didi.job:SyncOAToDidiTokenJob
    • extensions.sync_oa_to_didi.job:SyncOAToDidiExportFormJob
    • extensions.sync_oa_to_didi.job:SyncOAToDidiLegalEntitySyncJob

3.2 SyncOAToDidiTokenJobtoken 获取演示)

  • 目的:演示调用致远 OA 获取 token并在日志中进行基础脱敏输出。
  • 需要的 public_cfg 字段
    • base_url:致远 OA base url不包含具体路径
  • 需要的 secret_cfg 字段(解密后)
    • rest_userREST 帐号
    • rest_passwordREST 密码
    • loginName:可选(模拟登录名)
  • 行为特征
    • 使用 SeeyonClient.authenticate() 获取 token
    • 日志输出会对 token 做 mask避免完整泄露

3.3 SyncOAToDidiExportFormJobCAP4 无流程表单导出)

  • 目的:调用致远 OA CAP4 表单导出接口,返回原始响应文本,并将大文本按块写入 run_log尽力而为
  • 需要的 public_cfg 字段
    • base_url
    • templateCode
    • 可选:senderLoginName / rightId / doTrigger / param / extra(扩展字段 dict
  • 需要的 secret_cfg 字段(解密后)
    • rest_user / rest_password / loginName
  • 行为特征
    • 返回结构中包含 raw(原始文本)与 metacontent_length/content_type 等)
    • 大文本会拆分 chunk 记录到 run_log仍受 run_log 总量上限截断)

3.4 SyncOAToDidiLegalEntitySyncJobOA→滴滴公司主体同步示例

  • 目的:从 OA 导出数据中解析“工号/所属公司”,并同步到滴滴员工的 legal_entity_id
  • 需要的 public_cfg 字段
    • oa_base_url
    • oa_templateCode
    • didi_base_url
    • 可选:senderLoginName/rightId/doTrigger/param/extra(透传到 OA 导出)
  • 需要的 secret_cfg 字段(解密后)
    • OArest_user/rest_password/loginName
    • 滴滴:company_id/client_id/client_secret/sign_key
  • 行为特征(示例策略)
    • 从表单 definition 中定位 display 为“工号/所属公司”的字段名
    • 公司主体查询结果做进程内缓存
    • 员工修改按文档要求增加 150ms 间隔(避免限频)
    • 输出统计:总行数/成功/跳过/错误列表(截取前 50

4. 开发全流程指南(本地开发)

4.1 前置条件

  • 安装 Docker 与 docker compose
  • 端口规划(默认 compose 暴露):
    • 8000backend / Admin
    • 5432postgres如不需要宿主机直连可在 compose 里移除映射)
    • 6379redis同上

4.2 初始化与启动(开发模式)

  1. 在仓库根目录创建 .env(参考 env.example
cp env.example .env
  1. 启动(开发 overlay支持热更新
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build

或使用脚本:

./connecthub.sh dev-build
./connecthub.sh dev-start
  1. 首次启动会自动完成(无需手工执行):
  • 创建 /data 目录(宿主机对应 ./data
  • 生成并持久化 Fernet key默认 ./data/fernet.key
  • 初始化/升级数据库 schema建表、补列、兼容约束
  1. 打开 Admin
  • http://localhost:8000/admin

4.3 新增一个插件 Job标准步骤

  1. extensions/ 下创建你的模块与 Job 类(继承 BaseJob,实现 run(params, secrets))。
  2. 在 Admin 创建 Job
  • id:自定义(建议带命名空间)
  • enabledtrue
  • cron_expr:例如 * * * * *(每分钟)
  • handler_path:例如 extensions.your_ext.job:YourJob
  • public_cfg:必须是 JSON object形如 {...}
  • secret_cfg:必须是 JSON object形如 {...},保存时会加密)
  1. 观察执行结果:
  • 通过 Job 上的“立即运行”或等待 cron 触发
  • 在 JobLog 中查看 message/traceback/run_log/snapshot_params

4.4 查看日志(开发/排查)

  • 查看全部服务日志:
./connecthub.sh log
  • 只看 worker并 follow
./connecthub.sh log -f worker

4.5 常见开发坑(必须注意)

  • public_cfg/secret_cfg 在 Admin 中保存时必须输入 合法 JSON 对象(双引号、且为 {...}),否则会直接阻止落库。
  • 编辑 Job 时 secret_cfg 不回显:留空表示不更新;填写则会校验 JSON 并重新加密覆盖。
  • 如果你在不同环境/不同机器上复用数据库,必须同时迁移 /data/fernet.key,否则解密会失败。

5. 上线全流程指南docker compose 手动部署)

本项目当前形态为“统一环境 + 手动发布”,以下流程以 docker-compose.yml 为准(生产不要使用 dev overlay

5.1 部署前准备清单

  • 目标机依赖Docker、docker compose
  • 部署目录:建议固定到一个路径(例如 /opt/connecthub),保证 ./data 可持久化
  • 必须持久化的内容
    • ./data/pgdataPostgreSQL 数据目录(必须)
    • ./data/fernet.keyFernet key必须影响历史 secret_cfg 可解密)
    • ./data/logs:文件日志(可选,但推荐便于排障)
  • 端口与网络
    • 若非必要,建议仅暴露 80005432/6379 建议仅内网或不对外映射
  • 配置文件
    • .env与代码同目录放置compose env_file: .env
    • 核心变量:DB_URL / REDIS_URL / DATA_DIR / FERNET_KEY_PATH / LOG_DIR

5.2 首次上线(从零部署)

  1. 准备代码与配置:
cd /path/to/your/deploy/dir
cp env.example .env
  1. 构建并启动(生产 compose
docker compose up -d --build

或使用脚本:

./connecthub.sh build
  1. 验收检查点:
  • docker compose psbackend/worker/beat/postgres/redis 均为 healthy/running
  • GET http://<host>:8000/health 返回 ok
  • Admin 可打开:http://<host>:8000/admin
  • 能创建一个 Job 并“立即运行”,在 JobLog 中看到 RUNNING→SUCCESS/FAILURE 的完整链路

5.3 日常发布(升级)

  1. 拉取新版本代码(或替换为新代码包)。
  2. 重新构建并启动:
docker compose up -d --build
  1. 验证点(升级后必做):
  • backend 可访问、/health 正常
  • beat 在运行(否则 cron 不会触发)
  • worker 能写入 JobLog检查最新日志是否持续产生
  • 若有 schema 变更:确认 ensure_schema 执行未导致写库异常(看 worker 日志)

5.4 回滚策略(手动)

回滚核心目标:恢复“可用的旧版本服务”,并确保数据与 Fernet key 一致。

  • 代码/镜像回滚
    • 回到上一个可用版本的代码git tag/commit/发布包)
    • 重新 docker compose up -d --build
  • 关键风险(必须牢记)
    • 不能更换 ./data/fernet.key:一旦更换,历史 secret_cfg 将无法解密,任务会失败。
  • 数据回滚
    • 依赖备份(见第 6 章),优先恢复 Postgres 数据,再启动服务验证

6. 备份与恢复(必须项)

本项目需要至少备份以下两类资产:

  • PostgreSQL 数据(任务定义与日志)
  • Fernet key(密文配置解密必需)

6.1 备份策略(推荐最小集合)

  • 必须备份
    • ./data/fernet.key
    • PostgreSQL 数据(建议做“逻辑备份”或“目录快照”,至少二选一)
  • 可选备份
    • ./data/logs/(用于留存审计与排障)

6.2 逻辑备份(推荐,跨机器/跨路径更稳)

  1. 执行备份(示例:在目标机上对容器内 pg 执行 pg_dump
docker compose exec -T postgres pg_dump -U connecthub -d connecthub > backup_connecthub.sql
  1. 同步备份 Fernet key
cp ./data/fernet.key ./backup_fernet.key
  1. 恢复(新环境/故障恢复):
  • 启动 postgres/redis可先不启动 worker/beat
  • 导入 SQL
cat backup_connecthub.sql | docker compose exec -T postgres psql -U connecthub -d connecthub
  • 还原 Fernet key
cp ./backup_fernet.key ./data/fernet.key
  • 启动全量服务并验证:
    • Admin 能打开
    • 历史 Job 能正常运行(证明 secret_cfg 可解密)

6.3 目录快照备份(简单,但对一致性要求更高)

目录级备份前建议短暂停止写入(至少停止 worker/beat避免备份不一致。

  1. 停止任务执行(建议):
docker compose stop worker beat
  1. 打包 pgdatafernet.key
tar -czf backup_data_$(date +%F).tgz ./data/pgdata ./data/fernet.key
  1. 恢复后再启动 worker/beat
docker compose up -d

7. 排障手册(症状 → 定位 → 处理)

7.1 Admin 保存 Job 报错public_cfg/secret_cfg 必须是 JSON object

  • 症状:保存时报 public_cfg must be a JSON objectsecret_cfg must be a JSON object
  • 定位app/admin/views.pyon_model_change 校验)
  • 处理
    • 确认输入为 {...} 且 key 使用双引号
    • 不要输入数组 [...]、字符串 "..."、或空内容

7.2 任务不按 cron 触发

  • 症状Job 创建后一直没有新日志产生
  • 定位
    • beat 是否运行:docker compose ps./connecthub.sh log beat
    • worker 是否运行:./connecthub.sh log worker
    • Job 是否启用Admin 中 enabled
    • cron 是否正确:cron_expr 保存时虽校验,但仍可能逻辑不符合预期
    • 时区语义:调度使用 Asia/Shanghaiapp/tasks/dispatcher.py
    • last_run_at 是否阻挡:同一分钟不会重复触发(last_run_at >= now_min 会跳过)
  • 处理
    • 先用 Job 的“立即运行”验证执行链路(排除插件/外部系统问题)
    • 确认 beat 正常运行后再排 cron

7.3 Retry 提示“正在运行中,请结束后再重试”

  • 症状:点击 Retry 被拒绝
  • 定位app/admin/routes.pyRUNNING 状态保护)
  • 处理
    • 等待任务结束SUCCESS/FAILURE后再 Retry
    • 若任务卡死:检查 worker 进程状态与外部依赖,必要时重启 worker

7.4 secret_cfg 解密失败Invalid token / looks truncated

  • 症状任务失败traceback 提示 Fernet token 无效或被截断
  • 定位app/security/fernet.py:decrypt_json
  • 处理
    • 确认 ./data/fernet.key 未丢失、未被替换
    • 若是 token 被截断/污染:在 Admin 重新填写 secret_cfgJSON object保存让系统重新加密

7.5 外部系统 API 调用失败401/超时/限频)

  • 定位
    • 致远:app/integrations/seeyon.py401/Invalid token 会自动刷新重试一次)
    • 滴滴:app/integrations/didi.py401 会刷新 token 重试一次;示例 Job 有 150ms 间隔)
  • 处理
    • 查看 JobLog 的 run_logtraceback
    • 核对 secret_cfg 中凭证字段是否正确
    • 若接口限频:按业务需要在 Job 内增加节流/批处理(参考示例策略)

7.6 run_log/message 太长被截断

  • 症状JobLog 的 run_log 或 message 尾部出现 [TRUNCATED]
  • 定位
    • run_log 截断:app/core/log_capture.py
    • message 截断:app/tasks/execute.py
  • 处理
    • 减少单次输出量(避免把大 payload 全量打印)
    • 需要保留大文本时,可按块写日志(示例:extensions/sync_oa_to_didi/job.py 的分块写入方法)

8. 安全建议(上线必读)

  • 敏感信息管理

    • .env 不应提交到版本库;上线时使用权限受控的方式分发
    • secret_cfg 必须通过 Admin 输入并加密落库,避免在日志/代码中硬编码
  • 端口暴露与网络隔离

    • postgres/redis 建议仅内网访问或不对外映射端口
    • Admin 暴露到公网时建议加一层反向代理与访问控制(本仓库未内置鉴权)
  • 日志脱敏

    • 任何 token/密码类字段不应完整写入日志
    • 可参考示例 Job 的 token mask 思路(extensions/sync_oa_to_didi/job.py
  • 最小权限与持久化文件保护

    • 保护 ./data/fernet.key 的读权限与备份权限
    • ./data/pgdata 属于核心数据资产,应纳入备份与权限管理