Files
Neo-ZQYY/docs/_overview/04b-feedback/P1-6-trigger-api-merge.md
Neo 509cf43284 chore(docs): Wave 0 调研产出 + P0/P1/P2 反馈调研
建立项目级标杆文档 docs/_overview/ 作为产品全景索引,
解决"PRD 零碎、文档膨胀、跨子系统调研无入口"的问题。

主要内容:
- 00-index 总索引 + 维护协议 + 与 CLAUDE.md 关系
- 01-product-overview 产品全景脑图(6 角色 / 6 子系统 / 数据流 /
  7 业务概念 / 8+1 AI 矩阵 / 22 术语)
- 02a-miniprogram-page-matrix 小程序 21 页业务指纹
- 02b-adminweb-page-matrix admin-web 19 路由业务指纹
- 03-test-spec 测试规范 (L1-L5 分层 + 走查模板 + 75-95 case 估算)
- 04-doc-conflicts 39 条冲突索引(P0×8 / P1×13 / P2×13 + 5 子项)
- 04a/b/c-conflicts-*-detail 业务故事卡(7 字段:关联/逻辑/影响/选项/判定)
- 05-orphan-pages-cleanup admin-web 6 孤儿页面处置(1 归档 + 4 保留)
- WAVES-MASTER-PLAN.md 全 Wave 主计划(0-5,共 22-32 工作日)
- WAVE-1-KICKOFF.md Wave 1 实施 kickoff
- GLOBAL-DECISION-DASHBOARD.md 全局决策仪表板

反馈调研产物:
- 04a-feedback/ P0 两轮反馈(8+8 项决策 + D-1/2/3 + F-1/2 子代理产出)
- 04b-feedback/ P1 两轮反馈(13+1+5 项 + E-1/2/3/4 + G-1/2 子代理产出)
- 04c-feedback/ P2 反馈(13 项 + 5 子项 + H-1/2/3 子代理产出)
- NEO-DECISIONS-LOG 累积决策记录

关键追加发现 8 处 D Bug(原蓝本 0):
- P0-3 看板沙箱接入(Wave 1 W1-T1)
- P0-5 致命 1 (4 处 fdw_etl 残留, 已修 commit 17f045a)
- P0-5 致命 2 (JWT aud 缺失, 已修 commit 17f045a)
- P0-6 clearAllTasks 守卫 (Wave 3)
- P0-8 DBViewer 黑名单漏 (已修 commit 17f045a)
- P1-3 task-detail 跳转传 task_id 而非 customer_id
- P2-7 board-finance 隐式 null
- 2 个独立 Bug (page_context.created_at + ClueCategory 字典)

参考: docs/_overview/00-index.md
2026-05-04 07:38:28 +08:00

17 KiB
Raw Blame History

P1-6 触发器双 API 合并可行性

反馈背景Neo 倾向合并双 API/admin/ai/triggers vs /trigger-jobs "看下数据获取的泛用性,如果合适则合并"。 本文档基于现状代码评估泛用性、字段差异、合并方案与回归风险。

调研日期2026-05-04 调研范围:apps/backend/app/routers/{admin_triggers,trigger_jobs,admin_ai}.py / apps/backend/app/services/ai/admin_service.py / apps/admin-web/src/api/{adminAI,triggerJobs,triggers}.ts / apps/admin-web/src/pages/{AITriggers,TriggerJobs,TriggerManager}.tsx / 测试库 biz.{trigger_jobs,ai_trigger_jobs} 实际数据


