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,311 @@
# 实施计划RNS1.4 CHAT 对齐与联调收尾
## 概述
按照设计文档的 8 个组件将实施拆分为DDL 迁移 → 后端 Schema/Service/Router → FDW 验证 → 前端改造 → 联调收尾。每个任务增量构建确保无孤立代码。属性测试Hypothesis和单元测试作为可选子任务紧跟实现步骤。
## 任务
- [x] 1. DDL 迁移:扩展 ai_conversations 和 ai_messages 表
- [x] 1.1 创建 DDL 迁移脚本 `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql`
- ALTER TABLE `biz.ai_conversations` 新增 `context_type varchar(20)``context_id varchar(50)``title varchar(200)``last_message text``last_message_at timestamptz` 五个字段
- ALTER TABLE `biz.ai_messages` 新增 `reference_card` jsonb 字段
- 创建索引 `idx_ai_conv_context` ON `(user_id, site_id, context_type, context_id, last_message_at DESC NULLS LAST) WHERE context_type IS NOT NULL`
- 创建排序索引 `idx_ai_conv_last_msg` ON `(user_id, site_id, last_message_at DESC NULLS LAST)`
- 添加 COMMENT ON COLUMN 注释
- _需求: R2.3, R3.8, R3.10, R4.3, R7.5_
- [x] 2. 后端 Pydantic Schema 定义(组件 3
- [x] 2.1 创建 `apps/backend/app/schemas/xcx_chat.py`
- 继承 `CamelModel` 基类,定义 `ChatHistoryItem``ChatHistoryResponse``ReferenceCard``ChatMessageItem``ChatMessagesResponse``SendMessageRequest``SendMessageResponse``MessageBrief``ChatStreamRequest`
- 字段类型和可选性严格遵循设计文档组件 3 定义
- _需求: R2.2, R3.3, R4.1, R5.1, R5.3, R6.1, R11.4_
- [x] 2.2 编写 Schema 序列化单元测试 `apps/backend/tests/unit/test_xcx_chat_schema.py`
- 验证 ChatHistoryItem / ChatMessageItem / SendMessageResponse 的 camelCase 序列化输出
- 验证 ReferenceCard 可选字段为 None 时不报错
- _需求: R2.2, R3.3, R5.3, R11.4_
- [x] 3. 后端 chat_service 业务逻辑层(组件 2
- [x] 3.1 创建 `apps/backend/app/services/chat_service.py`
- 实现 `ChatService` 类,包含 `get_chat_history``get_or_create_session``get_messages``send_message_sync``build_reference_card``generate_title` 方法
- `get_chat_history`:查询 `biz.ai_conversations`,按 `last_message_at` 倒序JOIN `v_dim_member` 获取 `customerName`,分页返回
- `get_or_create_session`:按 `(user_id, site_id, context_type, context_id)` 查找或创建对话。复用规则task 入口始终复用无时限customer/coach 入口 ≤ 3 天复用、> 3 天新建general 入口始终新建
- `get_messages`:查询 `biz.ai_messages`,按 `created_at` 正序,验证 chatId 归属当前用户
- `send_message_sync`:存入用户消息 → 调用 AI → 存入 AI 回复 → 更新 session 元数据AI 失败时返回错误提示消息HTTP 200
- ⚠️ **P5 PRD 合规**:对话落库必须遵循 `docs/prd/specs/P5-miniapp-ai-integration.md` 数据写入规则:
- `app_id` 固定为 `app1_chat`
- 用户消息发送时即写入 `ai_messages`role=user
- 流式完成后完整 assistant 回复写入 `ai_messages`role=assistant`tokens_used`
- 首条消息为页面上下文 JSON`current_time`/`source_page`/`page_context`/`screen_content`
- `get_or_create_session` 仅用于 task/customer/coach 入口的对话复用task 无时限customer/coach 3 天时限general 入口始终新建(保持 P5 PRD 兼容)
- `build_reference_card`:从 FDW 查询客户指标(`items_sum` 口径),组装 referenceCard JSON
- `generate_title`:自定义标题 > 客户姓名 > 首条消息前 20 字
- _需求: R2.1-R2.6, R3.1-R3.10, R4.1-R4.3, R5.1-R5.6, R11.2, R11.6_
- [x] 3.2 编写属性测试:标题生成优先级
- **Property 4: 对话标题生成优先级**
- 使用 Hypothesis `st.fixed_dictionaries` 生成随机 title/customer_name/first_message 组合
- 验证:有 title 用 title否则用 customer_name否则用首条消息前 20 字,结果始终非空
- **验证: 需求 R2.4**
- [x] 3.3 编写属性测试:对话复用规则正确性
- **Property 6: 对话复用规则正确性**
- 使用 Hypothesis 生成随机 context_type/context_id/last_message_at 组合
- 验证task 入口同一 context_id 始终返回同一 chatIdcustomer/coach 入口 ≤ 3 天复用、> 3 天新建general 入口每次返回不同 chatId
- **验证: 需求 R3.8, R3.9, R3.10**
- [x] 3.4 编写属性测试referenceCard Round Trip
- **Property 7: referenceCard 持久化 Round Trip**
- 使用 Hypothesis 生成随机 referenceCard JSONtype/title/summary/data
- 验证JSON 序列化→存储→读取→反序列化等于原始对象
- **验证: 需求 R4.1, R4.3**
- [x] 3.5 编写属性测试:消息持久化与会话元数据更新
- **Property 8: 消息持久化与会话元数据更新**
- 使用 Hypothesis `st.text(min_size=1, max_size=500)` 生成随机消息内容
- 验证:发送后 ai_messages 包含用户消息和 AI 回复session 的 last_message 和 last_message_at 已更新
- **验证: 需求 R5.2, R5.4, R6.3, R6.4**
- [x] 3.6 编写单元测试AI 失败降级
- 测试文件 `apps/backend/tests/unit/test_xcx_chat_ai_fallback.py`
- 验证 AI 服务超时/异常时用户消息仍保存AI 回复为错误提示消息HTTP 200
- _需求: R5.5_
- [x] 4. 后端路由迁移与 CHAT-1/2/3/4 端点实现(组件 1
- [x] 4.1 创建 `apps/backend/app/routers/xcx_chat.py`,实现 CHAT-1/2/3/4 五个端点
- `GET /history` — CHAT-1 对话历史列表,调用 `chat_service.get_chat_history`
- `GET /{chat_id}/messages` — CHAT-2a 通过 chatId 查询消息
- `GET /messages?contextType=&contextId=` — CHAT-2b 通过上下文查询消息(按复用规则自动查找/创建对话)
- `POST /{chat_id}/messages` — CHAT-3 发送消息(同步回复)
- `POST /stream` — CHAT-4 SSE 流式端点,返回 `StreamingResponse(media_type="text/event-stream")`
- 所有端点使用 `Depends(require_approved())` 权限检查
- chatId 归属验证CHAT-3/4 不属于当前用户返回 HTTP 403
- _需求: R1.1, R1.3, R2.1, R3.1, R5.1, R6.1, R11.1, R11.2_
- [x] 4.2 在 `apps/backend/app/main.py` 中注册 `xcx_chat.router`,移除 `xcx_ai_chat.router`
- 删除 `xcx_ai_chat.py` 文件(不保留旧路径兼容)
- _需求: R1.2, R1.3_
- [x] 4.3 编写属性测试SSE 事件类型有效性
- **Property 9: SSE 事件类型有效性**
- 使用 Hypothesis `st.sampled_from(["message", "done", "error"])` + 对应 data 结构
- 验证事件类型为三者之一data 结构符合定义message→token, done→messageId+createdAt, error→message
- **验证: 需求 R6.2**
- [x] 4.4 编写属性测试:列表排序不变量
- **Property 3: 列表排序不变量**
- 使用 Hypothesis `st.lists(st.datetimes())` 生成随机时间戳列表
- 验证CHAT-1 对话列表按时间倒序CHAT-2 消息列表按时间正序
- **验证: 需求 R2.3, R3.5**
- [x] 4.5 编写单元测试:路由迁移与权限控制
- 测试文件 `apps/backend/tests/unit/test_xcx_chat_routes.py`
- 验证 `/api/xcx/chat/history` 返回 200需认证`/api/ai/conversations` 返回 404
- 验证未审核用户收到 403chatId 不属于当前用户收到 403
- **Property 1: 路由迁移完整性** / **Property 5: 权限控制与数据隔离**
- **验证: 需求 R1.1, R1.2, R5.6, R6.6, R11.1, R11.3**
- [x] 5. 检查点 — 后端实现验证
- 确保所有后端测试通过ask the user if questions arise.
- [x] 6. FDW 端到端验证脚本(组件 7
- [x] 6.1 创建 `scripts/ops/verify_fdw_e2e.py`
- 验证所有 `fdw_etl.*` 视图在 `test_zqyy_app` 中可访问SELECT 1 FROM ... LIMIT 1
- 验证带典型过滤条件assistant_id、member_id、日期范围的查询响应时间 < 3s
- 检查关键索引存在:`chat_sessions(assistant_id, customer_id)``chat_messages(session_id, created_at)`
- 输出结构化 JSON 报告,失败项标注需 DBA 介入
- 使用 `load_dotenv` 加载根 `.env`,连接 `test_zqyy_app`(遵循 testing-env.md 规范)
- _需求: R7.1, R7.2, R7.3, R7.4, R7.5_
- [x] 7. 前端 services/api.ts CHAT 模块对接(组件 6
- [x] 7.1 修改 `apps/miniprogram/miniprogram/services/api.ts`
- `fetchChatHistory()`:调用 `GET /api/xcx/chat/history`
- `fetchChatMessages(chatId)`:调用 `GET /api/xcx/chat/{chatId}/messages`
- 新增 `fetchChatMessagesByContext(contextType, contextId)`:调用 `GET /api/xcx/chat/messages?contextType={type}&contextId={id}`
- `sendChatMessage(chatId, content)`:调用 `POST /api/xcx/chat/{chatId}/messages`
- 移除所有 CHAT 相关 mock 数据导入,`USE_REAL_API` 对 CHAT 模块设为 `true`
- _需求: R1.4, R10.1_
- [x] 8. 前端 chat 页面改造(组件 4
- [x] 8.1 修改 `apps/miniprogram/miniprogram/pages/chat/chat.ts` 实现多入口参数路由
- `onLoad(options)` 中按优先级处理:`historyId``taskId``customerId``coachId` → 无参数(通用对话)
- `historyId` 入口:直接用作 chatId 加载历史消息
- `taskId` 入口:调用 `fetchChatMessagesByContext('task', taskId)`,同一 taskId 始终复用同一对话(无时限)
- `customerId` 入口:调用 `fetchChatMessagesByContext('customer', customerId)`,≤ 3 天复用、> 3 天新建
- `coachId` 入口:调用 `fetchChatMessagesByContext('coach', coachId)`,≤ 3 天复用、> 3 天新建
- 无参数入口:调用 `fetchChatMessagesByContext('general', '')`,始终新建
- _需求: R4.5, R4.6, R4.7_
- [x] 8.2 修改 chat.ts 将 `simulateStreamOutput()` 替换为真实 SSE 连接
- 使用 `wx.request` + `enableChunked: true` 接收 `POST /api/xcx/chat/stream` 的 SSE 响应
- 解析 `event: message`(逐 token 追加)、`event: done`(流结束)、`event: error`(错误处理)
- 移除 `simulateStreamOutput()``mockAIReplies` 相关代码
- SSE 连接中断时显示"连接中断"提示,允许重试
- _需求: R6.7, R10.1_
- [x] 8.3 确保 chat 页面 referenceCard 渲染与真实 API 数据兼容
- 验证 `toDataList()` 和 WXML 模板能正确渲染后端返回的 referenceCard 结构
- _需求: R4.4_
- [x] 9. 前端 chat-history 页面改造(组件 5
- [x] 9.1 修改 `apps/miniprogram/miniprogram/pages/chat-history/chat-history.ts`
- 移除 `mockChatHistory` 导入,调用 `fetchChatHistory()` 获取真实数据
- 响应字段映射:后端 `timestamp`ISO 8601`formatRelativeTime()` 处理
- 点击对话项跳转 chat 页面时传递 `historyId` 参数
- _需求: R2.1, R10.1, R10.2_
- [x] 10. 前端联调修复(组件 8
- [x] 10.1 修改 notes 页面实现触底加载
-`apps/miniprogram/miniprogram/pages/notes/notes.ts` 实现 `onReachBottom()` 生命周期函数
- 维护 `page` 状态,触底时 `page++` 调用 `fetchNotes({ page, pageSize })`
- 追加数据到已有列表,`hasMore === false` 时停止加载并显示"没有更多了"
- 加载过程中显示加载状态指示器,防止重复触发
- _需求: R8.1, R8.2, R8.3, R8.4_
- [x] 10.2 修改 customer-service-records 页面实现按月请求
-`apps/miniprogram/miniprogram/pages/customer-service-records/customer-service-records.ts` 修改月份切换逻辑
- 月份切换时调用 `fetchCustomerRecords({ customerId, year, month })`,清空列表 → loading → 渲染
- 首次加载默认当前月份数据
- _需求: R9.1, R9.2, R9.3, R9.4_
- [x] 11. 检查点 — 前端改造验证
- 确保所有前端改造完成chat 页面 4 个入口task-detail、customer-detail、coach-detail、chat-history均能正确进入并加载对话。ask the user if questions arise.
- [x] 12. Mock 数据移除与全量联调
- [x] 12.1 移除所有页面中的 mock 数据残留
- 搜索并移除所有 `import { mockXxx } from '../../utils/mock-data'` 引用
- 确保 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均使用真实 API
- _需求: R10.1, R10.2_
- [x] 12.2 验证全量联调
- 确保所有页面 API 调用失败时显示友好错误提示Toast 或空状态占位),不出现白屏
- 验证页面间跳转参数传递正确RNS1.0 T0-6 已修复的跨页面参数)
- 验证 chat 页面从 4 个入口进入时均能正确关联上下文
- _需求: R10.2, R10.3, R10.4, R10.5_
- [x] 13. 全链路端到端测试(真实 AI 接口)
- [x] 13.1 后端全链路测试:使用真实百炼 API 验证 CHAT-3同步和 CHAT-4SSE 流式)
- 启动后端服务连接 `test_zqyy_app`,使用真实测试用户 token
- 调用 `POST /api/xcx/chat/{chatId}/messages` 发送真实消息,验证:
- AI 回复内容质量(非乱码、语义相关、中文正常)
- 用户消息和 AI 回复均已持久化到 `biz.ai_messages`
- `biz.ai_conversations``last_message``last_message_at` 已更新
- `tokens_used` 字段已记录
- 调用 `POST /api/xcx/chat/stream` 验证 SSE 流式返回:
- 逐 token 事件格式正确(`event: message``event: done`
- 完整回复拼接后语义通顺
- 流结束后消息已落库
- 手动评估 AI 返回内容质量(至少 3 轮对话),记录评估结果
- _需求: R5.2, R5.4, R6.2, R6.3, R6.4, AC1, AC9_
- [x] 13.2 前端→后端→AI→数据库全链路验证
- 在微信开发者工具中启动小程序,连接本地后端
- 从 4 个入口task-detail、customer-detail、coach-detail、chat-history进入 chat 页面
- 每个入口发送至少 1 条消息,验证:
- SSE 流式逐字显示正常
- 消息发送后页面状态正确loading → 显示回复)
- 返回 chat-history 页面能看到刚才的对话记录
- referenceCard 在有客户关联时正确渲染(如有数据)
- 验证错误场景:网络断开时的提示、空消息拦截
- _需求: R6.7, R10.5, R4.4_
- [x] 13.3 AI 对话落库合规性验证(对照 P5 PRD + 用户确认的复用规则)
- 验证对话复用规则task 入口同一 taskId 始终复用customer/coach 入口 ≤ 3 天复用、> 3 天新建general 入口始终新建
- 验证首条消息格式:应用 1 的首条消息应为页面上下文 JSON`source_page`/`page_context`/`screen_content`/`current_time`),对照 P5 PRD 应用 1 Prompt 数据结构
- 验证 `app_id` 字段CHAT 模块对话的 `app_id` 应为 `app1_chat`
- 验证 `ai_messages.role` 值:仅 `user`/`assistant`/`system` 三种CHECK 约束)
- 验证 `tokens_used` 记录AI 回复消息应记录 token 消耗量
- 验证 `source_page``source_context`:从不同入口进入时应正确记录来源页面和上下文
- 验证 `context_type``context_id`:不同入口写入正确的上下文类型和 ID
- _需求: AC9, P5-PRD 数据写入规则_
- [x] 14. DDL 迁移合并到主 DDL 基线
- [x] 14.1 执行迁移脚本到 `test_zqyy_app`
- 运行 `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql`(任务 1 创建的脚本)
- 验证新字段和索引已正确创建(使用 BD 手册中的验证 SQL
- _需求: R2.3, R3.8, R3.10, R4.3, R7.5_
- [x] 14.2 合并到主 DDL 基线 `docs/database/ddl/zqyy_app__biz.sql`
-`biz.ai_conversations` 表定义中追加 5 个新字段:`context_type varchar(20)``context_id varchar(50)``title varchar(200)``last_message text``last_message_at timestamptz`
-`biz.ai_messages` 表定义中追加 1 个新字段:`reference_card jsonb`
- 在索引区追加 2 个新索引:`idx_ai_conv_context`条件索引WHERE context_type IS NOT NULL`idx_ai_conv_last_msg`
- 更新文件头部的"生成日期"注释
- _需求: DDL 基线同步_
- [x] 15. 文档更新落地
- [x] 15.1 更新 BD 手册 `docs/database/BD_Manual_ai_tables.md`
-`biz.ai_conversations` 字段明细中追加 5 个新字段context_type/context_id/title/last_message/last_message_at
-`biz.ai_messages` 字段明细中追加 reference_card 字段
- 在约束与索引表中追加 2 个新索引idx_ai_conv_context、idx_ai_conv_last_msg
- 更新兼容性影响:标注 RNS1.4 CHAT 模块依赖新字段
- 更新验证 SQL追加新字段和索引的验证查询
- 更新回滚策略:追加 DROP INDEX 和 ALTER TABLE DROP COLUMN
- _规范: db-docs.md 强制要求_
- [x] 15.2 更新 API 契约 `docs/miniprogram-dev/API-contract.md`
- CHAT 部分路径从 `/api/ai/*` 更新为 `/api/xcx/chat/*`
- 补充 CHAT-1历史列表、CHAT-2a/2b消息查询含 customerId 参数、CHAT-3发送消息端点定义
- 补充 referenceCard 结构定义
- 补充 SSE 事件类型定义message/done/error
- _需求: R1.1, R1.4_
- [x] 15.3 更新后端 API 参考 `apps/backend/docs/API-REFERENCE.md`
- 新增 `xcx_chat` 路由模块文档5 个端点)
- 移除 `xcx_ai_chat` 路由模块文档(已删除)
- _需求: R1.2, R1.3_
- [x] 15.4 更新后端 README `apps/backend/README.md`
- 路由模块摘要中:移除 `xcx_ai_chat`,新增 `xcx_chat`CHAT-1/2/3/4
- 服务层中:新增 `chat_service.py` 说明
- _需求: R1.3_
- [x] 15.5 更新文档地图 `docs/DOCUMENTATION-MAP.md`
- 在 3.1 FastAPI 后端部分新增 RNS1.4 模块xcx_chat.py、chat_service.py、xcx_chat schema
- 在 5.6 Spec 文件表中新增 `rns1-chat-integration` 条目
- 更新"最后更新"日期
- _规范: doc-map.md 归档规则_
- [x] 15.6 更新 RNS1 拆分计划 `docs/prd/Neo_Specs/RNS1-split-plan.md`
- 标注 RNS1.4 状态为"实施中"或"已完成"(视进度)
- _需求: 项目追踪_
- [x] 16. 最终检查点 — 全量验证
- 确保所有测试通过13 个页面均连接真实后端运行,无 mock 数据残留
- 确保 DDL 迁移已合并到主基线BD 手册已同步更新
- 确保 API 契约、后端 README、文档地图均已更新
- 确保 AI 对话落库符合 P5 PRD 规范
- ask the user if questions arise.
## 备注
- 标记 `*` 的子任务为可选,可跳过以加速 MVP 交付
- 每个任务引用了具体的需求编号R1-R11以确保可追溯性
- 属性测试验证通用正确性属性Property 1-9单元测试验证具体边界条件
- 检查点任务确保增量验证,避免问题累积
- 后端使用 PythonFastAPI + Pydantic前端使用 TypeScript微信小程序
### PRD 合规注意事项
- **P5 PRD 数据写入规则**`docs/prd/specs/P5-miniapp-ai-integration.md`
- 流式返回完成后,完整 assistant 回复写入 `ai_messages`role=assistant
- 用户消息在发送时即写入role=user
- 所有 AI 调用记录写入 `ai_conversations` + `ai_messages`(含 tokens_used 统计)
- 首条消息为页面上下文 JSON`source_page`/`page_context`/`screen_content`/`current_time`
- `app_id` 固定为 `app1_chat`
- **对话复用规则**(用户已确认,覆盖 P5 PRD 的"始终新建"规则):
- `task` 入口:同一 taskId 始终复用同一对话(无时限)
- `customer` / `coach` 入口:最后消息 ≤ 3 天复用,> 3 天新建
- `general` 入口(无参数):始终新建
- `chat-history` 入口:直接打开已有对话(传 historyId
### 文档更新清单
| 文档 | 更新内容 | 任务 |
|------|---------|------|
| `docs/database/ddl/zqyy_app__biz.sql` | 合并 5+1 新字段、2 新索引 | 14.2 |
| `docs/database/BD_Manual_ai_tables.md` | 新字段context_type/context_id/title/last_message/last_message_at/reference_card/索引/兼容性/回滚/验证 SQL | 15.1 |
| `docs/miniprogram-dev/API-contract.md` | CHAT 路径迁移 + 新端点定义 | 15.2 |
| `apps/backend/docs/API-REFERENCE.md` | xcx_chat 路由模块文档 | 15.3 |
| `apps/backend/README.md` | 路由模块 + 服务层更新 | 15.4 |
| `docs/DOCUMENTATION-MAP.md` | RNS1.4 模块 + spec 条目 | 15.5 |
| `docs/prd/Neo_Specs/RNS1-split-plan.md` | RNS1.4 状态更新 | 15.6 |