docs(audit): W1-T6 chat 多入口后端契约已就位 (P1-11 关闭)

Day 3 实地走读确认 CHAT-2b 端点已实现:
- 后端 xcx_chat.py:GET /api/xcx/chat/messages (contextType + contextId)
- 前端 chat.ts:6 分支 (historyId / taskId / customerId / coachId / sourcePage / general)
- 前后端字段契约 + chatId 归属验证均一致

E-2 调研当时只读到前端标 "待核",本次走读确认后端早已实现。
无代码改动,本审计文件作为"已就位"标记,避免后续误判。

参考: docs/audit/changes/2026-05-04__wave1_t6_chat_context_already_in_place.md
This commit is contained in:
Neo
2026-05-04 08:12:12 +08:00
parent b0340349bd
commit 3673090582

View File

@@ -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