一、双 API 字段对比(实际是 3 个 API

调研中发现当前实际有 3 个相关 API,不是 2 个,先把全图列清楚:

API 路径 文件 数据源 用途
GET /api/trigger-jobs routers/trigger_jobs.py biz.trigger_jobs 通用:所有定时任务(业务+AI 混合)
PATCH /api/trigger-jobs/:id/config 同上 biz.trigger_jobs 通用:改 cron / interval
POST /api/trigger-jobs/:id/run 同上 biz.trigger_jobs 通用:手动执行
GET /api/admin/ai/triggers routers/admin_ai.py:400 biz.trigger_jobs (filter job_type LIKE 'ai_%' OR job_name='task_generator') AI 视角:仅 AI 相关触发器
PATCH /api/admin/ai/triggers/:id 同上 biz.trigger_jobs AI 视角:改 cron / status / description
GET /api/admin/triggers/unified routers/admin_triggers.py 聚合 biz.trigger_jobs + biz.ai_trigger_jobs + public.scheduled_tasks 只读:跨数据源全景视图

核心事实

  • /admin/ai/triggers/trigger-jobs 操作的是同一张表 biz.trigger_jobs,前者只是加了 WHERE job_type LIKE 'ai_%' 过滤
  • /admin/triggers/unified 是只读聚合视图,跨 3 张表,与上述两个 API 不同源
  • biz.ai_trigger_jobs 是 AI 调用链历史记录表(事件实例),不是触发器配置表;当前测试库 0 条记录

1.1 GET 响应字段对比

字段 /trigger-jobs (TriggerJobItem) /admin/ai/triggers (TriggerItem)
id int int
job_name string string
job_type string string
trigger_condition string string
trigger_config dict|null dict不可为 null
last_run_at string|null string|null
next_run_at string|null string|null
status string string
description string|null string|null
last_error string|null string|null
created_at string|null 未返回

差异 = 1 个字段/admin/ai/triggers 不返回 created_at。两个返回值95% 重合

1.2 PATCH 入参字段对比

字段 /trigger-jobs/:id/config (UpdateTriggerConfigRequest) /admin/ai/triggers/:id (TriggerUpdateRequest)
cron_expression
interval_seconds
status
description
校验规则 "至少一个字段" + cron 正则 + interval ≥ 1 无 model_validatorcron 正则用 jsonb_set 写入

差异显著

  • /admin/ai/triggers 支持改 status启停和 description/trigger-jobs 不支持
  • /trigger-jobs 支持改 interval/admin/ai/triggers 不支持
  • 这是互补的字段集,不是冗余

1.3 排序与过滤差异

维度 /trigger-jobs /admin/ai/triggers
WHERE job_type LIKE 'ai_%' OR job_name = 'task_generator'
ORDER BY id trigger_condition DESC, job_name

1.4 测试库实际数据biz.trigger_jobs 9 行)

id  job_name                       job_type                       condition  status
1   task_generator                 task_generator                 cron       enabled  ← AI 列表也包含job_name 命中)
2   task_expiry_check              task_expiry_check              interval   enabled
3   recall_completion_check        recall_completion_check        event      enabled
4   note_reclassify_backfill       note_reclassify_backfill       event      enabled
57  ai_consumption_settled         ai_consumption_settled         event      enabled  ← AI
58  ai_note_created                ai_note_created                event      enabled  ← AI
59  ai_task_assigned               ai_task_assigned               event      enabled  ← AI
60  ai_dws_completed               ai_dws_completed               event      enabled  ← AI
61  ai_dws_prewarm_1000            ai_dws_prewarm                 cron       enabled  ← AI

/admin/ai/triggers 命中 6 行5 个 ai_ 前缀 + 1 个 task_generator /trigger-jobs 命中全部 9 行; /admin/triggers/unified 命中 9 行biz+ 0 行ai_trigger_jobs0 条记录)+ N 行 etl。


二、前端依赖分析

2.1 三个页面的数据消费

页面 数据源 消费的字段
pages/AITriggers.tsx (独立) listTriggers()/admin/ai/triggers id / job_name / trigger_condition / trigger_config.cron_expression / trigger_config.event_name / status / description / last_run_at / next_run_at / last_error
pages/TriggerJobs.tsx (独立) fetchTriggerJobs()/trigger-jobs id / job_name / trigger_condition / trigger_config.*cron+interval+event_name/ status / description / last_run_at / next_run_at / last_error
pages/TriggerManager.tsx (容器) 4 个 Tab
- all Tab → fetchUnifiedTriggers()/admin/triggers/unified
- biz Tab → fetchTriggerJobs()/trigger-jobsBizTriggersTab 内嵌
- ai Tab → 嵌入 <AITriggers /> + <AIOperations /> + <AITriggerJobs /> 三个组件
- etl Tab → fetchSchedules() → ETL API
按 Tab 分别展示

