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