594 lines
22 KiB
Markdown
594 lines
22 KiB
Markdown
# ConnectHub 功能与部署指南
|
||
|
||
本文件面向开发与运维,汇总 ConnectHub 当前仓库 **已实现的全部功能(feature)**,并给出 **开发→上线(docker compose 手动)→备份/回滚/排障** 的全流程指南。
|
||
|
||
> 约定:本文所有“定位”均以本仓库当前代码为准;`extensions/` 下内容被定义为 **标准示例**(可复制改造),不等同于平台核心能力。
|
||
|
||
---
|
||
|
||
## 1. 项目概览
|
||
|
||
### 1.1 项目定位
|
||
|
||
ConnectHub 是一个轻量级企业集成中间件:统一管理多系统集成任务(Job),提供定时调度、执行监控与“一键重试/立即运行”。
|
||
|
||
### 1.2 组件与架构(文字版)
|
||
|
||
- **FastAPI + SQLAdmin**:提供管理台(任务/日志)与简单健康检查
|
||
- **PostgreSQL**:持久化任务定义与执行日志
|
||
- **Redis**:Celery broker/backend
|
||
- **Celery worker**:执行任务
|
||
- **Celery beat**:每分钟 tick 调度器,按 cron 触发任务
|
||
|
||
### 1.3 入口与检查点
|
||
|
||
- **Admin**:`http://<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.py`(`Job`)
|
||
- **Admin 视图定位**:`app/admin/views.py`(`JobAdmin`)
|
||
- **字段含义**:
|
||
- `id`:任务 ID(创建必填,可在表单中编辑主键)
|
||
- `enabled`:是否启用(调度只触发启用任务)
|
||
- `cron_expr`:cron 表达式(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.py`(`JobLog`,`JobStatus`)
|
||
- **Admin 视图定位**:`app/admin/views.py`(`JobLogAdmin`)
|
||
- **展示与能力**:
|
||
- 只读(不可创建/编辑/删除)
|
||
- 列表支持按 `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.py`(`beat_schedule` 每 60 秒)
|
||
- **触发任务名**:`connecthub.dispatcher.tick`
|
||
|
||
- **调度器逻辑**
|
||
- **定位**:`app/tasks/dispatcher.py`
|
||
- **行为**:
|
||
- 只读取 `enabled=True` 的 Jobs(`app/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.py`(Celery task:`connecthub.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 调用层)
|
||
|
||
- **致远 OA(SeeyonClient)**
|
||
- **定位**:`app/integrations/seeyon.py`
|
||
- **能力**:
|
||
- `POST /seeyon/rest/token` 获取 token(`id`)
|
||
- 业务请求自动携带 `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.py`(`Settings` 从 `.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.log`(10MB * 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` 创建 engine;sqlite 兼容 `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`
|
||
- 行为:
|
||
- backend:`uvicorn --reload`
|
||
- worker/beat:`watchfiles` 监听 `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 SyncOAToDidiTokenJob(token 获取演示)
|
||
|
||
- **目的**:演示调用致远 OA 获取 token,并在日志中进行基础脱敏输出。
|
||
- **需要的 public_cfg 字段**
|
||
- `base_url`:致远 OA base url(不包含具体路径)
|
||
- **需要的 secret_cfg 字段(解密后)**
|
||
- `rest_user`:REST 帐号
|
||
- `rest_password`:REST 密码
|
||
- `loginName`:可选(模拟登录名)
|
||
- **行为特征**
|
||
- 使用 `SeeyonClient.authenticate()` 获取 token
|
||
- 日志输出会对 token 做 mask(避免完整泄露)
|
||
|
||
### 3.3 SyncOAToDidiExportFormJob(CAP4 无流程表单导出)
|
||
|
||
- **目的**:调用致远 OA CAP4 表单导出接口,返回原始响应文本,并将大文本按块写入 run_log(尽力而为)。
|
||
- **需要的 public_cfg 字段**
|
||
- `base_url`
|
||
- `templateCode`
|
||
- 可选:`senderLoginName` / `rightId` / `doTrigger` / `param` / `extra`(扩展字段 dict)
|
||
- **需要的 secret_cfg 字段(解密后)**
|
||
- `rest_user` / `rest_password` / `loginName`
|
||
- **行为特征**
|
||
- 返回结构中包含 `raw`(原始文本)与 `meta`(content_length/content_type 等)
|
||
- 大文本会拆分 chunk 记录到 run_log(仍受 run_log 总量上限截断)
|
||
|
||
### 3.4 SyncOAToDidiLegalEntitySyncJob(OA→滴滴公司主体同步示例)
|
||
|
||
- **目的**:从 OA 导出数据中解析“工号/所属公司”,并同步到滴滴员工的 `legal_entity_id`。
|
||
- **需要的 public_cfg 字段**
|
||
- `oa_base_url`
|
||
- `oa_templateCode`
|
||
- `didi_base_url`
|
||
- 可选:`senderLoginName/rightId/doTrigger/param/extra`(透传到 OA 导出)
|
||
- **需要的 secret_cfg 字段(解密后)**
|
||
- OA:`rest_user/rest_password/loginName`
|
||
- 滴滴:`company_id/client_id/client_secret/sign_key`
|
||
- **行为特征(示例策略)**
|
||
- 从表单 definition 中定位 display 为“工号/所属公司”的字段名
|
||
- 公司主体查询结果做进程内缓存
|
||
- 员工修改按文档要求增加 150ms 间隔(避免限频)
|
||
- 输出统计:总行数/成功/跳过/错误列表(截取前 50)
|
||
|
||
---
|
||
|
||
## 4. 开发全流程指南(本地开发)
|
||
|
||
### 4.1 前置条件
|
||
|
||
- 安装 Docker 与 docker compose
|
||
- 端口规划(默认 compose 暴露):
|
||
- `8000`:backend / Admin
|
||
- `5432`:postgres(如不需要宿主机直连,可在 compose 里移除映射)
|
||
- `6379`:redis(同上)
|
||
|
||
### 4.2 初始化与启动(开发模式)
|
||
|
||
1) 在仓库根目录创建 `.env`(参考 `env.example`):
|
||
|
||
```bash
|
||
cp env.example .env
|
||
```
|
||
|
||
2) 启动(开发 overlay,支持热更新):
|
||
|
||
```bash
|
||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
|
||
```
|
||
|
||
或使用脚本:
|
||
|
||
```bash
|
||
./connecthub.sh dev-build
|
||
./connecthub.sh dev-start
|
||
```
|
||
|
||
3) 首次启动会自动完成(无需手工执行):
|
||
- 创建 `/data` 目录(宿主机对应 `./data`)
|
||
- 生成并持久化 Fernet key(默认 `./data/fernet.key`)
|
||
- 初始化/升级数据库 schema(建表、补列、兼容约束)
|
||
|
||
4) 打开 Admin:
|
||
- `http://localhost:8000/admin`
|
||
|
||
### 4.3 新增一个插件 Job(标准步骤)
|
||
|
||
1) 在 `extensions/` 下创建你的模块与 Job 类(继承 `BaseJob`,实现 `run(params, secrets)`)。
|
||
2) 在 Admin 创建 Job:
|
||
- `id`:自定义(建议带命名空间)
|
||
- `enabled`:true
|
||
- `cron_expr`:例如 `* * * * *`(每分钟)
|
||
- `handler_path`:例如 `extensions.your_ext.job:YourJob`
|
||
- `public_cfg`:必须是 JSON object(形如 `{...}`)
|
||
- `secret_cfg`:必须是 JSON object(形如 `{...}`,保存时会加密)
|
||
3) 观察执行结果:
|
||
- 通过 Job 上的“立即运行”或等待 cron 触发
|
||
- 在 JobLog 中查看 `message/traceback/run_log/snapshot_params`
|
||
|
||
### 4.4 查看日志(开发/排查)
|
||
|
||
- 查看全部服务日志:
|
||
|
||
```bash
|
||
./connecthub.sh log
|
||
```
|
||
|
||
- 只看 worker(并 follow):
|
||
|
||
```bash
|
||
./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/pgdata`:PostgreSQL 数据目录(必须)
|
||
- `./data/fernet.key`:Fernet key(必须,影响历史 secret_cfg 可解密)
|
||
- `./data/logs`:文件日志(可选,但推荐便于排障)
|
||
- **端口与网络**
|
||
- 若非必要,建议仅暴露 `8000`;`5432/6379` 建议仅内网或不对外映射
|
||
- **配置文件**
|
||
- `.env`:与代码同目录放置(compose `env_file: .env`)
|
||
- 核心变量:`DB_URL` / `REDIS_URL` / `DATA_DIR` / `FERNET_KEY_PATH` / `LOG_DIR`
|
||
|
||
### 5.2 首次上线(从零部署)
|
||
|
||
1) 准备代码与配置:
|
||
|
||
```bash
|
||
cd /path/to/your/deploy/dir
|
||
cp env.example .env
|
||
```
|
||
|
||
2) 构建并启动(生产 compose):
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
或使用脚本:
|
||
|
||
```bash
|
||
./connecthub.sh build
|
||
```
|
||
|
||
3) 验收检查点:
|
||
- `docker compose ps` 中 `backend/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) 重新构建并启动:
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
3) 验证点(升级后必做):
|
||
- `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`):
|
||
|
||
```bash
|
||
docker compose exec -T postgres pg_dump -U connecthub -d connecthub > backup_connecthub.sql
|
||
```
|
||
|
||
2) 同步备份 Fernet key:
|
||
|
||
```bash
|
||
cp ./data/fernet.key ./backup_fernet.key
|
||
```
|
||
|
||
3) 恢复(新环境/故障恢复):
|
||
- 启动 postgres/redis(可先不启动 worker/beat)
|
||
- 导入 SQL:
|
||
|
||
```bash
|
||
cat backup_connecthub.sql | docker compose exec -T postgres psql -U connecthub -d connecthub
|
||
```
|
||
|
||
- 还原 Fernet key:
|
||
|
||
```bash
|
||
cp ./backup_fernet.key ./data/fernet.key
|
||
```
|
||
|
||
- 启动全量服务并验证:
|
||
- Admin 能打开
|
||
- 历史 Job 能正常运行(证明 secret_cfg 可解密)
|
||
|
||
### 6.3 目录快照备份(简单,但对一致性要求更高)
|
||
|
||
> 目录级备份前建议短暂停止写入(至少停止 worker/beat),避免备份不一致。
|
||
|
||
1) 停止任务执行(建议):
|
||
|
||
```bash
|
||
docker compose stop worker beat
|
||
```
|
||
|
||
2) 打包 `pgdata` 与 `fernet.key`:
|
||
|
||
```bash
|
||
tar -czf backup_data_$(date +%F).tgz ./data/pgdata ./data/fernet.key
|
||
```
|
||
|
||
3) 恢复后再启动 worker/beat:
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 排障手册(症状 → 定位 → 处理)
|
||
|
||
### 7.1 Admin 保存 Job 报错:public_cfg/secret_cfg 必须是 JSON object
|
||
|
||
- **症状**:保存时报 `public_cfg must be a JSON object` 或 `secret_cfg must be a JSON object`
|
||
- **定位**:`app/admin/views.py`(`on_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/Shanghai`(`app/tasks/dispatcher.py`)
|
||
- last_run_at 是否阻挡:同一分钟不会重复触发(`last_run_at >= now_min` 会跳过)
|
||
- **处理**:
|
||
- 先用 Job 的“立即运行”验证执行链路(排除插件/外部系统问题)
|
||
- 确认 beat 正常运行后再排 cron
|
||
|
||
### 7.3 Retry 提示“正在运行中,请结束后再重试”
|
||
|
||
- **症状**:点击 Retry 被拒绝
|
||
- **定位**:`app/admin/routes.py`(RUNNING 状态保护)
|
||
- **处理**:
|
||
- 等待任务结束(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_cfg`(JSON object)保存,让系统重新加密
|
||
|
||
### 7.5 外部系统 API 调用失败(401/超时/限频)
|
||
|
||
- **定位**:
|
||
- 致远:`app/integrations/seeyon.py`(401/Invalid token 会自动刷新重试一次)
|
||
- 滴滴:`app/integrations/didi.py`(401 会刷新 token 重试一次;示例 Job 有 150ms 间隔)
|
||
- **处理**:
|
||
- 查看 JobLog 的 `run_log` 与 `traceback`
|
||
- 核对 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` 属于核心数据资产,应纳入备份与权限管理
|
||
|