2.2 字段消费交集

AITriggers.tsxTriggerJobs.tsx / BizTriggersTab 消费的字段完全相同(除 created_at 在 AITriggers 未消费)。

2.3 操作能力消费

操作 AITriggers (in TriggerManager AI Tab) BizTriggersTab (in TriggerManager biz Tab) TriggerJobs (独立页面)
启停status 切换) Switch 组件
改 cron Modal Modal (只有列表 + run
改 interval Modal
改 description Modal
手动执行 Button
清空所有任务 (业务专用)

核心事实:三个页面消费字段几乎一致,但操作能力是互补的/admin/ai/triggers PATCH 比 /trigger-jobs/:id/config PATCH 多了 status / description少了 interval前端 UI 也按各自支持的字段开放对应控件。

2.4 路由注册情况admin-web 主导航)

apps/admin-web 当前 4 处入口:

  • 独立页面 TriggerJobs.tsx(路径见路由表)
  • 独立页面 AITriggers.tsx
  • 聚合容器 TriggerManager.tsx Tab "biz" 嵌套 BizTriggersTab(与 TriggerJobs.tsx 几乎重复)
  • 聚合容器 TriggerManager.tsx Tab "ai" 嵌套 <AITriggers />

已经有冗余BizTriggersTab 的 columns 与 TriggerJobs.tsx 重复定义。这是当前需要先收敛的 UI 层债务。


三、数据获取泛用性评估

3.1 三个 API 的"可替代性"矩阵

