KAIROS.WORKSPACE
技术宇宙 / ONLINE
返回踩坑地图
踩坑复盘中级5 分钟

订阅接口 503 被前端翻译成了"邮箱格式不对"

后端在数据库故障时复用了 invalid 状态码,前端按 state 映射文案,服务不可用被误导为输入错误的跨层契约复盘。

#API 设计#错误处理#订阅

现象

一次例行代码走查中发现:订阅接口的 catch 分支在数据库写入失败时返回 state: 'invalid' + HTTP 503 + message: '订阅服务暂时不可用,请稍后重试。'。而前端只按 state 映射两种文案:rate-limited → “尝试次数过多”,其余一律 → “邮箱格式似乎不对,请检查后重新提交。”——服务端精心准备的 message 被整块丢弃。

后果:服务真挂了的时候,用户反复修改正确的邮箱地址,越改越困惑;“稍后再试”的引导完全没机会触达。

根因

两层各自的问题叠在一起:

  1. 服务端状态复用invalid 本意是“输入无效”,故障分支图省事也塞进了 invalid,真实原因只活在没人读的 message 字段里。状态枚举 ['pending','confirmed','invalid',...] 里没有一个表达“服务暂不可用”的取值。
  2. 前端把映射写死成二元state === 'rate-limited' ? A : B,隐含假设“失败必是输入问题”。

修复

  • 前端:payload.message 存在时优先透传,否则按 response.status 兜底分桶(429 限流文案、≥500 服务不可用文案、其余输入文案)。
  • 状态页:当服务端通过 redirect 带上 message 查询参数时同样优先展示,保证无 JS 的原生表单流也看到真实原因。
  • 服务端(遗留建议):为服务故障引入独立的 unavailable 状态值属于契约变更,按变更控制流程登记后再做。

教训

  • 面向用户的文案要透传,不要在客户端重新发明。 服务端最清楚失败原因,前端的职责是渲染而非猜测。
  • 错误状态枚举要能区分“用户可修复”与“用户不可修复”两类,前者引导改输入,后者引导等待或换渠道(RSS)。
  • 走查这种问题最有效的手段:把接口的每一个失败分支在本地用 mock 打出来,对照前端实际显示——本项目随后用 Playwright 路由拦截把“503 透传”固化成了回归用例。