包含多个会话的累积代码变更: - 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>
40 KiB
API 参考手册
后端 API 基于 FastAPI 构建,所有端点均以 /api/ 为前缀。
在线文档:启动后访问 http://localhost:8000/docs(Swagger UI)或 /redoc(ReDoc)。
全局响应格式(RNS1.0)
所有 JSON 成功响应(HTTP 2xx)自动包装为统一格式:
{ "code": 0, "data": { ... } }
异常响应格式:
{ "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
管理后台用户名密码登录。
请求体:
{ "username": "admin", "password": "..." }
响应:
{ "access_token": "...", "refresh_token": "...", "token_type": "bearer" }
POST /api/auth/refresh
刷新访问令牌。
请求体:
{ "refresh_token": "..." }
2. 小程序认证 /api/xcx-auth
小程序用户的完整生命周期:微信登录 → 提交申请 → 管理员审批 → 正式使用。
POST /api/xcx-auth/login
微信登录。用 wx.login() 获取的 code 换取 JWT。
请求体:
{ "code": "微信临时登录凭证" }
响应:
{
"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
提交入驻申请。需受限令牌或完整令牌。
请求体:
{
"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
查询当前用户状态和申请记录。需受限令牌或完整令牌。
响应:
{
"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
切换当前门店,返回新的令牌对。需完整令牌。
请求体:
{ "site_id": 2 }
POST /api/xcx-auth/refresh
刷新令牌。
请求体:
{ "refresh_token": "..." }
POST /api/xcx-auth/dev-login
开发模式 mock 登录(仅 WX_DEV_MODE=true 时注册)。
请求体:
{ "openid": "模拟openid", "status": "approved" }
说明:
status可选,为空时保留已有用户当前状态,新用户默认new- 仅开发/测试环境可用
3. 任务配置 /api/tasks
所有端点需 JWT 认证。
GET /api/tasks/registry
按业务域分组的 ETL 任务列表。
响应示例:
{
"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 等)
响应:
{ "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:检查并发状态和最小间隔,不满足条件返回 409force=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保护
请求体:
{ "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
所有端点需 JWT(approved 状态)。
GET /api/xcx/tasks
获取当前助教的活跃任务列表。
响应:TaskListItem[]
POST /api/xcx/tasks/{id}/pin
置顶任务。
POST /api/xcx/tasks/{id}/unpin
取消置顶。
POST /api/xcx/tasks/{id}/abandon
放弃任务(需填写原因)。
请求体:
{ "reason": "放弃原因" }
POST /api/xcx/tasks/{id}/restore
取消放弃,恢复为活跃状态。
GET /api/xcx/tasks/{id}
获取任务详情(TASK-2)。返回单个任务的完整信息,含服务记录、备注、AI 分析、维客线索。
响应包含 customerId 字段,供前端跳转客户详情/对话页面使用。
14. 小程序绩效 /api/xcx/performance
所有端点需 JWT(approved 状态)。
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)。返回指定月份的服务记录明细,按日期分组,支持分页。
查询参数:
month:月份(格式YYYY-MM,默认当月)page:页码(默认 1)page_size:每页条数(默认 20)
15. 小程序备注 /api/xcx/notes
所有端点需 JWT(approved 状态)。
POST /api/xcx/notes
创建备注(含星星评分,可选关联任务)。
请求体:
{
"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
所有端点需 JWT(approved 状态)。
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[]
[
{ "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)
18. 小程序 CHAT /api/xcx/chat
所有端点需 JWT(approved 状态)。替代原 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。
请求体:
{ "content": "消息内容" }
响应:SendMessageResponse(含 userMessage 和 aiReply,各含 id/content/createdAt)
AI 失败降级:用户消息仍保存,aiReply.content 返回错误提示,HTTP 200。
POST /api/xcx/chat/stream
SSE 流式对话端点(CHAT-4)。chatId 归属验证在流开始前完成。
请求体:
{ "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
租户管理员登录。
请求体:
{ "username": "admin01", "password": "..." }
响应:
{ "access_token": "...", "refresh_token": "...", "token_type": "bearer" }
错误码:
- 401:用户名或密码错误(统一消息,不区分)
- 403:账号已被禁用(
is_active=false)
POST /api/tenant/auth/refresh
刷新租户管理员令牌。验证 aud=tenant-admin + type=refresh。
请求体:
{ "refresh_token": "..." }
响应:同登录响应格式。
错误码:
- 401:无效的刷新令牌 / 令牌类型不匹配
20. 租户用户审核与管理 /api/tenant
所有端点需租户管理员 JWT(aud=tenant-admin),数据自动按 managed_site_ids 隔离。
GET /api/tenant/my-sites
当前管理员管辖的店铺列表(用于前端筛选下拉)。
响应:
[
{ "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过滤,覆盖该租户下所有店铺(不受 JWTmanaged_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'。
请求体:
{ "role": "coach", "assistantId": 123, "staffId": null }
错误码:
- 409:该申请已被处理(非 pending 状态)
POST /api/tenant/applications/{id}/reject
审核拒绝。
请求体:
{ "reason": "拒绝原因" }
错误码:
- 409:该申请已被处理
GET /api/tenant/users
已通过审核用户列表,支持角色筛选、关键词搜索和分页。
查询参数:
role:按角色筛选(可选)keyword:关键词搜索(姓名/手机号,可选)page:页码(默认 1)page_size:每页条数(默认 20,最大 100)
响应:{ items, total, page, pageSize }
PATCH /api/tenant/users/{id}
编辑用户角色/门店/状态。
请求体(部分更新):
{ "role": "staff", "siteId": 2, "status": "disabled" }
错误码:
- 403:目标门店不在管辖范围内
- 404:用户不存在
PUT /api/tenant/users/{id}/binding
更新用户助教/员工绑定。
请求体:
{ "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
响应:
{
"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
确认写入。单事务写入目标表,失败回滚整批。
请求体:
{
"uploadId": 1,
"resolutions": [
{ "rowIndex": 3, "action": "replace" },
{ "rowIndex": 5, "action": "keep" }
]
}
响应:
{ "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 筛选(可选)
响应:
{
"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。
请求体:
{ "category": "消费习惯", "summary": "每周消费 2-3 次", "detail": "偏好周末下午时段" }
错误码:
- 404:线索不存在(或不在管辖范围)
- 422:无效的线索大类 / 摘要为空或超长
DELETE /api/tenant/clues/{id}
物理删除线索。
错误码:
- 404:线索不存在
PATCH /api/tenant/clues/{id}/visibility
切换线索 is_hidden 状态。
请求体:
{ "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 继承当前租户管理员。
请求体:
{
"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
重置店铺管理员密码。
请求体:
{ "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 选择。
请求体:
{
"username": "admin01",
"password": "...",
"displayName": "管理员01",
"tenantId": 1,
"managedSiteIds": [1, 2, 3]
}
响应(201):
{ "id": 1, "createdAt": "2026-03-20T10:00:00" }
错误码:
- 409:用户名已存在
PATCH /api/admin/tenant-admins/{id}
编辑租户管理员(username / display_name / managed_site_ids / is_active)。
请求体(部分更新):
{ "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
重置租户管理员密码。
请求体:
{ "newPassword": "..." }
错误码:
- 404:租户管理员不存在
WebSocket /ws/logs/{execution_id}
实时日志推送。连接后自动接收指定执行的日志流。
25. 注册体系管理 /api/admin(admin_registry)
管理「连接器 → 租户 → 店铺」三级注册体系。所有端点需管理后台 JWT。
GET /api/admin/tenants
所有活跃租户列表(含连接器名称)。
响应:
[
{
"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)
响应:
[
{
"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)
请求体:
{ "new_code": "LLQ002" }
响应:
{
"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)
响应:
[
{
"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,新增/更新店铺信息。
响应:
{ "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
批量缓存失效。
请求体:
{ "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
创建批量执行请求,返回预估(不立即执行)。
请求体:
{ "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 分钟)。
请求体:
{ "batch_id": "uuid" }
错误码:
- 400:batch_id 无效或已过期
- 409:batch_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
返回有日志数据的日期列表。
响应:
{ "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
按日期范围手动清理日志。
请求体:
{ "start_date": "2026-03-20", "end_date": "2026-03-21" }
响应:
{ "deleted_dates": ["2026-03-20", "2026-03-21"], "deleted_count": 2 }
GET /api/admin/dev-trace/settings
返回当前 trace 设置。
响应:
{
"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
更新运行时设置(不需重启,即时生效)。
请求体(部分更新):
{ "enabled": false, "retention_days": 14, "log_sql": false }
响应:更新后的完整设置对象(同 GET 响应格式)。
GET /api/admin/dev-trace/coverage
返回最近一次覆盖率扫描结果。
响应:
{
"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。
响应:
[
{
"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"
某数据源查询失败时记录日志,返回其他数据源数据。
响应:
[
{
"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。
请求体(部分更新,至少提供一个字段):
{ "cron_expression": "0 3 * * 1", "interval_seconds": null }
校验规则:
- 空请求体(两个字段均为 null)→ 422
cron_expression格式无效(非 5 字段 / 超范围值)→ 422interval_seconds < 1→ 422job_id不存在 → 404
响应:更新后的完整 TriggerJobItem。
错误码约定
| HTTP 状态码 | 含义 |
|---|---|
| 200 | 成功 |
| 201 | 创建成功 |
| 400 | 请求参数错误 / SQL 执行错误 |
| 401 | 未认证 / 令牌无效 / 受限令牌 |
| 404 | 资源不存在 |
| 408 | 查询超时 |
| 409 | 状态冲突(如删除非 pending 任务) |
| 422 | 请求体验证失败 |
| 500 | 服务器内部错误 |