场景 可用 API 评估
列出全部触发器(业务+AI /trigger-jobs / /admin/triggers/unified(聚合,包含 etl 单源场景下 /trigger-jobs 已够用
仅看 AI 触发器 /admin/ai/triggers / /trigger-jobs + 前端过滤 后端过滤省一次网络传输;前端过滤更灵活
跨表全景biz + ai_jobs + etl /admin/triggers/unified 只读,无替代
改 status / description /admin/ai/triggers PATCH 缺口(/trigger-jobs 不支持)
改 interval /trigger-jobs/:id/config PATCH 缺口(/admin/ai/triggers 不支持)
手动 run /trigger-jobs/:id/run POST 缺口

结论GET 端可合并(/trigger-jobs?job_type_prefix=ai_ 即可替代 /admin/ai/triggersPATCH 端必须先合并字段集才能合并 APIPOST run 端 /admin/ai/triggers 本就没有,不冲突。

3.2 泛用性瓶颈

/trigger-jobs 的 PATCH 当前故意不暴露 status / description,原因推测:

  1. 业务触发器的启停可能影响 ETL 调度需要更严格的权限控制admin_ai 路由有 _require_admin() 守卫)
  2. description 是给后台管理员看的"说明",业务侧可能不希望随便改

如果直接合并,需要先决策:/trigger-jobs PATCH 是否允许改 status / description


四、合并方案对比

方案 A · 完全合并(删除 /admin/ai/triggers

步骤

  1. 扩展 apps/backend/app/schemas/trigger_jobs.py::UpdateTriggerConfigRequeststatus / description 字段
  2. apps/backend/app/routers/trigger_jobs.py::update_trigger_config 加白名单:仅当 status_new in (enabled, disabled) 时通过description 直接 UPDATE
  3. GET /api/trigger-jobs 加 query 参数 ?job_type_prefix=ai_,等价于 /admin/ai/triggers 的过滤
  4. apps/admin-web/src/api/adminAI.ts 删除 listTriggers / updateTrigger 函数
  5. apps/admin-web/src/pages/AITriggers.tsx 改用 triggerJobs.tsfetchTriggerJobs({ jobTypePrefix: "ai_" }) + 新 PATCH 函数
  6. 删除后端 /admin/ai/triggers 路由
  7. 同步收敛 BizTriggersTabTriggerJobs.tsx 的 column 重复定义

评估

维度
工作量 schemas/路由/前端 API 层/2 个组件改造)
回归范围 AITriggers 页面端到端 + TriggerManager AI/biz Tab + TriggerJobs 独立页 + __tests__/adminAiAppTypes.test.ts 类的对齐测试
长期维护成本 低(单源真相)
风险点 1改 status / description 的权限放开后业务触发器id 1-4可能被误改建议加守卫"业务触发器 status 改动需二次确认"。2admin-web 之外的调用方(如 mcp-server需排查

方案 B · 字段子集合并(保留两 API底层 service 合并)

步骤

  1. 新建 apps/backend/app/services/trigger_service.py,集中 list_triggers(filter) / update_trigger(id, fields) 两个内核函数
  2. routers/admin_ai.py::list_triggersrouters/trigger_jobs.py::get_trigger_jobs 都调用 trigger_service.list_triggers(),参数不同
  3. 两个 PATCH 端点都调用 trigger_service.update_trigger(...),前者传 status/description 子集,后者传 cron/interval 子集
  4. 前端代码不动

评估

维度
工作量 中(后端重构,前端零改动)
回归范围 后端 service 层单测 + 路由集成测试
长期维护成本 API 两份仍在,但底层一致)
风险点 没有真正解决 Neo 反馈的"双 API"困惑,前端开发仍要在两个 API 客户端之间选

方案 C · 不合并,只补文档边界

步骤

  1. apps/backend/app/routers/admin_ai.pytrigger_jobs.py 顶部 docstring 互引:"本路由 PATCH 仅改 status/description业务/AI 都可),改 cron/interval 请用 /trigger-jobs"
  2. 同步 apps/backend/CLAUDE.md 增加一节"触发器 API 边界"

评估

维度
工作量 极低(< 50 行注释)
长期维护成本 高(每次新人都要重新理解两 API 边界)

五、推荐实施步骤

推荐方案 A完全合并,分 4 阶段:

阶段 1 · 后端 API 扩展D+0

  1. apps/backend/app/schemas/trigger_jobs.py::UpdateTriggerConfigRequest 新增字段:
    status: Literal["enabled", "disabled"] | None = None
    description: str | None = None
    
    model_validator 改为"四选一即可"
  2. apps/backend/app/routers/trigger_jobs.py::update_trigger_config
    • 增加 status / description UPDATE 分支
    • 新增 query 参数 ?job_type_prefix=str for GET
    • 新增 query 参数 ?include_event=bool(默认 true便于按需排除
  3. 在 admin_ai.py 的 /admin/ai/triggers GET/PATCH 下加 deprecation headerDeprecation: true

阶段 2 · 前端切换调用D+1

  1. apps/admin-web/src/api/triggerJobs.ts::UpdateTriggerConfigReq 加 status/description
  2. apps/admin-web/src/api/triggerJobs.ts::fetchTriggerJobs 加可选 params: { job_type_prefix?: string }
  3. apps/admin-web/src/pages/AITriggers.tsx:
    • import 改为 triggerJobs.ts
    • listTriggers()fetchTriggerJobs({ job_type_prefix: 'ai_' })
    • updateTrigger(id, body)updateTriggerConfig(id, body)
  4. 收敛 pages/TriggerManager.tsx::BizTriggersTab columns 重复定义:直接 import TriggerJobs 或抽公共组件 <TriggerJobsTable filter={...} />

阶段 3 · 删除旧 APID+2灰度后

  1. 删除 apps/backend/app/routers/admin_ai.py/triggers 两个端点
  2. 删除 apps/backend/app/services/ai/admin_service.py::list_triggers / update_trigger
  3. 删除 apps/admin-web/src/api/adminAI.tslistTriggers / updateTrigger / TriggerItem / TriggerUpdateRequest
  4. 跑全量回归pytest backend + admin-web 端到端)

阶段 4 · 文档同步D+2

  1. apps/backend/CLAUDE.md 触发器章节加"统一 API: /api/trigger-jobs"
  2. 写审计 docs/audit/changes/<日期>__trigger-api-merge.md
  3. 顺手补:/admin/triggers/unified 仍保留(跨表只读视图),但在 docstring 标明"如果只看 biz 表用 /trigger-jobs跨 etl/ai_jobs 时再用 unified"

六、给 Neo 的决策清单

选项 推荐
Q1 采纳方案 A完全合并还是方案 B保留双 API 仅合并底层)? 方案 A
Q2 合并后 /trigger-jobs PATCH 允许改 status业务触发器id 1-4task_generator/task_expiry_check 等)是否需要"业务触发器禁止禁用"守卫? 推荐加一个白名单:PROTECTED_JOB_NAMES = {"task_generator"},禁用时 422
Q3 /admin/triggers/unified 是否保留?(它聚合了 ai_trigger_jobs + scheduled_tasks与单表 API 不同源) 保留(跨数据源场景仍需要)
Q4 合并后是否把独立页面 pages/AITriggers.tsx 也删掉,仅保留 TriggerManager.tsx 的 Tab 视图? 推荐删(已被 TriggerManager 覆盖)
Q5 pages/TriggerJobs.tsx 独立页面是否也合并到 TriggerManager.tsx?路由表清理? 推荐合并;路由表保留旧路径 redirect → TriggerManager?tab=biz
Q6 灰度策略:直接 D+0 删除旧 API还是 D+0 加 deprecation header → D+7 删除? 推荐 D+0 加 headerD+2 删除admin-web 是单仓库唯一调用方,可短窗口)

