Files
Neo-ZQYY/apps/backend/docs/API-REFERENCE.md
Neo 80bda9b991 chore(audit): 2026-04-20 历史批次预审 + 文档同步 + .gitignore 修正
- 新增 docs/audit/changes/2026-04-20__historical-batch-pre-audit.md
  157 文件分批盘点审计(7 条主线 + 10 项高/中风险 + 2 份迁移 SQL DDL 清单)
- 补追 docs/audit/changes/2026-04-15__meituan-settle-core-sync.md
  原审计产物因 .gitignore 屏蔽长期未入仓,本次一并追回
- 刷新 docs/audit/audit_dashboard.md(33 条审计记录)
- .gitignore 白名单放行 docs/audit/changes/*.md 与 audit_dashboard.md
  同时屏蔽 changes/changes/ 嵌套误产物目录
- 新增 docs/specs/audit-gap-recovery/tasks.md
  扫描嵌套目录发现 96 份 D 类孤本(从未入过 git history),
  生成独立 PRD 供单开任务清理与补追
- 文档同步(高风险项):
  - apps/backend/docs/API-REFERENCE.md (+69)
  - apps/miniprogram/README.md (+50)
  - apps/etl/connectors/feiqiu/docs/architecture/data_flow.md (+52/-2)
  - apps/etl/connectors/feiqiu/docs/architecture/system_overview.md (+5/-3)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 06:32:58 +08:00

1495 lines
43 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API 参考手册
后端 API 基于 FastAPI 构建,所有端点均以 `/api/` 为前缀。
在线文档:启动后访问 `http://localhost:8000/docs`Swagger UI`/redoc`ReDoc
## 全局响应格式RNS1.0
所有 JSON 成功响应HTTP 2xx自动包装为统一格式
```json
{ "code": 0, "data": { ... } }
```
异常响应格式:
```json
{ "code": 401, "message": "未认证" }
```
规则:
- `code=0` 表示成功,`data` 为业务数据
- `code≠0` 表示错误,`code` 为 HTTP 状态码,`message` 为错误描述
- SSE 流式端点(`text/event-stream`)和非 JSON 响应不包装,直接透传
- 所有小程序 API 响应字段名为 camelCase`userId``storeName`
---
## 1. 管理后台认证 `/api/auth`
### POST `/api/auth/login`
管理后台用户名密码登录。
请求体:
```json
{ "username": "admin", "password": "..." }
```
响应:
```json
{ "access_token": "...", "refresh_token": "...", "token_type": "bearer" }
```
### POST `/api/auth/refresh`
刷新访问令牌。
请求体:
```json
{ "refresh_token": "..." }
```
---
## 2. 小程序认证 `/api/xcx-auth`
小程序用户的完整生命周期:微信登录 → 提交申请 → 管理员审批 → 正式使用。
### POST `/api/xcx-auth/login`
微信登录。用 `wx.login()` 获取的 code 换取 JWT。
请求体:
```json
{ "code": "微信临时登录凭证" }
```
响应:
```json
{
"access_token": "...",
"refresh_token": "...",
"token_type": "bearer",
"user_status": "pending | approved | rejected | disabled",
"user_id": 1
}
```
说明:
- 首次登录自动创建 `auth.users` 记录status=new前端引导至申请页
- new/pending/rejected 用户获得受限令牌(`limited=True`),仅可访问申请相关端点
- approved 用户获得完整令牌,包含 `site_id``roles`
### POST `/api/xcx-auth/apply`
提交入驻申请。需受限令牌或完整令牌。
请求体:
```json
{
"site_code": "AB123",
"applied_role_text": "助教",
"phone": "13800138000",
"employee_number": "E001",
"nickname": "张三"
}
```
说明:
- `site_code` 格式2 字母 + 3 数字(如 `AB123`),映射到 `auth.site_code_mapping`
- 后端自动进行人员匹配(`matching.py`),在 ETL 库中查找助教/员工记录
### GET `/api/xcx-auth/status`
查询当前用户状态和申请记录。需受限令牌或完整令牌。
响应:
```json
{
"user_id": 1,
"status": "approved",
"nickname": "张三",
"applications": [
{
"id": 1,
"site_code": "AB123",
"applied_role_text": "助教",
"status": "approved",
"review_note": null,
"created_at": "2026-02-25T10:00:00",
"reviewed_at": "2026-02-25T11:00:00"
}
]
}
```
### GET `/api/xcx-auth/sites`
获取当前用户关联的门店列表。需完整令牌。
### POST `/api/xcx-auth/switch-site`
切换当前门店,返回新的令牌对。需完整令牌。
请求体:
```json
{ "site_id": 2 }
```
### POST `/api/xcx-auth/refresh`
刷新令牌。
请求体:
```json
{ "refresh_token": "..." }
```
### POST `/api/xcx-auth/dev-login`
开发模式 mock 登录(仅 `WX_DEV_MODE=true` 时注册)。
请求体:
```json
{ "openid": "模拟openid", "status": "approved" }
```
说明:
- `status` 可选,为空时保留已有用户当前状态,新用户默认 `new`
- 仅开发/测试环境可用
---
## 3. 任务配置 `/api/tasks`
所有端点需 JWT 认证。
### GET `/api/tasks/registry`
按业务域分组的 ETL 任务列表。
响应示例:
```json
{
"groups": {
"会员": [
{
"code": "DWD_LOAD_FROM_ODS",
"name": "ODS → DWD 加载",
"domain": "会员",
"layer": "DWD",
"requires_window": true,
"is_ods": false,
"is_dimension": false,
"default_enabled": true,
"is_common": true
}
]
}
}
```
### GET `/api/tasks/dwd-tables`
按业务域分组的 DWD 表定义。
### GET `/api/tasks/flows`
返回 7 种 Flow 定义和 4 种处理模式。
Flow 列表:
| ID | 名称 | 层级 |
|----|------|------|
| `api_ods` | API → ODS | ODS |
| `api_ods_dwd` | API → ODS → DWD | ODS, DWD |
| `api_full` | API → ODS → DWD → DWS → INDEX | ODS, DWD, DWS, INDEX |
| `ods_dwd` | ODS → DWD | DWD |
| `dwd_dws` | DWD → DWS汇总 | DWS |
| `dwd_dws_index` | DWD → DWS → INDEX | DWS, INDEX |
| `dwd_index` | DWD → DWS指数 | INDEX |
处理模式:
| ID | 名称 | 说明 |
|----|------|------|
| `increment_only` | 仅增量处理 | 只处理新增和变更的数据 |
| `verify_only` | 仅校验修复 | 校验现有数据并修复不一致 |
| `increment_verify` | 增量 + 校验修复 | 先增量处理,再校验并修复 |
| `full_window` | 全窗口处理 | 用 API 返回数据的实际时间范围处理全部层 |
### POST `/api/tasks/validate`
验证任务配置并返回 CLI 命令预览。`store_id` 从 JWT 自动注入。
### GET `/api/tasks/sync-check`
对比后端硬编码任务列表与 ETL 真实注册表,返回差异。
---
## 4. 任务执行 `/api/execution`
所有端点需 JWT 认证,`site_id` 从 JWT 提取。
### POST `/api/execution/run`
直接执行任务(不经过队列)。异步启动 ETL CLI 子进程。
请求体:`TaskConfigSchema`flow、tasks、window 等)
响应:
```json
{ "execution_id": "uuid", "message": "任务已提交执行" }
```
### GET `/api/execution/queue`
获取当前门店的待执行队列。
### POST `/api/execution/queue`
将任务配置添加到执行队列。
### PUT `/api/execution/queue/reorder`
调整队列中任务的执行顺序。
### DELETE `/api/execution/queue/{task_id}`
从队列中删除待执行任务(仅 pending 状态)。
### POST `/api/execution/{execution_id}/cancel`
取消正在执行的任务。
### GET `/api/execution/history`
执行历史记录(按 `started_at` 降序,默认 50 条,最多 200 条)。
### GET `/api/execution/{execution_id}/logs`
获取指定执行的完整日志。优先从内存缓冲区读取(执行中),否则从数据库读取(已完成)。
---
## 5. 调度管理 `/api/schedules`
所有端点需 JWT 认证。
### GET `/api/schedules`
列出当前门店的所有调度任务。
### 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`
所有端点需 JWT 认证。使用 ETL 只读连接 + RLS 门店隔离。
### GET `/api/db/schemas`
返回 ETL 数据库中的 Schema 列表。
### GET `/api/db/schemas/{name}/tables`
返回指定 Schema 下所有表的名称和行数统计。
### GET `/api/db/tables/{schema}/{table}/columns`
返回指定表的列定义(列名、数据类型、是否可空、默认值)。
### POST `/api/db/query`
只读 SQL 执行。
安全措施:
- 拦截写操作关键词INSERT / UPDATE / DELETE / DROP / TRUNCATE
- 返回行数上限 1000 行
- 查询超时 30 秒
- 连接级 `read_only` 保护
请求体:
```json
{ "sql": "SELECT * FROM dwd.member_info LIMIT 10" }
```
---
## 7. ETL 状态 `/api/etl-status`
### GET `/api/etl-status/cursors`
返回各 ODS 表的最新数据游标(查询 `meta.etl_cursor`)。
### GET `/api/etl-status/recent-runs`
返回最近 50 条任务执行记录。
---
## 8. 环境配置 `/api/env-config`
### GET `/api/env-config`
读取根 `.env` 文件内容(敏感值脱敏显示)。
### PUT `/api/env-config`
更新 `.env` 文件中的配置项。
---
## 9. 运维面板 `/api/ops`
### GET `/api/ops/system`
服务器系统资源概况CPU、内存、磁盘、启动时间
### GET `/api/ops/services`
所有环境test/prod的服务运行状态PID、端口、内存、CPU、运行时长
### POST `/api/ops/services/{env}/start`
启动指定环境的后端服务。
### POST `/api/ops/services/{env}/stop`
停止指定环境的后端服务。
### POST `/api/ops/services/{env}/restart`
重启指定环境的后端服务。
### GET `/api/ops/git`
所有环境的 Git 状态(分支、最新提交、是否有本地修改)。
### POST `/api/ops/git/{env}/pull`
对指定环境执行 `git pull --ff-only`
### POST `/api/ops/git/{env}/sync-deps`
对指定环境执行 `uv sync --all-packages`
### GET `/api/ops/env-file/{env}`
读取指定环境的 `.env` 文件(敏感值脱敏)。
---
## 10. 其他端点
### GET `/health`
健康检查。返回 `{"status": "ok"}`
### GET `/api/xcx-test`
MVP 全链路验证端点,从 `test."xcx-test"` 表读取数据。
### GET/POST `/api/wx-callback`
微信消息推送回调。GET 用于签名验证POST 用于接收消息。
---
## 11. 管理端申请审核 `/api/admin/applications`
### GET `/api/admin/applications`
获取待审核申请列表。需管理后台 JWT。
### POST `/api/admin/applications/{id}/approve`
批准申请。
### POST `/api/admin/applications/{id}/reject`
拒绝申请。
---
## 12. 营业日配置 `/api/business-day`
### GET `/api/business-day/config`
获取营业日分割点配置(`BUSINESS_DAY_START_HOUR`)。
---
## 13. 小程序任务 `/api/xcx/tasks`
所有端点需 JWTapproved 状态)。
### GET `/api/xcx/tasks`
获取当前助教的活跃任务列表。
响应:`TaskListItem[]`
### POST `/api/xcx/tasks/{id}/pin`
置顶任务。
### POST `/api/xcx/tasks/{id}/unpin`
取消置顶。
### POST `/api/xcx/tasks/{id}/abandon`
放弃任务(需填写原因)。
请求体:
```json
{ "reason": "放弃原因" }
```
### POST `/api/xcx/tasks/{id}/restore`
取消放弃,恢复为活跃状态。
### GET `/api/xcx/tasks/{id}`
获取任务详情TASK-2。返回单个任务的完整信息含服务记录、备注、AI 分析、维客线索。
响应包含 `customerId` 字段,供前端跳转客户详情/对话页面使用。
---
## 14. 小程序绩效 `/api/xcx/performance`
所有端点需 JWTapproved 状态)。
### GET `/api/xcx/performance`
绩效概览PERF-1。返回指定月份的收入档位、DateGroup 分组记录、新客/常客列表。
查询参数:
- `month`:月份(格式 `YYYY-MM`,默认当月)
响应包含:
- `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。返回指定月份的服务记录明细按日期分组支持分页。
查询参数:
- `year`:年份(必填)
- `month`:月份(`1-12`,必填)
- `page`:页码(默认 1
- `page_size`:每页条数(默认 20最大 100
- `coach_id`:目标助教 ID可选管理者视角
权限分流(运行时通过 `get_user_permissions()` 实时检查,非 `require_permission` 中间件):
- 不带 `coach_id`(查自己):需 `view_tasks` 权限,`assistant_id` 由当前登录用户绑定决定
-`coach_id`(查他人):需 `view_board_coach` 权限(`manager`/`head_coach`/`staff``assistant_id` 使用传入值;同 site 约束由 `user.site_id` 隐式保证
- 缺少对应权限统一返回 `HTTP 403 权限不足`
对应服务层变更:`performance_service.get_records()` 新增 `assistant_id_override` 参数。
响应字段新增:
- `dateGroups[].records[].isScattered`:散客标记(`member_id ≤ 0` 时为 `true`),前端据此将客户姓名置灰
---
## 15. 小程序备注 `/api/xcx/notes`
所有端点需 JWTapproved 状态)。
### POST `/api/xcx/notes`
创建备注(含星星评分,可选关联任务)。
请求体:
```json
{
"target_type": "member",
"target_id": 1,
"content": "备注内容",
"task_id": null,
"rating_service_willingness": 4,
"rating_revisit_likelihood": 3
}
```
### GET `/api/xcx/notes`
查询某目标的备注列表(按创建时间倒序)。
查询参数:
- `target_type`:目标类型(默认 `member`
- `target_id`:目标 ID必填
### DELETE `/api/xcx/notes/{id}`
删除备注(验证归属后硬删除)。
---
## 16. 小程序配置 `/api/xcx/config`
所有端点需 JWTapproved 状态)。
### GET `/api/xcx/config/skill-types`
项目类型筛选器配置CONFIG-1。返回前端筛选器选项列表。
数据源:`app.v_cfg_area_category`(基于 `dws.cfg_area_category` 去重到 category 级别,排除 SPECIAL/OTHER按 sort_order 排序)。
响应头部自动插入"不限"选项key=ALL不存储在数据库中。
响应:`SkillTypeItem[]`
```json
[
{ "key": "ALL", "label": "不限", "emoji": "🔍", "cls": "" },
{ "key": "BILLIARD", "label": "🎱 中式/追分", "emoji": "🎱", "cls": "" },
{ "key": "SNOOKER", "label": "斯诺克", "emoji": "斯", "cls": "" },
{ "key": "MAHJONG", "label": "🀄 麻将/棋牌", "emoji": "🀄", "cls": "" },
{ "key": "KTV", "label": "🎤 团建/K歌", "emoji": "🎤", "cls": "" }
]
```
降级行为:查询失败时返回空数组 `[]`
---
## 17. 小程序三看板 `/api/xcx/board`
所有端点需 JWT + 对应权限。
### GET `/api/xcx/board/coaches`
助教看板BOARD-1。返回助教列表支持排序×技能×时间三重筛选。
查询参数:
- `sort`:排序维度(`perf_desc`/`perf_asc`/`salary_desc`/`salary_asc`/`sv_desc`/`task_desc`,默认 `perf_desc`
- `skill`:技能筛选(`ALL`/`BILLIARD`/`SNOOKER`/`MAHJONG`/`KTV`,默认 `ALL`
- `time`:时间范围(`month`/`quarter`/`last_month`/`last_3m`/`last_quarter`/`last_6m`,默认 `month`
约束:`time=last_6m` + `sort=sv_desc` 互斥,返回 400。
权限:`view_board_coach`
响应:`CoachBoardResponse`(含 `items` + `dimType`
### GET `/api/xcx/board/customers`
客户看板BOARD-2。返回客户列表支持维度×项目筛选 + 分页。
查询参数:
- `dimension`:客户维度(`recall`/`potential`/`balance`/`recharge`/`recent`/`spend60`/`freq60`/`loyal`,默认 `recall`
- `project`:项目筛选(`ALL`/`BILLIARD`/`SNOOKER`/`MAHJONG`/`KTV`,默认 `ALL`
- `page`:页码(默认 1
- `page_size`:每页条数(默认 20最大 100
权限:`view_board_customer`
响应:`CustomerBoardResponse`(含 `items`/`total`/`page`/`pageSize`
### GET `/api/xcx/board/finance`
财务看板BOARD-3。返回 6 大板块(经营一览/预收资产/营收结构/现金流/支出/助教分析)。
查询参数:
- `time`:时间范围(`month`/`lastMonth`/`week`/`lastWeek`/`quarter3`/`quarter`/`lastQuarter`/`half6`,默认 `month`
- `area`:区域筛选(`all`/`hall`/`hallA`/`hallB`/`hallC`/`mahjong`/`teamBuilding`,默认 `all`
- `compare`环比开关0=关闭/1=开启,默认 0
约束:`area≠all``recharge` 板块为 null`compare=0` 时响应不含环比字段。
权限:`view_board_finance`
响应:`FinanceBoardResponse`(含 `overview`/`recharge`/`revenue`/`cashflow`/`expense`/`coachAnalysis`
---
## 17A. 小程序助教详情 `/api/xcx/coaches`
所有端点需 JWTapproved 状态)+ `view_board_coach` 权限。
### GET `/api/xcx/coaches/{coach_id}/banner`
助教 banner 轻量信息。仅返回 `id` / `name` / `level` / `storeName`,用于 `coach-service-records` 等只需 banner 数据的页面首屏快速加载。
`/{coach_id}` 详情快一个数量级。
权限:`view_board_coach`
响应:`CoachBannerResponse`
```json
{ "id": 123, "name": "张三", "level": "金牌", "storeName": "朗朗桌球(总店)" }
```
### GET `/api/xcx/coaches/{coach_id}`
助教详情COACH-1。返回助教基础信息、绩效、收入、档位、任务分组、TOP 客户、近期服务、历史月份、备注。
权限:`view_board_coach`2026-03-27 权限改造 W4助教详情跟助教看板走
响应:`CoachDetailResponse`
字段说明:
- `storeName`:助教所在门店名(跟随被查看助教所在门店,供小程序 banner 展示)
- `performance``PerformanceMetrics`,与任务页 `PerformanceSummary` 同源(来自 `monthly_summary` 实时值)。字段从 6 扩展到 25
- 核心字段:`totalHours``totalIncome``totalCustomers``monthLabel``tierNodes``basicHours``bonusHours``currentTier`(数组下标 0-based`nextTierHours``tierCompleted``bonusMoney``incomeTrend``incomeTrendDir``prevMonth``currentTierLabel`
- 详情专属扩展:`customerBalance``tasksCompleted`
- 兼容旧字段(前端渐进适配):`monthlyHours``monthlySalary`
- `taskStats``CoachTaskStats`,当月任务完成统计(按 `task_type` 分类计数,数据源 `coach_tasks` 表)
- `callback``follow_up_visit` 完成数
- `recall``high_priority_recall` + `priority_recall` 完成数
- `topCustomers[].isScattered`:散客标识(`member_id ≤ 0`),前端据此将客户姓名置灰
- `serviceRecords[].isScattered`:同上,作用于近期服务记录
---
## 17B. 小程序客户详情 `/api/xcx/customers`
所有端点需 JWTapproved 状态)+ `view_board_customer` 权限。
### GET `/api/xcx/customers/{customer_id}`
客户详情CUST-1。返回客户基础信息、Banner 概览、AI 洞察、助教任务、心动助教、维客线索、消费记录、备注。
权限:`view_board_customer`2026-03-27 权限改造 W4客户详情跟客户看板走
响应:`CustomerDetailResponse`
字段说明(本次新增):
- `consumptionRecords[].foodDetail`:自定义食品类目名称(`string | null`)。为空时前端降级展示「食品酒水」
- `notes[].creatorName`:备注创建者姓名
- `notes[].creatorRole`:备注创建者角色
---
## 18. 小程序 CHAT `/api/xcx/chat`
所有端点需 JWTapproved 状态)。替代原 `xcx_ai_chat``/api/ai/*`),统一迁移到 `/api/xcx/chat/*` 路径。
### GET `/api/xcx/chat/history`
对话历史列表CHAT-1。返回当前用户的对话列表按最后消息时间倒序。
查询参数:
- `page`:页码(默认 1
- `page_size`:每页条数(默认 20最大 100
响应:`ChatHistoryResponse`(含 `items`/`total`/`page`/`pageSize`
每条对话包含:`id`(对话 ID`title`(对话标题)、`customerName`(关联客户姓名,可选)、`lastMessage`(最后消息摘要)、`timestamp`(最后消息时间)、`unreadCount`(未读数)。
### GET `/api/xcx/chat/messages`
通过上下文查询消息CHAT-2b。根据 `contextType` + `contextId` 自动查找或创建对话。
查询参数:
- `contextType`:上下文类型(`task`/`customer`/`coach`/`general`,必填)
- `contextId`:上下文 ID必填
- `page`:页码(默认 1
- `page_size`:每页条数(默认 50最大 100
对话复用规则:`task` 始终复用(无时限);`customer`/`coach` ≤ 3 天复用、> 3 天新建;`general` 始终新建。
响应:`ChatMessagesResponse`(含 `chatId`/`items`/`total`/`page`/`pageSize`
### GET `/api/xcx/chat/{chat_id}/messages`
通过 chatId 查询消息CHAT-2a。消息按 `created_at` 正序。
路径参数:
- `chat_id`:对话 ID
查询参数:
- `page`:页码(默认 1
- `page_size`:每页条数(默认 50最大 100
响应:`ChatMessagesResponse`
每条消息包含:`id``role``user`/`assistant`)、`content``createdAt``referenceCard`(可选,含 `type`/`title`/`summary`/`data` 键值对)。
### POST `/api/xcx/chat/{chat_id}/messages`
发送消息并获取同步 AI 回复CHAT-3。chatId 归属验证:不属于当前用户返回 403。
请求体:
```json
{ "content": "消息内容" }
```
响应:`SendMessageResponse`(含 `userMessage``aiReply`,各含 `id`/`content`/`createdAt`
AI 失败降级:用户消息仍保存,`aiReply.content` 返回错误提示HTTP 200。
### POST `/api/xcx/chat/stream`
SSE 流式对话端点CHAT-4。chatId 归属验证在流开始前完成。
请求体:
```json
{ "chatId": 1, "content": "消息内容" }
```
响应:`text/event-stream`(不经过 ResponseWrapper 包装)
SSE 事件类型:
- `event: message``data: {"token": "文本片段"}`
- `event: done``data: {"messageId": 123, "createdAt": "ISO8601"}`
- `event: error``data: {"message": "错误描述"}`
---
## 15. 维客线索 `/api/member-retention-clue`
替代原 `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 状态码 | 含义 |
|-------------|------|
| 200 | 成功 |
| 201 | 创建成功 |
| 400 | 请求参数错误 / SQL 执行错误 |
| 401 | 未认证 / 令牌无效 / 受限令牌 |
| 404 | 资源不存在 |
| 408 | 查询超时 |
| 409 | 状态冲突(如删除非 pending 任务) |
| 422 | 请求体验证失败 |
| 500 | 服务器内部错误 |