补齐三个成功响应缺失的 meta 信封;信封实现抽为公共(docs/05 §3.3)
"缺 meta"很容易被误判成"有":X-Trace-ID 是**响应头**(中间件加),和 body 里的
meta.trace_id 是两件事;错误响应一直有 meta(异常处理器统一加),漏的只有成功路径。
核到三个端点在把 service 的内部结构直接当响应体返回:
- GET /conversations/{session_id}/messages → 裸 {"data": [...]},meta 整个缺失,
游标也没地方放(§3.3 要求列表的 data 为纯数组、next_cursor/has_more 进 meta)
- POST /conversation-messages/{id}/feedback → 同样没有 meta
- GET /knowledge-references/{token} → 直接返回资源对象
改动:
- 新增 app/api/views/envelope.py,把 envelope / list_envelope 抽成一份公共实现,
风控链路改为复用它 —— 同一份契约写两遍的结果就是其中一处漏了 meta。
- ConversationService.messages 改为返回内部结构 {items, next_cursor, has_more},
用 limit + 1 判断 has_more:只看"取满没取满"会把恰好等于 limit 的最后一页说成
还有下一页。next_cursor 取本页最后一条的 message_id —— 游标语义是"取更旧的一页",
天然可续,集成测试本来就是这么翻页的。
- Controller 统一套信封,data 仍是数组、字段名不变,前端不需要改。
测试:新增 tests/unit/api/test_response_envelope.py,断言 set(body) == {"data","meta"}
(多或少一个顶层字段都会红),并覆盖 has_more / next_cursor / trace_id;
另更新两处既有断言(limit 20→21、feedback 返回裸对象)。
门禁:ruff 干净 / mypy 138 文件 / 696 unit+contract / 33 integration。
This commit is contained in:
@@ -22,12 +22,34 @@ class ConversationService:
|
||||
async def messages(
|
||||
self, session_id: str, context: RequestContext, limit: int, before: int | None = None
|
||||
) -> dict[str, object]:
|
||||
"""消息列表投影;`before` 是文档 §3.8 的游标(记录 ID 边界),已由入口校验。"""
|
||||
"""消息列表投影;`before` 是文档 §3.8 的游标(记录 ID 边界),已由入口校验。
|
||||
|
||||
取 `limit + 1` 行来判断"还有没有更旧的":只看"取满没取满"会把恰好等于 limit 的
|
||||
最后一页说成还有下一页。`next_cursor` 就是本页最后一条的 `message_id` ——
|
||||
游标语义是"取更旧的一页",所以它天然可续(集成测试正是这么翻页的)。
|
||||
|
||||
返回的是内部结构 `{items, next_cursor, has_more}`,由 Controller 用
|
||||
`list_envelope` 拆成 `docs/05` §3.3 要求的 `{data, meta}`;此前这里直接返回
|
||||
`{"data": [...]}`,成功响应因此**完全没有 `meta.trace_id`**。
|
||||
"""
|
||||
rows = await self.repository.messages(
|
||||
session_id, int(context.user_id), limit, before=before
|
||||
session_id, int(context.user_id), limit + 1, before=before
|
||||
)
|
||||
return {"data": [{"message_id": str(row.id), "role": row.role, "content": row.content,
|
||||
"created_at": row.created_at.isoformat() + "Z"} for row in rows]}
|
||||
has_more = len(rows) > limit
|
||||
page_rows = rows[:limit]
|
||||
return {
|
||||
"items": [
|
||||
{
|
||||
"message_id": str(row.id),
|
||||
"role": row.role,
|
||||
"content": row.content,
|
||||
"created_at": row.created_at.isoformat() + "Z",
|
||||
}
|
||||
for row in page_rows
|
||||
],
|
||||
"next_cursor": str(page_rows[-1].id) if has_more and page_rows else None,
|
||||
"has_more": has_more,
|
||||
}
|
||||
|
||||
async def feedback(
|
||||
self, message_id: int, context: RequestContext, rating: int,
|
||||
@@ -50,7 +72,9 @@ class ConversationService:
|
||||
self.session.add(feedback)
|
||||
self._audit(context, message.session_id, "conversation.feedback_created",
|
||||
{"feedback_no": feedback.feedback_no})
|
||||
return {"data": {"feedback_no": feedback.feedback_no, "status": feedback.status}}
|
||||
# 只返回业务数据;§3.3 的信封由 Controller 套(此前这里自带 {"data": ...},
|
||||
# 于是成功响应没有 meta.trace_id)。
|
||||
return {"feedback_no": feedback.feedback_no, "status": feedback.status}
|
||||
|
||||
# 转人工申请(POST /api/v1/conversations/{id}/handover-requests)的唯一实现
|
||||
# 在 PublicPlatformService.write("handover", ...):它同一事务写工单 + Outbox
|
||||
|
||||
Reference in New Issue
Block a user