feat: chat integration, tenant admin spec, backend chat service, miniprogram updates, DEMO moved to tmp, XCX-TEST removed, migrations & docs

This commit is contained in:
Neo
2026-03-20 09:02:10 +08:00
parent 3d2e5f8165
commit beb88d5bea
388 changed files with 6436 additions and 25458 deletions

View File

@@ -0,0 +1,201 @@
# 需求文档 — RNS1.4CHAT 对齐与联调收尾
## 简介
RNS1.4 是 NS1 小程序后端 API 补全项目的最后一个子 spec负责 CHAT 模块路径迁移和功能补全、FDW 端到端验证、以及全量前后端联调。本 spec 依赖 RNS1.0-1.3 全部完成,是整个 RNS1 系列的收尾阶段,确保 13 个页面全部连接真实后端运行,无 mock 数据残留。
### 依赖
- RNS1.0(基础设施与契约重写)— 全局响应包装中间件、camelCase 转换、重写后的 API 契约
- RNS1.1(任务与绩效接口)— TASK-1/2、PERF-1/2、PIN 接口已实现
- RNS1.2(客户与助教接口)— CUST-1/2、COACH-1 接口已实现
- RNS1.3(三看板接口)— BOARD-1/2/3、CONFIG-1 接口已实现
- 后端已有 `xcx_ai.py`(现有 `/api/ai/*` 路由,需迁移)
- 前端已有 chat.ts 和 chat-history.ts 页面P5.2 交付),当前使用 mock 数据和模拟流式输出
### 来源文档
- `docs/prd/Neo_Specs/RNS1-split-plan.md` — 拆分计划主文档
- `docs/miniprogram-dev/API-contract.md` — API 契约RNS1.0 T0-5 重写后版本)
- `docs/prd/Neo_Specs/storyboard-walkthrough-assistant-view.md` — 助教视角走查报告GAP-45~51
## 术语表
- **Backend**FastAPI 后端应用,位于 `apps/backend/`
- **Miniprogram**:微信小程序前端应用,位于 `apps/miniprogram/`
- **CHAT_1_API**:对话历史列表接口 `GET /api/xcx/chat/history`,返回分页的对话列表
- **CHAT_2_API**:对话消息接口 `GET /api/xcx/chat/{chatId}/messages``GET /api/xcx/chat/messages?customerId={customerId}`,返回指定对话的消息列表
- **CHAT_3_API**:发送消息接口 `POST /api/xcx/chat/{chatId}/messages`,发送用户消息并获取 AI 同步回复
- **CHAT_4_SSE**SSE 流式端点 `POST /api/xcx/chat/stream`,通过 Server-Sent Events 逐 token 返回 AI 回复
- **SSE**Server-Sent Events服务端推送事件协议用于 AI 流式回复的逐 token 输出
- **referenceCard**:引用卡片,消息中附带的结构化上下文数据(类型/标题/摘要/键值对),用于展示客户概览等信息
- **FDW**PostgreSQL Foreign Data Wrapper用于从业务库 `zqyy_app` 访问 ETL 库 `etl_feiqiu` 的数据
- **chat_sessions**:业务库 `zqyy_app` 中的对话会话表,存储对话元数据
- **chat_messages**:业务库 `zqyy_app` 中的消息表,存储对话消息内容
- **Response_Wrapper**RNS1.0 实现的全局响应包装中间件,`text/event-stream` 响应自动跳过包装
- **items_sum**DWD-DOC 强制使用的消费金额口径
- **联调**:前后端联合调试,验证所有页面使用真实后端数据正常运行
## 需求
### 需求 1CHAT 路径迁移T4-1
**用户故事:** 作为前后端开发者,我希望 CHAT 模块的 API 路径从 `/api/ai/*` 统一迁移到 `/api/xcx/chat/*`,以便与其他小程序接口保持一致的路径命名规范。
#### 验收标准
1. THE Backend SHALL 将现有 `/api/ai/*` 路由全部迁移到 `/api/xcx/chat/*` 路径下,包括同步端点和 SSE 流式端点
2. THE Backend SHALL 在迁移后移除原 `/api/ai/*` 路径,不保留旧路径的兼容映射
3. THE Backend SHALL 将迁移后的路由注册到 `xcx_chat` router或等效命名与其他 `xcx_*` 路由模块保持一致的组织结构
4. THE Miniprogram SHALL 更新 `services/api.ts` 中所有 CHAT 相关的 API 调用路径,从 `/api/ai/*` 改为 `/api/xcx/chat/*`
5. WHEN CHAT_4_SSE 端点迁移到 `/api/xcx/chat/stream`THE Response_Wrapper SHALL 继续对 `text/event-stream` 响应跳过包装RNS1.0 已实现的行为不受路径变更影响)
### 需求 2实现 CHAT-1 对话历史列表T4-2 历史列表部分)
**用户故事:** 作为助教,我希望在对话历史页面看到所有对话记录(含对话标题和最后消息摘要),以便快速找到并继续之前的对话。
#### 验收标准
1. THE CHAT_1_API SHALL 实现 `GET /api/xcx/chat/history` 端点,接受 `page`(默认 1`pageSize`(默认 20查询参数返回分页的对话历史列表
2. THE CHAT_1_API SHALL 为每条对话记录返回以下字段:`id`(对话 ID`title`(对话标题)、`customerName`(关联客户姓名,可选)、`lastMessage`(最后一条消息摘要)、`timestamp`最后消息时间ISO 8601 格式)、`unreadCount`(未读消息数)
3. THE CHAT_1_API SHALL 从 `zqyy_app.chat_sessions` 查询对话列表,按最后消息时间倒序排列
4. THE CHAT_1_API SHALL 为 `title` 字段生成对话标题:优先使用对话会话中存储的自定义标题,若无自定义标题则使用关联客户姓名,若均无则使用首条消息内容的前 20 个字符作为标题
5. THE CHAT_1_API SHALL 返回标准分页字段:`total`(总记录数)、`page`(当前页码)、`pageSize`(每页条数)
6. THE CHAT_1_API SHALL 通过当前登录用户的身份过滤对话列表,确保每位助教只能看到自己的对话记录
### 需求 3实现 CHAT-2 对话消息查看T4-2 消息查看部分)
**用户故事:** 作为助教,我希望查看指定对话的消息列表,以便回顾与 AI 助手的对话内容。
#### 验收标准
1. THE CHAT_2_API SHALL 实现两个等效的消息查询端点:`GET /api/xcx/chat/{chatId}/messages`(通过对话 ID 查询)和 `GET /api/xcx/chat/messages?contextType={type}&contextId={id}`(通过上下文类型和 ID 查询)
2. THE CHAT_2_API SHALL 接受 `page`(默认 1`pageSize`(默认 50查询参数返回分页的消息列表
3. THE CHAT_2_API SHALL 为每条消息返回以下字段:`id`(消息 ID`role``user``assistant`)、`content`(消息内容)、`createdAt`创建时间ISO 8601 格式)、`referenceCard`(引用卡片,可选)
4. THE CHAT_2_API SHALL 统一使用 `createdAt` 作为消息时间字段名(替代前端使用的 `timestamp` 和旧契约的 `created_at`,遵循 camelCase 规范)
5. THE CHAT_2_API SHALL 从 `zqyy_app.chat_messages` 查询消息列表,按 `createdAt` 正序排列(最早的消息在前)
6. THE CHAT_2_API SHALL 在响应中返回 `chatId` 字段,供前端后续发送消息时使用(尤其是通过上下文入口时,前端需要获取对应的 `chatId`
7. THE CHAT_2_API SHALL 返回标准分页字段:`total`(总记录数)、`page`(当前页码)、`pageSize`(每页条数)
#### 3.2 上下文对话复用规则
8. WHEN 通过 `contextType``contextId` 查询参数调用消息端点时THE CHAT_2_API SHALL 按以下规则查找或创建对话:
- `contextType='task'`:查找同一用户、同一 `contextId`taskId的已有对话找到则复用无时限找不到则新建
- `contextType='customer'``contextType='coach'`:查找同一用户、同一 `contextId` 的已有对话,若最后消息时间 ≤ 3 天则复用,> 3 天或不存在则新建
- `contextType='general'`:始终新建对话
9. THE CHAT_2_API SHALL 在 `ai_conversations` 中记录 `context_type``context_id` 字段,用于后续对话查找
10. THE CHAT_2_API SHALL 确保对话复用查找基于 `(user_id, site_id, context_type, context_id)` 组合,不同用户的对话互不影响
### 需求 4CHAT referenceCard 支持T4-3
**用户故事:** 作为助教,我希望在与 AI 助手对话时,消息中能附带客户概览卡片(含余额、消费、到店频次等键值对数据),以便在对话上下文中快速查看客户关键信息。
#### 验收标准
1. THE CHAT_2_API SHALL 为消息返回可选的 `referenceCard` 字段,结构包含:`type`(引用类型,`customer``record` 枚举)、`title`(卡片标题,如 `"张伟 — 消费概览"`)、`summary`(摘要文字)、`data``Record<string, string>` 键值对详情,如 `{ "近30天消费": "¥2,380", "到店次数": "8次" }`
2. WHEN AI 助手回复消息涉及特定客户时THE Backend SHALL 从 FDW 查询该客户的关键指标(余额、近期消费、到店频次等),组装为 `referenceCard` 附加到 AI 回复消息中
3. THE Backend SHALL 将 `referenceCard` 数据持久化存储到 `chat_messages` 表中(作为 JSON 字段),以便历史消息查看时仍能展示引用卡片
4. THE Miniprogram SHALL 在 chat 页面的消息列表中,检测消息的 `referenceCard` 字段,若存在则渲染为结构化卡片组件(标题 + 摘要 + 键值对列表)
#### 4.2 多入口参数路由GAP-50
5. THE Miniprogram SHALL 在 chat 页面的 `onLoad(options)` 中实现多入口参数路由逻辑,按以下优先级处理入口参数:
-`options.historyId` 存在(从 chat-history 跳转),使用 `historyId` 作为 `chatId` 直接加载历史消息
-`options.taskId` 存在(从 task-detail 跳转),使用 `contextType=task` + `contextId=taskId` 调用 CHAT_2_API由后端查找同一任务的已有对话始终复用无时限
-`options.customerId` 存在(从 customer-detail 跳转),使用 `contextType=customer` + `contextId=customerId` 调用 CHAT_2_API由后端按 3 天时限判断复用或新建
-`options.coachId` 存在(从 coach-detail 跳转),使用 `contextType=coach` + `contextId=coachId` 调用 CHAT_2_API由后端按 3 天时限判断复用或新建
6. WHEN 通过上下文入口进入对话后THE Miniprogram SHALL 将后端返回的 `chatId` 缓存到页面 data 中,后续发送消息和 SSE 流式请求均使用该 `chatId`
7. IF chat 页面未收到任何入口参数(`historyId`/`taskId`/`customerId`/`coachId` 均为空THEN THE Miniprogram SHALL 使用 `contextType=general` 调用 CHAT_2_API 创建一个通用对话
### 需求 5CHAT-3 发送消息T4-2 发送部分)
**用户故事:** 作为助教,我希望在对话页面发送消息后能立即收到 AI 的同步回复,以便在不支持 SSE 的场景下也能正常使用 AI 助手。
#### 验收标准
1. THE CHAT_3_API SHALL 实现 `POST /api/xcx/chat/{chatId}/messages` 端点,接受请求体 `{ content: string }`
2. THE CHAT_3_API SHALL 将用户消息存入 `chat_messages` 表,调用 AI 服务获取回复,将 AI 回复也存入 `chat_messages`
3. THE CHAT_3_API SHALL 返回包含用户消息和 AI 回复的响应:`userMessage`(含 `id`/`content`/`createdAt`)和 `aiReply`(含 `id`/`content`/`createdAt`
4. THE CHAT_3_API SHALL 在发送消息后更新 `chat_sessions` 表的 `lastMessage` 和最后消息时间字段
5. IF AI 服务调用失败或超时THEN THE CHAT_3_API SHALL 仍保存用户消息,并返回 AI 回复为错误提示消息(如 `{ content: "抱歉AI 助手暂时无法回复,请稍后重试" }`HTTP 状态码保持 200
6. THE CHAT_3_API SHALL 验证请求的 `chatId` 属于当前登录助教,不属于时返回 HTTP 403
### 需求 6CHAT-4 SSE 流式端点T4-1 SSE 部分)
**用户故事:** 作为助教,我希望 AI 助手的回复能以流式方式逐字显示,以便获得更自然的对话体验,减少等待感。
#### 验收标准
1. THE CHAT_4_SSE SHALL 实现 `POST /api/xcx/chat/stream` 端点,接受请求体 `{ chatId: string, content: string }`,响应内容类型为 `text/event-stream`
2. THE CHAT_4_SSE SHALL 发送以下三种 SSE 事件类型:
- `event: message` — 逐 token 输出,`data``{"token": "<文本片段>"}`
- `event: done` — 流结束,`data``{"messageId": "<完整消息ID>", "createdAt": "<ISO 8601>"}`
- `event: error` — 错误,`data``{"message": "<错误描述>"}`
3. THE CHAT_4_SSE SHALL 在流开始前将用户消息存入 `chat_messages` 表,在流结束后将完整的 AI 回复存入 `chat_messages`
4. THE CHAT_4_SSE SHALL 在流结束后更新 `chat_sessions` 表的 `lastMessage` 和最后消息时间字段
5. THE Response_Wrapper SHALL 对 `text/event-stream` 响应跳过全局包装,直接透传 SSE 事件流RNS1.0 已实现)
6. THE CHAT_4_SSE SHALL 验证请求的 `chatId` 属于当前登录助教,不属于时返回 HTTP 403此时响应为普通 JSON 错误,非 SSE
7. THE Miniprogram SHALL 将 chat 页面现有的 `simulateStreamOutput()`(模拟逐字输出)替换为真实的 SSE 连接,通过 `wx.request` 或兼容方案接收 `text/event-stream` 响应
### 需求 7FDW 端到端验证T4-4
**用户故事:** 作为后端开发者,我希望验证所有 FDW 查询在测试环境链路(`test_zqyy_app``test_etl_feiqiu`)上正常工作,以便确保 RNS1.1-1.3 实现的所有接口在真实数据链路上不会因 FDW 连接、权限或性能问题而失败。
#### 验收标准
1. THE Backend SHALL 验证所有 `fdw_etl.*` 视图在 `test_zqyy_app` 数据库中可正常访问,包括但不限于:`v_dws_assistant_salary_calc``v_dwd_assistant_service_log``v_dim_member``v_dim_assistant``v_dws_member_consumption_summary``v_dws_member_assistant_relation_index``v_dws_finance_*` 系列视图
2. THE Backend SHALL 验证每个 FDW 视图的查询响应时间在可接受范围内(单次查询不超过 3 秒),对超时的查询记录慢查询日志并评估是否需要添加索引
3. THE Backend SHALL 验证 FDW 查询在带有典型过滤条件(如 `assistant_id``member_id`、日期范围)时能正确返回数据,且结果集与直接查询 `test_etl_feiqiu` 的结果一致
4. IF 某个 FDW 视图不存在或权限不足THEN THE Backend SHALL 记录具体的错误信息(视图名、错误类型),并在验证报告中标注需要 DBA 介入修复
5. THE Backend SHALL 检查 FDW 链路上的关键索引是否存在:`chat_sessions` 表的 `(assistant_id, customer_id)` 索引、`chat_messages` 表的 `(session_id, created_at)` 索引
### 需求 8前端联调修复 — notes 页触底加载T4-5 F11
**用户故事:** 作为助教,我希望在备注列表页面滚动到底部时自动加载更多备注,以便查看全部备注记录而不需要手动翻页。
#### 验收标准
1. THE Miniprogram SHALL 在 notes 页面实现 `onReachBottom()` 生命周期函数,当用户滚动到页面底部时自动请求下一页备注数据
2. WHEN 触底加载触发时THE Miniprogram SHALL 将 `page` 参数加 1调用 `fetchNotes({ page, pageSize })` 接口,将返回的备注追加到已有列表末尾
3. WHEN 后端返回的 `hasMore``false` 或返回的备注数量小于 `pageSize`THE Miniprogram SHALL 停止触底加载,显示"没有更多了"提示
4. THE Miniprogram SHALL 在触底加载过程中显示加载状态指示器,防止重复触发请求
### 需求 9前端联调修复 — customer-service-records 按月请求T4-5 F10
**用户故事:** 作为助教,我希望客户服务记录页面在切换月份时向后端请求对应月份的数据,以便在数据量大时页面仍能快速响应,而不是全量加载后本地过滤。
#### 验收标准
1. THE Miniprogram SHALL 修改 customer-service-records 页面的月份切换逻辑,从当前的"全量加载后本地过滤"改为"按月请求 API"
2. WHEN 用户切换月份时THE Miniprogram SHALL 使用新的 `year`/`month` 参数调用 `fetchCustomerRecords({ customerId, year, month })` 接口,加载对应月份的服务记录
3. THE Miniprogram SHALL 在月份切换时清空已有记录列表,显示加载状态,待新数据返回后渲染
4. THE Miniprogram SHALL 在首次加载时默认请求当前月份的数据,而非全量数据
5. THE Backend SHALL 确保 CUST-2 接口支持 `year``month` 查询参数仅返回指定月份的服务记录RNS1.2 T2-4 已实现按月查询能力)
### 需求 10全量前后端联调T4-5 联调部分)
**用户故事:** 作为开发团队,我们希望 13 个小程序页面全部连接真实后端运行,无 mock 数据残留,以便确认整个应用在真实数据环境下功能完整、交互正常。
#### 验收标准
1. THE Miniprogram SHALL 移除所有页面中的内联 mock 数据和 mock 数据导入(`import { mockXxx } from '../../utils/mock-data'`),全部替换为真实 API 调用
2. THE Miniprogram SHALL 确保以下 13 个页面均能使用真实后端数据正常渲染:`task-list``task-detail``notes``performance``performance-records``customer-detail``customer-service-records``coach-detail``board-coach``board-customer``board-finance``chat-history``chat`
3. WHEN 某个页面的 API 调用失败时THE Miniprogram SHALL 显示友好的错误提示(如 Toast 或空状态占位),不出现白屏或未捕获异常
4. THE Miniprogram SHALL 验证所有页面间的跳转参数传递正确RNS1.0 T0-6 已修复的跨页面参数),确保目标页面能正确加载对应数据
5. THE Miniprogram SHALL 验证 chat 页面从 4 个入口task-detail、customer-detail、coach-detail、chat-history进入时均能正确关联上下文并加载对应对话
6. IF 联调过程中发现新的 Bug 或数据不一致问题THEN THE Miniprogram 和 Backend SHALL 在本 spec 范围内修复,修复内容记录到联调问题清单中
### 需求 11全局约束与权限控制
**用户故事:** 作为系统管理员,我希望所有 CHAT 接口遵循统一的权限控制和数据隔离规则,以确保每位助教只能访问自己的对话数据。
#### 验收标准
1. THE Backend SHALL 对所有 RNS1.4 CHAT 接口CHAT-1、CHAT-2、CHAT-3、CHAT-4执行 `require_approved()` 权限检查,确保用户状态为 `approved`
2. THE Backend SHALL 通过当前登录用户的身份信息过滤对话数据,确保每位助教只能访问自己创建的或与自己关联的对话
3. IF 当前用户未通过审核(状态非 `approved`THEN THE Backend SHALL 返回 HTTP 403 `{ code: 403, message: "用户未通过审核,无法访问此资源" }`
4. THE Backend SHALL 对所有 CHAT 接口的响应字段名统一使用 camelCase 格式(与 RNS1.0 的 CamelCase_Converter 一致)
5. THE Backend SHALL 确保 CHAT 模块的错误响应格式与全局异常处理器一致:`{ code: <HTTP状态码>, message: <错误详情> }`
6. WHEN CHAT 模块查询 FDW 数据(如为 referenceCard 获取客户指标THE Backend SHALL 遵循 DWD-DOC 强制规则:金额使用 `items_sum` 口径,会员信息通过 `member_id` JOIN `dim_member` 获取