9.1 KiB
9.1 KiB
M0:前端直接依赖的响应头、Envelope 与错误格式冻结基线
日期:2026-04-20
依据来源:
server-node/src/http.tsserver-node/src/middleware/responseEnvelope.tsserver-node/src/middleware/errorHandler.tsserver-node/src/middleware/requestId.tspackages/shared/src/http.tssrc/services/apiClient.tssrc/services/authService.tssrc/services/aiService.tssrc/editor/shared/jsonClient.tssrc/services/apiClient.test.ts
1. 文档目的
这份文档用于完成 M0 的第六条任务:
- 整理当前前端直接依赖的响应头、envelope、错误格式
这里的“直接依赖”指的是:如果 Axum 重写时把这些头或 body 结构改掉,当前前端 src/services/*、编辑器请求层和鉴权异常处理就会立刻出问题。
2. 冻结结论
当前前端直接依赖的响应契约,冻结为以下 4 层:
- 请求侧默认会发送
x-genarrative-response-envelope: v1。 - 响应侧默认要回
x-request-id、x-api-version、x-route-version。 - 成功响应在请求方要求 envelope 时,必须返回标准
ok/data/error/meta结构。 - 错误响应既要兼容标准 envelope,也要兼容旧式
{ error, meta }/{ message, code }解析回退。
补充结论:
- 当前正式前端代码里,没有生产用例主动关闭 envelope 请求头。
x-response-time-ms当前不是前端代码的直接读取项,但属于现有兼容头集合,重写时仍应保留。- 鉴权链路额外直接依赖错误码
CAPTCHA_REQUIRED与error.details.captchaChallenge。
3. 当前前端直接依赖矩阵
| 依赖项 | 当前值/结构 | 当前消费者 | 当前作用 |
|---|---|---|---|
| 请求头 | x-genarrative-response-envelope: v1 |
src/services/apiClient.ts、src/editor/shared/jsonClient.ts |
请求标准 envelope 响应。 |
| 响应头 | x-request-id |
src/services/apiClient.ts |
构造 ApiClientError.meta.requestId 的回退来源。 |
| 响应头 | x-api-version |
src/services/apiClient.ts、packages/shared/src/http.ts |
识别标准 envelope / error body。 |
| 响应头 | x-route-version |
src/services/apiClient.ts |
构造 ApiClientError.meta.routeVersion 的回退来源。 |
| 成功 body | { ok: true, data, error: null, meta } |
unwrapApiResponse(...) |
前端默认解包标准成功 envelope。 |
| 错误 body | { ok: false, data: null, error, meta } |
ApiClientError、parseApiErrorMessage(...) |
标准错误解析。 |
| 旧错误 body | { error, meta } / { message, code } |
parseApiErrorMessage(...) |
老接口或非标准错误回退解析。 |
| 错误细节 | error.code === 'CAPTCHA_REQUIRED' 且 error.details.captchaChallenge |
src/services/authService.ts |
手机验证码发送前的验证码挑战弹出。 |
4. 请求侧冻结要求
4.1 Envelope 请求头
当前前端默认行为:
src/services/apiClient.ts会自动补:x-genarrative-response-envelope: v1
src/editor/shared/jsonClient.ts也会自动补:x-genarrative-response-envelope: v1
当前后端接受的 envelope 触发值:
1truev1envelope
但当前前端真实发送值冻结为:
v1
补充冻结点:
- 虽然
apiClient提供了omitEnvelopeHeader选项,但当前生产代码没有实际依赖它。 - 因此第一阶段 Axum 应默认兼容“前端请求即要 envelope”的模式。
4.2 鉴权与凭证约定
当前前端请求层默认还会做:
Authorization: Bearer <token>自动注入。credentials: same-origin。- 遇到
401时尝试走/api/auth/refresh自动刷新。
这不是本文重点,但它解释了为什么 envelope 和错误格式必须在 /api/auth/refresh 上也保持兼容。
5. 响应头冻结要求
5.1 必须保留的前端直接依赖头
| 响应头 | 当前来源 | 当前前端用法 |
|---|---|---|
x-request-id |
requestIdMiddleware + applyApiResponseHeaders |
ApiClientError.meta.requestId 的 header 回退来源。 |
x-api-version |
applyApiResponseHeaders |
当前标准 API 契约版本识别。 |
x-route-version |
applyApiResponseHeaders |
ApiClientError.meta.routeVersion 的 header 回退来源。 |
5.2 兼容头但非直接读取项
| 响应头 | 当前状态 | 说明 |
|---|---|---|
x-response-time-ms |
当前统一输出 | 目前前端代码未直接读取,但设计文档与联调约定已锁定,不能随意删除。 |
补充冻结点:
requestIdMiddleware会优先回显请求方传入的x-request-id,否则服务端自生成。ApiClientError读取元信息时优先取 bodymeta,没有再回退到 headers。- 这意味着即便 envelope body 缺少部分
meta字段,headers 仍必须完整。
6. 成功响应 Envelope 冻结格式
当前标准成功 envelope:
{
"ok": true,
"data": {},
"error": null,
"meta": {
"apiVersion": "2026-04-08",
"requestId": "req-xxx",
"routeVersion": "2026-04-08",
"operation": "runtime.story.initial",
"latencyMs": 12,
"timestamp": "2026-04-20T00:00:00.000Z"
}
}
冻结规则:
ok必须为true。data为真实业务负载。error必须为null。meta.apiVersion必须存在,因为unwrapApiResponse(...)与isApiResponse(...)依赖它判断标准 envelope。
补充说明:
- 如果请求未带 envelope 头,当前后端可以直接返回裸
data。 - 但由于当前前端默认都会请求 envelope,第一阶段 Axum 基本等价于“所有 JSON 成功响应都要兼容这个结构”。
7. 错误响应 Envelope 与旧格式回退
7.1 当前标准错误 envelope
{
"ok": false,
"data": null,
"error": {
"code": "UNAUTHORIZED",
"message": "缺少 Authorization Bearer Token",
"details": null
},
"meta": {
"apiVersion": "2026-04-08",
"requestId": "req-xxx",
"routeVersion": "2026-04-08",
"operation": "auth.me",
"latencyMs": 3,
"timestamp": "2026-04-20T00:00:00.000Z"
}
}
冻结规则:
ok必须为false。data必须为null。error.code、error.message必须存在。error.details可为对象或null。meta.apiVersion必须存在。
7.2 当前旧式错误格式回退
当请求未要求 envelope,或某些链路仍走旧写法时,当前后端与前端仍兼容以下错误结构:
{ "error": { "code": "...", "message": "...", "details": ... }, "meta": {...} }{ "message": "...", "code": "..." }{ "error": { "message": "..." } }- 纯文本错误响应
parseApiErrorMessage(rawText, fallbackMessage) 的当前回退顺序固定为:
parsed.error.message- 顶层
message error.code或顶层code,拼成fallback(CODE)- 原始文本
- 调用方的
fallbackMessage
这意味着:
- Axum 第一阶段不能只兼容标准 envelope,而忽略旧错误解析的回退行为。
- 至少在迁移过渡期,
parseApiErrorMessage(...)可识别的信息要继续保留。
8. 前端对错误细节的业务级直接依赖
8.1 验证码挑战
src/services/authService.ts 当前明确依赖:
error instanceof ApiClientErrorerror.code === 'CAPTCHA_REQUIRED'error.details.captchaChallenge
冻结要求:
- 如果后端要继续触发验证码挑战,必须继续返回:
code: 'CAPTCHA_REQUIRED'details.captchaChallenge
- 不能只返回中文文案而不带结构化
details。
8.2 元信息透传
ApiClientError 当前会保留:
statuscodedetailsmeta.apiVersionmeta.requestIdmeta.routeVersionmeta.operationmeta.latencyMsmeta.timestamp
冻结要求:
- Axum 不能把这些字段全都删成单纯
message字符串。 - 即使部分业务 UI 现在没显示这些字段,它们已经进入前端错误对象结构。
9. 当前消费者清单
以下文件已构成当前前端的直接依赖面:
src/services/apiClient.tssrc/services/authService.tssrc/services/aiService.tssrc/editor/shared/jsonClient.tspackages/shared/src/http.ts
10. 本轮冻结后的硬约束
后续迁移中,不允许出现以下情况:
- 删除
x-genarrative-response-envelope: v1的请求协商能力。 - 删除
x-request-id、x-api-version、x-route-version这些当前前端直接依赖的响应头。 - 把成功 envelope 从
{ ok, data, error, meta }改成其他字段名。 - 把错误 envelope 从
{ ok: false, data: null, error, meta }改成只有message。 - 删除
CAPTCHA_REQUIRED + details.captchaChallenge这一结构化错误契约。 - 让前端默认请求 envelope,但后端返回裸数据且不再可被
unwrapApiResponse(...)识别。
11. 本任务完成定义
当以下条件成立时,这条任务视为完成:
- 当前前端直接依赖的响应头、envelope、错误格式已有书面冻结清单。
- 已明确哪些是前端直接读取项,哪些是兼容保留项。
- 后续 Axum handler、错误中间件、response envelope 中间件可以直接按本文对齐,而不再靠人工试错。