附录:关键文件清单

后端路由

  • apps/backend/app/routers/trigger_jobs.py — 通用 trigger_jobs API保留并扩展
  • apps/backend/app/routers/admin_ai.py:400-440/admin/ai/triggers GET/PATCH待删除
  • apps/backend/app/routers/admin_triggers.py/admin/triggers/unified 跨数据源聚合(保留)

后端 service

  • apps/backend/app/services/ai/admin_service.py:752-820list_triggers / update_trigger(待删除,逻辑迁移到 trigger_jobs 路由)
  • apps/backend/app/services/trigger_scheduler.py:346list_trigger_jobs(保留,是基础设施)

后端 schema

  • apps/backend/app/schemas/trigger_jobs.pyTriggerJobItem / UpdateTriggerConfigRequest(扩展 status/description
  • apps/backend/app/schemas/admin_ai.py:250-269TriggerItem / TriggerUpdateRequest(待删除)
  • apps/backend/app/schemas/admin_triggers.py:14UnifiedTriggerItem(保留)

前端 API

  • apps/admin-web/src/api/triggerJobs.ts — 主 API 客户端(扩展)
  • apps/admin-web/src/api/adminAI.ts:337-366listTriggers / updateTrigger / TriggerItem(待删除)
  • apps/admin-web/src/api/triggers.tsfetchUnifiedTriggers(保留)

前端页面

  • apps/admin-web/src/pages/TriggerManager.tsx — 容器页(保留)
  • apps/admin-web/src/pages/AITriggers.tsx — 改用 triggerJobs API 后保留组件,删 listTriggers 调用
  • apps/admin-web/src/pages/TriggerJobs.tsx — 独立页面(建议删除并 redirect
  • apps/admin-web/src/pages/AITriggerJobs.tsx — 注意:这是 ai_trigger_jobs 历史日志页面,与本调研 API 无关(用 /admin/ai/trigger-jobs 是另一组端点)

数据库

  • db/zqyy_app/schemas/biz.sql:344-360biz.trigger_jobs 表定义
  • db/zqyy_app/schemas/biz.sql:48-150biz.ai_trigger_jobs 表定义(事件历史,与触发器配置不同)

测试

  • apps/admin-web/src/__tests__/adminAiAppTypes.test.ts — 现有对齐测试(合并后扩展,加上 trigger PATCH 字段对齐用例)

审计参考

  • docs/audit/changes/2026-03-23__trigger-jobs-admin-web-miniprogram-cleanup.md — 上次 trigger_jobs 改动
  • docs/audit/changes/2026-03-24__trigger-jobs-clear-task-interaction.md — clear-all 交互改动