feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本

包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-06 00:03:48 +08:00
parent 70324d8542
commit 6f8f12314f
515 changed files with 76604 additions and 7456 deletions

View File

@@ -255,15 +255,42 @@ Flow 列表:
### POST `/api/schedules`
创建调度任务,自动计算 `next_run_at`
请求体新增字段P16
- `min_run_interval_value`最小运行间隔数值int默认 0 表示无限制)
- `min_run_interval_unit`:间隔单位(`minutes`/`hours`/`days`,默认 `minutes`
### PUT `/api/schedules/{schedule_id}`
更新调度任务(部分更新,仅更新请求中提供的字段)。
请求体新增可选字段P16
- `min_run_interval_value`:最小运行间隔数值
- `min_run_interval_unit`:间隔单位
### DELETE `/api/schedules/{schedule_id}`
删除调度任务。
### PATCH `/api/schedules/{schedule_id}/toggle`
切换启用/禁用状态。禁用时 `next_run_at` 置 NULL启用时重新计算。
### POST `/api/schedules/{schedule_id}/run`
手动触发执行调度任务。
查询参数:
- `force`是否强制执行bool默认 `false`
行为:
- `force=false`:检查并发状态和最小间隔,不满足条件返回 409
- `force=true`:绕过所有检查,直接入队执行
错误码:
- 409任务正在执行中`last_status='running'`
- 409最小运行间隔未到提示距下次可执行的剩余时间
列表响应新增字段P16
- `min_run_interval_value`最小运行间隔数值0 表示无限制)
- `min_run_interval_unit`:间隔单位
- `last_success_at`最后一次成功执行时间TIMESTAMPTZ可为 null
---
## 6. 数据库查看器 `/api/db`
@@ -422,7 +449,15 @@ MVP 全链路验证端点,从 `test."xcx-test"` 表读取数据。
查询参数:
- `month`:月份(格式 `YYYY-MM`,默认当月)
响应包含:`currentTier`/`nextTier`/`upgradeHoursNeeded`/`upgradeBonus`(收入档位)、`thisMonthRecords`DateGroup 分组)、`lastMonthIncome``incomeItems`(含 desc`newCustomers`/`regularCustomers`
响应包含:
- `currentTier`:当前档位助教到手单价(`basicRate` = 客户价 - base_deduction`incentiveRate` = 客户价 × (1 - bonus_deduction_ratio)
- `nextTier`:下一档到手单价(同上公式,使用下一档抽成参数)
- `upgradeHoursNeeded`距下一档所需小时数next_tier.min_hours - total_hours
- `upgradeBonus`:升档后因抽成降低的节省额
- `incomeItems`:始终 3 项基础课收入、激励课收入、Top3 销冠奖),金额为 0 时仍显示
- `thisMonthRecords`DateGroup 分组)、`lastMonthIncome``newCustomers`/`regularCustomers`
档位数据来源:`cfg_performance_tier` 配置表(通过 `fdw_queries.get_performance_tiers()` 查询)。
### GET `/api/xcx/performance/records`
绩效明细PERF-2。返回指定月份的服务记录明细按日期分组支持分页。
@@ -613,11 +648,768 @@ SSE 事件类型:
替代原 `member-birthday` 端点,提供维客线索管理能力。
---
## 19. 租户管理后台认证 `/api/tenant/auth`
租户管理员独立认证体系,与小程序认证完全隔离。使用用户名+密码登录JWT `aud=tenant-admin`
### POST `/api/tenant/auth/login`
租户管理员登录。
请求体:
```json
{ "username": "admin01", "password": "..." }
```
响应:
```json
{ "access_token": "...", "refresh_token": "...", "token_type": "bearer" }
```
错误码:
- 401用户名或密码错误统一消息不区分
- 403账号已被禁用`is_active=false`
### POST `/api/tenant/auth/refresh`
刷新租户管理员令牌。验证 `aud=tenant-admin` + `type=refresh`
请求体:
```json
{ "refresh_token": "..." }
```
响应:同登录响应格式。
错误码:
- 401无效的刷新令牌 / 令牌类型不匹配
---
## 20. 租户用户审核与管理 `/api/tenant`
所有端点需租户管理员 JWT`aud=tenant-admin`),数据自动按 `managed_site_ids` 隔离。
### GET `/api/tenant/my-sites`
当前管理员管辖的店铺列表(用于前端筛选下拉)。
响应:
```json
[
{ "siteId": 1, "siteName": "XX球房", "siteCode": "LLQ001" }
]
```
### GET `/api/tenant/roles`
小程序可用角色列表(从 `auth.roles` 动态读取,排除 `tenant_admin`/`site_admin` 管理类角色)。
响应:`RoleItem[]`(含 `id`/`code`/`name`/`description`
### GET `/api/tenant/site-staff`
按角色 + 门店查询人员候选列表。直连 ETL 库底层表,手动 `site_id` 过滤FDW 视图 RLS 跨库不生效)。
查询参数:
- `role`:角色 code`coach` → 查 `dwd.dim_assistant`,其他 → 查 `dwd.dim_staff`,必填)
- `site_id`:店铺 site_id`site_code` 二选一)
- `site_code`:店铺编号(与 `site_id` 二选一)
响应:`StaffCandidate[]`(含 `id`/`identityLabel`/`name`/`mobile`/`entryTime`/`source`
说明:
- coach 角色的 `identityLabel``dws.cfg_assistant_level_price` 配置表读取中文等级名,回退为数字
- 非 coach 角色的 `identityLabel` 使用 `dim_staff.job` 字段(职位名称)
### GET `/api/tenant/applications`
申请列表,支持状态筛选、门店筛选和分页。排除 `cancelled` 状态申请。
查询参数:
- `status`:按状态筛选(`pending`/`approved`/`rejected`,可选)
- `site_id`:按门店筛选(可选)
- `page`:页码(默认 1
- `page_size`:每页条数(默认 20最大 100
隔离策略:
- `tenant_admin`:按 `tenant_id` 过滤,覆盖该租户下所有店铺(不受 JWT `managed_site_ids` 限制)
- `site_admin`:按 `managed_site_ids` 精确过滤
响应:`{ items, total, page, pageSize }`
说明:`phone` 字段取自 `auth.user_applications.phone`申请时填写NOT NULL`auth.users.phone`
### GET `/api/tenant/applications/{id}/match-suggestions`
关联匹配建议。通过 `site_code_mapping` 解析 `site_id`,在 `v_dim_assistant``scd2_is_current=1`)和 `v_dim_staff` + `v_dim_staff_ex` 中按 phone 匹配。
响应:`MatchSuggestion[]`(含 `assistantId`/`staffId`/`name`/`number`/`sourceTable`
### POST `/api/tenant/applications/{id}/approve`
审核通过。事务内多表写入:`users.status='approved'``user_site_roles``user_assistant_binding``user_applications.status='approved'`
请求体:
```json
{ "role": "coach", "assistantId": 123, "staffId": null }
```
错误码:
- 409该申请已被处理非 pending 状态)
### POST `/api/tenant/applications/{id}/reject`
审核拒绝。
请求体:
```json
{ "reason": "拒绝原因" }
```
错误码:
- 409该申请已被处理
### GET `/api/tenant/users`
已通过审核用户列表,支持角色筛选、关键词搜索和分页。
查询参数:
- `role`:按角色筛选(可选)
- `keyword`:关键词搜索(姓名/手机号,可选)
- `page`:页码(默认 1
- `page_size`:每页条数(默认 20最大 100
响应:`{ items, total, page, pageSize }`
### PATCH `/api/tenant/users/{id}`
编辑用户角色/门店/状态。
请求体(部分更新):
```json
{ "role": "staff", "siteId": 2, "status": "disabled" }
```
错误码:
- 403目标门店不在管辖范围内
- 404用户不存在
### PUT `/api/tenant/users/{id}/binding`
更新用户助教/员工绑定。
请求体:
```json
{ "assistantId": 456, "staffId": null }
```
---
## 21. 租户 Excel 上传 `/api/tenant/excel`
所有端点需租户管理员 JWT。支持 4 种模板财务支出expense、团购收入platform_income、助教奖罚salary_adj、充值业绩归属recharge_commission
### POST `/api/tenant/excel/upload`
上传 Excel 文件,执行格式校验 → 人员匹配 → 冲突检测。
请求:`multipart/form-data`
- `file`Excel 文件(.xlsx/.xls
- `upload_type`:模板类型
- `site_id`:门店 ID
响应:
```json
{
"errors": [],
"warnings": [{ "rowIndex": 2, "column": "助教姓名", "message": "未匹配到助教/员工:张三" }],
"passedRows": [...],
"uploadId": 1,
"conflicts": [{ "rowIndex": 3, "fieldDiffs": [{ "field": "金额", "oldValue": "100.00", "newValue": "200.00" }] }]
}
```
错误码:
- 400无效的模板类型 / 无效的 Excel 文件 / 文件内容为空 / 解析失败
### POST `/api/tenant/excel/confirm`
确认写入。单事务写入目标表,失败回滚整批。
请求体:
```json
{
"uploadId": 1,
"resolutions": [
{ "rowIndex": 3, "action": "replace" },
{ "rowIndex": 5, "action": "keep" }
]
}
```
响应:
```json
{ "message": "写入成功", "inserted": 10, "updated": 2, "resolved": 3 }
```
错误码:
- 404上传记录不存在
- 409该上传批次已被处理
### GET `/api/tenant/excel/logs`
上传记录列表,分页,按 `managed_site_ids` 隔离。
查询参数:
- `page`:页码(默认 1
- `page_size`:每页条数(默认 20最大 100
响应:`{ items, total, page, pageSize }`
### GET `/api/tenant/excel/template/{type}`
下载空白 Excel 模板文件(含表头和格式说明)。
路径参数:
- `type`:模板类型(`expense`/`platform_income`/`salary_adj`/`recharge_commission`
响应:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
---
## 22. 租户维客线索管理 `/api/tenant`
所有端点需租户管理员 JWT。线索操作先校验 `site_id` 是否在管辖范围内,不在则返回 404避免泄露线索存在性
### GET `/api/tenant/customers/search`
客户搜索。在管辖门店范围内搜索 `v_dim_member`nickname 模糊匹配 OR mobile 精确匹配,`scd2_is_current=1`)。手机号脱敏返回。
查询参数:
- `keyword`:搜索关键词(必填,最少 1 字符)
- `site_id`:指定门店 ID 筛选(可选)
响应:
```json
{
"items": [
{ "memberId": 1, "nickname": "张三", "mobileMasked": "138****1234", "siteId": 1, "siteName": "XX球房" }
]
}
```
### GET `/api/tenant/customers/{member_id}/clues`
该客户在管辖门店范围内的全部线索,支持 source 和 is_hidden 筛选。
查询参数:
- `source`:按来源筛选(`manual`/`ai_consumption`/`ai_note`,可选)
- `is_hidden`:按隐藏状态筛选(可选)
响应:`{ items: ClueListItem[] }`
### PATCH `/api/tenant/clues/{id}`
编辑线索 category/summary/detail。
请求体:
```json
{ "category": "消费习惯", "summary": "每周消费 2-3 次", "detail": "偏好周末下午时段" }
```
错误码:
- 404线索不存在或不在管辖范围
- 422无效的线索大类 / 摘要为空或超长
### DELETE `/api/tenant/clues/{id}`
物理删除线索。
错误码:
- 404线索不存在
### PATCH `/api/tenant/clues/{id}/visibility`
切换线索 is_hidden 状态。
请求体:
```json
{ "isHidden": true }
```
---
## 23. 租户店铺管理员 CRUD `/api/tenant/site-admins`
所有端点需租户管理员 JWT`aud=tenant-admin`),且 `admin_type` 必须为 `tenant_admin`。店铺管理员(`admin_type='site_admin'`)无权访问。
### GET `/api/tenant/site-admins`
店铺管理员列表。
响应:`SiteAdmin[]`(含 `id`/`username`/`displayName`/`managedSiteIds`/`isActive`/`createdAt`
### POST `/api/tenant/site-admins`
创建店铺管理员。`admin_type` 自动设为 `site_admin``tenant_id` 继承当前租户管理员。
请求体:
```json
{
"username": "LL0001zhangsan",
"password": "...",
"displayName": "张三",
"managedSiteIds": [1]
}
```
- 409用户名已存在
### PATCH `/api/tenant/site-admins/{id}`
编辑店铺管理员displayName / managedSiteIds / isActive
- 404管理员不存在或不属于当前租户
- 422至少需要提供一个修改字段
### DELETE `/api/tenant/site-admins/{id}`
软删除店铺管理员(`deleted_at` 设为当前时间)。
- 404管理员不存在
- 409管理员已被删除
### POST `/api/tenant/site-admins/{id}/reset-password`
重置店铺管理员密码。
请求体:
```json
{ "newPassword": "..." }
```
- 404管理员不存在
---
## 24. 管理端租户管理员 CRUD `/api/admin`
所有端点需管理后台 JWT`admin_users` 表认证)。
### GET `/api/admin/tenant-admins`
管理员列表,支持分页和关键词搜索。
查询参数:
- `page`:页码(默认 1
- `page_size`:每页条数(默认 20最大 100
- `keyword`:关键词搜索(用户名/显示名称,可选)
- `include_inactive`是否包含已禁用管理员bool默认 `false`
响应:`{ items, total, page, pageSize }`
每条记录新增 `tenant_name` 字段(来自 `biz.tenants` JOIN
### POST `/api/admin/tenant-admins`
创建租户管理员。密码 bcrypt 哈希存储。`tenant_id``biz.tenants` 选择,`managed_site_ids``biz.sites` 选择。
请求体:
```json
{
"username": "admin01",
"password": "...",
"displayName": "管理员01",
"tenantId": 1,
"managedSiteIds": [1, 2, 3]
}
```
响应201
```json
{ "id": 1, "createdAt": "2026-03-20T10:00:00" }
```
错误码:
- 409用户名已存在
### PATCH `/api/admin/tenant-admins/{id}`
编辑租户管理员username / display_name / managed_site_ids / is_active
请求体(部分更新):
```json
{ "username": "newname", "displayName": "新名称", "managedSiteIds": [1, 2], "isActive": false }
```
错误码:
- 404租户管理员不存在
- 409用户名已存在修改 username 时冲突)
- 422至少需要提供一个修改字段
### DELETE `/api/admin/tenant-admins/{id}`
软删除租户管理员(`is_active=false`)。
错误码:
- 404租户管理员不存在
- 409管理员已被禁用
### POST `/api/admin/tenant-admins/{id}/reset-password`
重置租户管理员密码。
请求体:
```json
{ "newPassword": "..." }
```
错误码:
- 404租户管理员不存在
### WebSocket `/ws/logs/{execution_id}`
实时日志推送。连接后自动接收指定执行的日志流。
---
## 25. 注册体系管理 `/api/admin`admin_registry
管理「连接器 → 租户 → 店铺」三级注册体系。所有端点需管理后台 JWT。
### GET `/api/admin/tenants`
所有活跃租户列表(含连接器名称)。
响应:
```json
[
{
"id": 1,
"tenant_id": 12345,
"tenant_name": "朗朗桌球",
"connector_name": "飞球",
"is_active": true
}
]
```
### GET `/api/admin/tenants/{tenant_id}/sites`
指定租户下所有活跃店铺。
路径参数:
- `tenant_id`:租户 ID`biz.tenants.id`
响应:
```json
[
{
"id": 1,
"site_id": 67890,
"site_name": "XX球房",
"site_code": "LLQ001",
"site_label": "朗朗桌球XX店",
"is_active": true
}
]
```
### PUT `/api/admin/sites/{site_id}/site-code`
设置/修改店铺简写ID。事务内执行旧 code 标记退役 → 新 code 插入历史 → 更新 sites.site_code。
路径参数:
- `site_id`:店铺 ID`biz.sites.id`
请求体:
```json
{ "new_code": "LLQ002" }
```
响应:
```json
{
"site_id": 1,
"old_code": "LLQ001",
"new_code": "LLQ002",
"history_cleaned": false
}
```
校验规则:
- 格式6 位字符3+3 模式,统一大写存储
- 全局唯一:`biz.sites.site_code` + `biz.site_code_history.site_code` 均不可重复
错误码:
- 400格式不合法
- 409简写ID 已被占用
### GET `/api/admin/sites/{site_id}/site-code-history`
查看店铺简写ID 变更历史。
路径参数:
- `site_id`:店铺 ID`biz.sites.id`
响应:
```json
[
{
"id": 1,
"site_code": "LLQ001",
"is_current": false,
"created_at": "2026-03-20T10:00:00",
"retired_at": "2026-03-22T15:30:00"
},
{
"id": 2,
"site_code": "LLQ002",
"is_current": true,
"created_at": "2026-03-22T15:30:00",
"retired_at": null
}
]
```
### POST `/api/admin/sites/sync`
手动触发店铺信息同步。通过 FDW 读取 ETL 库 `dwd.dim_site``scd2_is_current=1`),对比 `biz.sites`,新增/更新店铺信息。
响应:
```json
{ "inserted": 3, "updated": 1 }
```
---
## 26. AI 监控后台 `/api/admin/ai`
所有端点需管理后台 JWT`Depends(require_admin)`。P15 新增13 个端点。
### GET `/api/admin/ai/dashboard`
AI 运行总览。返回今日统计、7 天趋势、App 分布、Token 预算、告警列表、App 健康状态。
查询参数:
- `site_id`:门店筛选(可选)
### GET `/api/admin/ai/trigger-jobs`
调度任务分页列表 + 今日去重统计。
查询参数:
- `event_type``status``site_id``date_from``date_to`:筛选(均可选)
- `page`:页码(默认 1
- `page_size`:每页条数(默认 20
### GET `/api/admin/ai/trigger-jobs/{job_id}`
调度任务详情(含 payload、error_message
### POST `/api/admin/ai/trigger-jobs/{job_id}/retry`
手动重跑:创建新 trigger_job`is_forced=true`),异步执行。
### GET `/api/admin/ai/run-logs`
调用记录分页列表。
查询参数:
- `app_type``status``trigger_type``site_id``date_from``date_to`:筛选(均可选)
- `page``page_size`:分页
### GET `/api/admin/ai/run-logs/{log_id}`
调用记录详情(含完整 prompt/response/error不脱敏
### POST `/api/admin/ai/cache/invalidate`
批量缓存失效。
请求体:
```json
{ "site_id": 123, "app_type": "app3_clue", "member_id": 456 }
```
`site_id` 必填,`app_type``member_id` 可选。
### GET `/api/admin/ai/budget`
Token 预算使用情况(日/月已用量、上限、百分比)。
### POST `/api/admin/ai/batch-run`
创建批量执行请求,返回预估(不立即执行)。
请求体:
```json
{ "app_types": ["app3_clue", "app7_customer_analysis"], "member_ids": [1, 2, 3], "site_id": 123 }
```
响应:`{ batch_id, estimated_calls, estimated_tokens }`
### POST `/api/admin/ai/batch-run/confirm`
确认批量执行(`batch_id` 有效期 10 分钟)。
请求体:
```json
{ "batch_id": "uuid" }
```
错误码:
- 400batch_id 无效或已过期
- 409batch_id 已确认
### GET `/api/admin/ai/alerts`
告警列表(`ai_run_logs` WHERE status IN ('failed','timeout','circuit_open'))。
查询参数:
- `alert_status`:告警状态筛选(可选)
- `site_id`:门店筛选(可选)
- `page``page_size`:分页
### POST `/api/admin/ai/alerts/{log_id}/ack`
确认告警:`alert_status → acknowledged`
### POST `/api/admin/ai/alerts/{log_id}/ignore`
忽略告警:`alert_status → ignored`
---
## 27. 开发调试全链路日志 `/api/admin/dev-trace`
所有端点需管理后台 JWT + admin 角色(`Depends(require_admin)`),非 admin 返回 403。
DevTrace 模块提供开发阶段的全链路请求追踪能力,覆盖 HTTP 请求、SSE 流式响应、WebSocket 连接、后台 Job 执行、异常/错误、数据库连接生命周期和中间件层。日志以 JSON Lines 格式写入本地文件系统,仅用于开发调试,不影响生产环境。
### GET `/api/admin/dev-trace/dates`
返回有日志数据的日期列表。
响应:
```json
{ "dates": ["2026-03-23", "2026-03-22", "2026-03-21"] }
```
### GET `/api/admin/dev-trace/requests`
按条件分页查询请求列表。
查询参数:
- `date`:日期筛选(格式 `YYYY-MM-DD`,必填)
- `start_time`:开始时间(`HH:MM`,可选)
- `end_time`:结束时间(`HH:MM`,可选)
- `trace_type`:追踪类型筛选(`http`/`sse`/`ws`/`job`,可选)
- `method`HTTP 方法筛选(`GET`/`POST`/`PUT`/`DELETE`,可选)
- `path_contains`:路径关键词模糊匹配(可选)
- `status_code`HTTP 状态码精确匹配(可选)
- `min_duration`:最小耗时(毫秒,可选)
- `has_error`是否包含错误bool可选
- `span_type`:包含指定 span 类型的记录(可选)
- `page`:页码(默认 1
- `page_size`:每页条数(默认 50
响应:`{ items, total, page, pageSize }`
### GET `/api/admin/dev-trace/request/{request_id}`
返回指定 request_id 的完整 trace 记录(含所有 spans
路径参数:
- `request_id`:请求 ID
响应包含完整字段:`request_id``trace_type``timestamp``method``path``status_code``total_duration_ms``user_id``site_id``db_query_count``db_total_ms``error``spans`span 数组,每个含 `span_type`/`module`/`function`/`description_zh`/`duration_ms`/`params`/`result_summary`/`extra`)。
错误码:
- 404指定 request_id 不存在
### POST `/api/admin/dev-trace/cleanup`
按日期范围手动清理日志。
请求体:
```json
{ "start_date": "2026-03-20", "end_date": "2026-03-21" }
```
响应:
```json
{ "deleted_dates": ["2026-03-20", "2026-03-21"], "deleted_count": 2 }
```
### GET `/api/admin/dev-trace/settings`
返回当前 trace 设置。
响应:
```json
{
"enabled": true,
"log_dir": "export/dev-trace-logs",
"retention_days": 7,
"log_sql": true,
"log_params": true,
"coverage_scan_interval_minutes": 60
}
```
### PUT `/api/admin/dev-trace/settings`
更新运行时设置(不需重启,即时生效)。
请求体(部分更新):
```json
{ "enabled": false, "retention_days": 14, "log_sql": false }
```
响应:更新后的完整设置对象(同 GET 响应格式)。
### GET `/api/admin/dev-trace/coverage`
返回最近一次覆盖率扫描结果。
响应:
```json
{
"scanned_at": "2026-03-23T10:00:00",
"route": { "total": 30, "covered": 28, "uncovered": ["func_a", "func_b"] },
"service": { "total": 45, "covered": 40, "uncovered": ["svc_x", "svc_y", "svc_z", "svc_w", "svc_v"] },
"job": { "total": 4, "covered": 4, "uncovered": [] },
"sse": { "total": 1, "covered": 1, "uncovered": [] },
"ws": { "total": 1, "covered": 1, "uncovered": [] }
}
```
### POST `/api/admin/dev-trace/coverage/scan`
手动触发覆盖率扫描。
响应:扫描完成后的覆盖率结果(同 GET 响应格式)。
---
## 28. 数据库健康监控 `/api/admin/db-health`
所有端点需管理后台 JWT。
### GET `/api/admin/db-health`
返回 4 个数据库的健康状态etl_feiqiu / test_etl_feiqiu / zqyy_app / test_zqyy_app
对每个库执行诊断 SQL`pg_stat_activity`(连接池)、`pg_database_size()`(大小)、慢查询统计。连接失败时返回 `status: "disconnected"`,其余字段为 null。即使所有库都连接失败仍返回 HTTP 200。
响应:
```json
[
{
"db_name": "zqyy_app",
"status": "connected",
"active_connections": 5,
"idle_connections": 3,
"db_size_mb": 128.45,
"slow_query_count": 0
},
{
"db_name": "etl_feiqiu",
"status": "disconnected",
"active_connections": null,
"idle_connections": null,
"db_size_mb": null,
"slow_query_count": null
}
]
```
---
## 29. 触发器统一视图 `/api/admin/triggers`
所有端点需管理后台 JWT。
### GET `/api/admin/triggers/unified`
聚合三张表的触发器数据,返回统一格式列表。
数据源:
- `biz.trigger_jobs`(业务触发器)→ `source="biz"`
- `biz.ai_trigger_jobs`AI 事件链,最近 100 条)→ `source="ai"`
- `public.scheduled_tasks`ETL 调度)→ `source="etl"`
某数据源查询失败时记录日志,返回其他数据源数据。
响应:
```json
[
{
"id": 1,
"name": "daily_sync",
"source": "biz",
"trigger_condition": "cron",
"status": "active",
"last_run_at": "2026-03-25T03:00:00",
"next_run_at": "2026-03-26T03:00:00",
"last_error": null
}
]
```
---
## 30. 触发器配置编辑 `/api/trigger-jobs`
在已有的 `/api/trigger-jobs` 路由模块中新增 PATCH 端点。需管理后台 JWT。
### PATCH `/api/trigger-jobs/{id}/config`
编辑触发器的 `cron_expression``interval_seconds`
仅 merge 请求中非 null 的字段到 `trigger_config` JSONB不覆盖其他已有字段。更新后重新计算 `next_run_at`
请求体(部分更新,至少提供一个字段):
```json
{ "cron_expression": "0 3 * * 1", "interval_seconds": null }
```
校验规则:
- 空请求体(两个字段均为 null→ 422
- `cron_expression` 格式无效(非 5 字段 / 超范围值)→ 422
- `interval_seconds < 1` → 422
- `job_id` 不存在 → 404
响应:更新后的完整 `TriggerJobItem`
---
## 错误码约定
| HTTP 状态码 | 含义 |