加载中…
错误码与限流

双重表达:HTTP 状态码 + 业务码

每个响应的结果由两层共同表达,客户端应以业务码 code 分支、以 HTTP 状态码做传输层判断:

  • HTTP 状态码:表达请求在协议层的处理结果(200/401/404/422/429/500...);
  • 业务码 code:表达业务语义(OK、UNAUTHORIZED、NOT_FOUND...),成功恒为 OK。

失败响应结构(与成功同信封):

{
  "code": "VALIDATION_FAILED",
  "message": "手机号不能为空",
  "data": {},
  "errors": { "mobile": "手机号不能为空" },
  "request_id": "9f2a5c8e1b3d7f40"
}

errors 与 data 的分工:

字段 用途
errors 字段级校验错误集合,键=字段名、值=错误文案。适合做表单逐项红字标注
data 失败时仍需回传的辅助业务数据(如 405 的 allow 允许方法列表)

业务码(code)全集

业务码 典型 HTTP 含义与客户端建议动作
OK 200 成功
BUSINESS_RULE_VIOLATION 422 通用业务规则违反(如库存不足、状态不允许操作)。展示 message 即可
INVALID_PARAMS 422 / 400 参数非法 / 无效 ID。检查请求参数
VALIDATION_FAILED 422 字段级校验失败。按 errors 逐字段提示
UNAUTHORIZED 401 未携带或携带了无效令牌。清本地令牌 → 跳登录
AUTH_REQUIRED 401 需要登录后访问(部分场景带 data.jump_url 引导地址)
FORBIDDEN 403 已登录但无权限(如工作端接口无员工身份、模块未装)
NOT_FOUND 404 资源不存在(数据 ID 无效、路由不存在)
QUOTA_EXCEEDED 422 / 429 配额 / 额度超限(AI 对话次数、短信额度等)
RATE_LIMITED 429 请求过频被限流。按 Retry-After 退避重试
SERVER_ERROR 500 服务端兜底错误。展示通用错误提示,可记录 request_id 反馈
AI_CONFIG_UNAVAILABLE 503 AI 模型 / 服务不可用(见《AI 助手接口》)
CHAT_NOT_FOUND 404 AI 会话不存在或无权限(见《AI 助手接口》)
CHAT_CREATE_FAILED 500 AI 会话创建失败
CHAT_SAVE_FAILED 500 AI 会话保存失败

业务码字符串是对外契约:客户端可硬编码这些值做分支,服务端只会在末尾追加新码,不会改名。新增码会通过文档与发版说明同步。

HTTP 状态码行为说明

状态码 触发场景 备注
200 成功;以及「检查登录态」这类"查询结果负面但不是错误"的接口 未登录调 check_login_state 也返回 200 + login_state:false
204 CORS 预检(OPTIONS)通过 空 body + 全套 Access-Control-Allow-* 头
401 必须鉴权的接口未带 / 带错令牌 code=UNAUTHORIZED
403 无工作端身份等权限不足 code=FORBIDDEN
404 数据不存在;路由不存在(含方法/路径拼写错误) 路由不存在时也是 JSON 信封,不是 HTML 404 页
405 OPTIONS 预检未命中白名单;路由存在但 HTTP 方法不允许 方法不允许时 code 仍为 NOT_FOUND、message 为 Method Not Allowed,data.allow 给出允许的方法列表
422 字段校验失败、业务规则违反(默认失败状态码) 默认的"业务失败"载体
429 触发定向限流 带 Retry-After 头
500 未预期异常 / 邮件发送失败等 code=SERVER_ERROR
503 AI Provider 不可用 code=AI_CONFIG_UNAVAILABLE

405 示例(用 GET 调了一个只支持 POST 的接口):

{
  "code": "NOT_FOUND",
  "message": "Method Not Allowed",
  "data": { "allow": ["POST"] },
  "errors": {},
  "request_id": "9f2a5c8e1b3d7f40"
}

限流(429)

以下接口按客户端 IP 定向限流,超限返回 429 RATE_LIMITED 并带 Retry-After 响应头(单位:秒,表示最早可重试时间):

HTTP/1.1 429 Too Many Requests
Retry-After: 37
Content-Type: application/json; charset=utf-8

{
  "code": "RATE_LIMITED",
  "message": "请求过于频繁,请稍后再试",
  "data": {},
  "errors": {},
  "request_id": "9f2a5c8e1b3d7f40"
}

完整配额表:

接口 配额 窗口
POST user/login_post 5 次 60 秒
POST user/login_phone_post 5 次 60 秒
POST user/register_post 5 次 60 秒
POST user/password_reset_post 5 次 300 秒
POST captcha/verification 5 次 300 秒
POST guestbook/store 5 次 300 秒
POST consultation/store 5 次 300 秒
POST email/store 5 次 300 秒
GET sn/search 20 次 60 秒
POST chat/stream 20 次 60 秒
POST chat/new_session 10 次 60 秒

重试约定:收到 429 后应读取 Retry-After 秒数做退避;不要立即重试。前端对表单类接口(登录/注册)建议在按钮上做倒计时提示。

除上表外,公共匿名写接口在各控制器业务层还有一层按 IP 的"灌水"防护(isWaterByIp),两者互补:中间件拦高频,业务层拦内容灌水。

排查建议

  1. 先看 code 与 errors:表单类问题基本都由 errors 定位到具体字段;
  2. 记录 request_id:只要把该 ID 提供给服务端,即可在日志中定位完整请求链路;
  3. 区分 401 的两种子态:UNAUTHORIZED 是令牌无效(清令牌重登);若未来收到 AUTH_REQUIRED 且有 data.jump_url,可直接跳转该地址登录;
  4. 405 时看 data.allow:确认自己用的 HTTP 方法是否与文档一致(注意方法伪装 _method / X-HTTP-Method-Override 是否拼写正确)。
添加日期:2026-10-06