diff --git a/docs/audit/changes/2026-05-04__wave1_t6_chat_context_already_in_place.md b/docs/audit/changes/2026-05-04__wave1_t6_chat_context_already_in_place.md new file mode 100644 index 0000000..adf7fd7 --- /dev/null +++ b/docs/audit/changes/2026-05-04__wave1_t6_chat_context_already_in_place.md @@ -0,0 +1,92 @@ +# Wave 1 W1-T6 — chat 多入口后端契约(状态:已就位) + +| 字段 | 值 | +|---|---| +| 日期 | 2026-05-04 | +| Wave | 1 / Day 3 | +| 范围 | P1-11 chat 多入口后端契约 | +| 文件改动 | **零代码改动**(后端契约 + 前端 6 分支均已就位) | + +## 一、走读结论 + +E-2 调研(`docs/_overview/04b-feedback/P1-3-4-cross-page-params.md`)报告: +> 前端已 6 分支修了,只剩后端契约待核 + +**实地走读 2026-05-04 确认**:**后端契约也已实现**,前后端形成完整链路。 + +### 后端 `apps/backend/app/routers/xcx_chat.py` + +```python +@router.get("/messages", response_model=ChatMessagesResponse) +@trace_service("通过上下文查询消息", "Get messages by context") +async def get_chat_messages_by_context( + context_type: str = Query(..., alias="contextType"), + context_id: str = Query(..., alias="contextId"), + page: int = Query(1, ge=1), + page_size: int = Query(50, ge=1, le=100), + user: CurrentUser = Depends(require_approved()), +) -> ChatMessagesResponse: + """CHAT-2b: 通过上下文类型和 ID 查询消息(自动查找/创建对话)。""" + svc = ChatService() + chat_id = svc.get_or_create_session( + user_id=user.user_id, + site_id=user.site_id, + context_type=context_type, + context_id=context_id if context_id else None, + ) + messages, total, resolved_chat_id = svc.get_messages(...) + return ChatMessagesResponse(chat_id=resolved_chat_id, ...) +``` + +### 前端 `apps/miniprogram/miniprogram/pages/chat/chat.ts` + +L213 注释:`// 优先级:historyId → taskId → customerId → coachId → general` + +实际 6 分支(`onLoad(options)`): + +| 分支 | 入参 | 调用 | 后端契约 | +|---|---|---|---| +| 1 | `options.historyId` | `loadMessages(historyId)` | `GET /api/xcx/chat/{chat_id}/messages` (CHAT-2a) | +| 2 | `options.taskId` | `loadMessagesByContext('task', options.taskId)` | `GET /api/xcx/chat/messages?contextType=task&contextId=...` (CHAT-2b) | +| 3 | `options.customerId` | `loadMessagesByContext('customer', options.customerId)` | 同上 (customer) | +| 4 | `options.coachId` | `loadMessagesByContext('coach', options.coachId)` | 同上 (coach) | +| 5 | `options.sourcePage` | `loadMessagesByContext(sourcePage, '')` | 同上 (任意 sourcePage,空 contextId) | +| 6 | 默认 | `loadMessagesByContext('general', '')` | 同上 (general) | + +`fetchChatMessagesByContext` 调的就是 CHAT-2b 端点。 + +## 二、前后端契约一致性核对 + +| 字段 | 前端传 | 后端期望 | 一致? | +|---|---|---|---| +| context type | `'task' / 'customer' / 'coach' / 'general' / sourcePage` | Query alias=`contextType` | ✅ | +| context id | 字符串(可空) | Query alias=`contextId` | ✅ | +| 返回值 chatId | 前端缓存 `setData({ chatId })` 用于后续发送 | response.chat_id | ✅ | +| chatId 归属验证 | 后续发送时由后端校验(L164 stream / L367 send_message) | 已实现 | ✅ | + +## 三、E-2 调研为何标"待核" + +E-2 调研产物时点(2026-05-04 早于本走读)只读到了前端,标"后端契约待核"。本次走读确认后端契约**早已实现**(CHAT-2b 端点存在 + 业务逻辑完整 + 类型定义完备)。 + +可能的早期 timeline(推测): +- 前端先实现 6 分支,占位调 `/api/xcx/chat/messages?contextType=...` +- 后端某次会话补全 CHAT-2b +- E-2 调研只读前端文件,未读后端 → 标"待核" + +## 四、本次行动 + +**无代码改动**,只产出本审计文件作为"已就位"标记,避免后续误以为 W1-T6 仍是待办。 + +W1-T6 状态:**关闭**。 + +## 五、未覆盖项(Wave 5 / 单测补漏) + +- 单测覆盖:`tests/test_chat_get_or_create_session.py` 缺失,留 Wave 2/3 补 +- 4 种 contextType 端到端 e2e 测试:留 Wave 1 §14 走查时验证(W1-T8) +- contextType / contextId 校验:可加 Pydantic Enum 限制(目前是 free string,留 Wave 5 文档收尾时可强化) + +## 六、关联 + +- 调研:[`docs/_overview/04b-feedback/P1-3-4-cross-page-params.md`](../../_overview/04b-feedback/P1-3-4-cross-page-params.md) E-2 +- 总报告:[`docs/_overview/04b-feedback/00-P1-feedback-response-summary.md`](../../_overview/04b-feedback/00-P1-feedback-response-summary.md) §三 `**前端已 6 分支修了,只剩后端契约待核**`(描述更新为已就位) +- Wave 1 kickoff:[`docs/_overview/WAVE-1-KICKOFF.md`](../../_overview/WAVE-1-KICKOFF.md) §二 W1-T6