Files
Genarrative/backend-rewrite-tasklist/M0_FRONTEND_RESPONSE_CONTRACT_BASELINE_2026-04-20.md
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

9.1 KiB
Raw Blame History

M0前端直接依赖的响应头、Envelope 与错误格式冻结基线

日期:2026-04-20

依据来源:

  • server-node/src/http.ts
  • server-node/src/middleware/responseEnvelope.ts
  • server-node/src/middleware/errorHandler.ts
  • server-node/src/middleware/requestId.ts
  • packages/shared/src/http.ts
  • src/services/apiClient.ts
  • src/services/authService.ts
  • src/services/aiService.ts
  • src/editor/shared/jsonClient.ts
  • src/services/apiClient.test.ts

1. 文档目的

这份文档用于完成 M0 的第六条任务:

  • 整理当前前端直接依赖的响应头、envelope、错误格式

这里的“直接依赖”指的是:如果 Axum 重写时把这些头或 body 结构改掉,当前前端 src/services/*、编辑器请求层和鉴权异常处理就会立刻出问题。

2. 冻结结论

当前前端直接依赖的响应契约,冻结为以下 4 层:

  1. 请求侧默认会发送 x-genarrative-response-envelope: v1
  2. 响应侧默认要回 x-request-idx-api-versionx-route-version
  3. 成功响应在请求方要求 envelope 时,必须返回标准 ok/data/error/meta 结构。
  4. 错误响应既要兼容标准 envelope也要兼容旧式 { error, meta } / { message, code } 解析回退。

补充结论:

  1. 当前正式前端代码里,没有生产用例主动关闭 envelope 请求头。
  2. x-response-time-ms 当前不是前端代码的直接读取项,但属于现有兼容头集合,重写时仍应保留。
  3. 鉴权链路额外直接依赖错误码 CAPTCHA_REQUIREDerror.details.captchaChallenge

3. 当前前端直接依赖矩阵

依赖项 当前值/结构 当前消费者 当前作用
请求头 x-genarrative-response-envelope: v1 src/services/apiClient.tssrc/editor/shared/jsonClient.ts 请求标准 envelope 响应。
响应头 x-request-id src/services/apiClient.ts 构造 ApiClientError.meta.requestId 的回退来源。
响应头 x-api-version src/services/apiClient.tspackages/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 } ApiClientErrorparseApiErrorMessage(...) 标准错误解析。
旧错误 body { error, meta } / { message, code } parseApiErrorMessage(...) 老接口或非标准错误回退解析。
错误细节 error.code === 'CAPTCHA_REQUIRED'error.details.captchaChallenge src/services/authService.ts 手机验证码发送前的验证码挑战弹出。

4. 请求侧冻结要求

4.1 Envelope 请求头

当前前端默认行为:

  1. src/services/apiClient.ts 会自动补:
    • x-genarrative-response-envelope: v1
  2. src/editor/shared/jsonClient.ts 也会自动补:
    • x-genarrative-response-envelope: v1

当前后端接受的 envelope 触发值:

  1. 1
  2. true
  3. v1
  4. envelope

但当前前端真实发送值冻结为:

  1. v1

补充冻结点:

  1. 虽然 apiClient 提供了 omitEnvelopeHeader 选项,但当前生产代码没有实际依赖它。
  2. 因此第一阶段 Axum 应默认兼容“前端请求即要 envelope”的模式。

4.2 鉴权与凭证约定

当前前端请求层默认还会做:

  1. Authorization: Bearer <token> 自动注入。
  2. credentials: same-origin
  3. 遇到 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 当前统一输出 目前前端代码未直接读取,但设计文档与联调约定已锁定,不能随意删除。

补充冻结点:

  1. requestIdMiddleware 会优先回显请求方传入的 x-request-id,否则服务端自生成。
  2. ApiClientError 读取元信息时优先取 body meta,没有再回退到 headers。
  3. 这意味着即便 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"
  }
}

冻结规则:

  1. ok 必须为 true
  2. data 为真实业务负载。
  3. error 必须为 null
  4. meta.apiVersion 必须存在,因为 unwrapApiResponse(...)isApiResponse(...) 依赖它判断标准 envelope。

补充说明:

  1. 如果请求未带 envelope 头,当前后端可以直接返回裸 data
  2. 但由于当前前端默认都会请求 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"
  }
}

冻结规则:

  1. ok 必须为 false
  2. data 必须为 null
  3. error.codeerror.message 必须存在。
  4. error.details 可为对象或 null
  5. meta.apiVersion 必须存在。

7.2 当前旧式错误格式回退

当请求未要求 envelope或某些链路仍走旧写法时当前后端与前端仍兼容以下错误结构

  1. { "error": { "code": "...", "message": "...", "details": ... }, "meta": {...} }
  2. { "message": "...", "code": "..." }
  3. { "error": { "message": "..." } }
  4. 纯文本错误响应

parseApiErrorMessage(rawText, fallbackMessage) 的当前回退顺序固定为:

  1. parsed.error.message
  2. 顶层 message
  3. error.code 或顶层 code,拼成 fallbackCODE
  4. 原始文本
  5. 调用方的 fallbackMessage

这意味着:

  1. Axum 第一阶段不能只兼容标准 envelope而忽略旧错误解析的回退行为。
  2. 至少在迁移过渡期,parseApiErrorMessage(...) 可识别的信息要继续保留。

8. 前端对错误细节的业务级直接依赖

8.1 验证码挑战

src/services/authService.ts 当前明确依赖:

  1. error instanceof ApiClientError
  2. error.code === 'CAPTCHA_REQUIRED'
  3. error.details.captchaChallenge

冻结要求:

  1. 如果后端要继续触发验证码挑战,必须继续返回:
    • code: 'CAPTCHA_REQUIRED'
    • details.captchaChallenge
  2. 不能只返回中文文案而不带结构化 details

8.2 元信息透传

ApiClientError 当前会保留:

  1. status
  2. code
  3. details
  4. meta.apiVersion
  5. meta.requestId
  6. meta.routeVersion
  7. meta.operation
  8. meta.latencyMs
  9. meta.timestamp

冻结要求:

  1. Axum 不能把这些字段全都删成单纯 message 字符串。
  2. 即使部分业务 UI 现在没显示这些字段,它们已经进入前端错误对象结构。

9. 当前消费者清单

以下文件已构成当前前端的直接依赖面:

  1. src/services/apiClient.ts
  2. src/services/authService.ts
  3. src/services/aiService.ts
  4. src/editor/shared/jsonClient.ts
  5. packages/shared/src/http.ts

10. 本轮冻结后的硬约束

后续迁移中,不允许出现以下情况:

  1. 删除 x-genarrative-response-envelope: v1 的请求协商能力。
  2. 删除 x-request-idx-api-versionx-route-version 这些当前前端直接依赖的响应头。
  3. 把成功 envelope 从 { ok, data, error, meta } 改成其他字段名。
  4. 把错误 envelope 从 { ok: false, data: null, error, meta } 改成只有 message
  5. 删除 CAPTCHA_REQUIRED + details.captchaChallenge 这一结构化错误契约。
  6. 让前端默认请求 envelope但后端返回裸数据且不再可被 unwrapApiResponse(...) 识别。

11. 本任务完成定义

当以下条件成立时,这条任务视为完成:

  1. 当前前端直接依赖的响应头、envelope、错误格式已有书面冻结清单。
  2. 已明确哪些是前端直接读取项,哪些是兼容保留项。
  3. 后续 Axum handler、错误中间件、response envelope 中间件可以直接按本文对齐,而不再靠人工试错。