API错误码设计详解

API错误码设计详解

互联网前后端与微服务协作里,错误怎么表达直接影响联调效率、监控质量和用户体验。成熟团队通常遵守一条分工:

HTTP 状态码 描述传输与协议层;业务 code 描述应用语义;统一 JSON 信封让前端、网关、监控都能机器处理。

本文面向 全栈与架构 读者:不限定某一语言框架,覆盖单体到微服务、从设计原则到中心化治理与业界方案。适用于 REST / JSON、BFF / 网关、gRPC / Thrift 等常见形态,语言栈可替换。

速览

  • 成功 :本文约定 code === 0 (国内常见);云厂商 API 亦常见 HTTP 200 且无业务 codecode: "OK"------团队按历史包袱选型即可。
  • 业务失败 :常见 HTTP 200 + code≠0;鉴权/限流/下游不可用用对应 HTTP 状态码。
  • 前端 :用 code 分支 ,不要用 message 字符串匹配。
  • 微服务 :错误要 可追溯、可归属、不吞下游语义traceId 标配。
  • 治理 :错误码是 API 契约,宜 YAML/Proto 中心化 + CI 生成常量。

目录

  • [1. 两层错误:HTTP 与业务码](#1. 两层错误:HTTP 与业务码)
  • [2. 设计原则](#2. 设计原则)
  • [3. 统一响应结构](#3. 统一响应结构)
  • [4. 错误码如何编号](#4. 错误码如何编号)
  • [5. 后端:全局异常与分层](#5. 后端:全局异常与分层)
  • [6. 前端:拦截器与分支策略](#6. 前端:拦截器与分支策略)
  • [7. 两种流派与生产折中](#7. 两种流派与生产折中)
  • [8. 微服务:挑战与传递规范](#8. 微服务:挑战与传递规范)
  • [9. 中心化错误码治理](#9. 中心化错误码治理)
  • [10. 与 OpenAPI、监控联动](#10. 与 OpenAPI、监控联动)
  • [11. 业界方案速览](#11. 业界方案速览)
  • [12. 反模式](#12. 反模式)
  • [13. 落地阶段建议](#13. 落地阶段建议)
  • [14. 名词速查卡](#14. 名词速查卡)

1. 两层错误:HTTP 与业务码

层次 谁关心 典型值 作用
HTTP Status 浏览器、CDN、网关、爬虫 200、400、401、403、404、429、502、503 协议与基础设施语义
业务 code 前端逻辑、产品文案、业务监控 0 成功;20002 密码错误 具体业务分支

#mermaid-svg-3oqJZnGYo6GlsKlZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3oqJZnGYo6GlsKlZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3oqJZnGYo6GlsKlZ .error-icon{fill:#552222;}#mermaid-svg-3oqJZnGYo6GlsKlZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3oqJZnGYo6GlsKlZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3oqJZnGYo6GlsKlZ .marker.cross{stroke:#333333;}#mermaid-svg-3oqJZnGYo6GlsKlZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3oqJZnGYo6GlsKlZ p{margin:0;}#mermaid-svg-3oqJZnGYo6GlsKlZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3oqJZnGYo6GlsKlZ .cluster-label text{fill:#333;}#mermaid-svg-3oqJZnGYo6GlsKlZ .cluster-label span{color:#333;}#mermaid-svg-3oqJZnGYo6GlsKlZ .cluster-label span p{background-color:transparent;}#mermaid-svg-3oqJZnGYo6GlsKlZ .label text,#mermaid-svg-3oqJZnGYo6GlsKlZ span{fill:#333;color:#333;}#mermaid-svg-3oqJZnGYo6GlsKlZ .node rect,#mermaid-svg-3oqJZnGYo6GlsKlZ .node circle,#mermaid-svg-3oqJZnGYo6GlsKlZ .node ellipse,#mermaid-svg-3oqJZnGYo6GlsKlZ .node polygon,#mermaid-svg-3oqJZnGYo6GlsKlZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3oqJZnGYo6GlsKlZ .rough-node .label text,#mermaid-svg-3oqJZnGYo6GlsKlZ .node .label text,#mermaid-svg-3oqJZnGYo6GlsKlZ .image-shape .label,#mermaid-svg-3oqJZnGYo6GlsKlZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-3oqJZnGYo6GlsKlZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3oqJZnGYo6GlsKlZ .rough-node .label,#mermaid-svg-3oqJZnGYo6GlsKlZ .node .label,#mermaid-svg-3oqJZnGYo6GlsKlZ .image-shape .label,#mermaid-svg-3oqJZnGYo6GlsKlZ .icon-shape .label{text-align:center;}#mermaid-svg-3oqJZnGYo6GlsKlZ .node.clickable{cursor:pointer;}#mermaid-svg-3oqJZnGYo6GlsKlZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3oqJZnGYo6GlsKlZ .arrowheadPath{fill:#333333;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3oqJZnGYo6GlsKlZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3oqJZnGYo6GlsKlZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3oqJZnGYo6GlsKlZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3oqJZnGYo6GlsKlZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3oqJZnGYo6GlsKlZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3oqJZnGYo6GlsKlZ .cluster text{fill:#333;}#mermaid-svg-3oqJZnGYo6GlsKlZ .cluster span{color:#333;}#mermaid-svg-3oqJZnGYo6GlsKlZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3oqJZnGYo6GlsKlZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3oqJZnGYo6GlsKlZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-3oqJZnGYo6GlsKlZ .icon-shape,#mermaid-svg-3oqJZnGYo6GlsKlZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3oqJZnGYo6GlsKlZ .icon-shape p,#mermaid-svg-3oqJZnGYo6GlsKlZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3oqJZnGYo6GlsKlZ .icon-shape .label rect,#mermaid-svg-3oqJZnGYo6GlsKlZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3oqJZnGYo6GlsKlZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3oqJZnGYo6GlsKlZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3oqJZnGYo6GlsKlZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 401/502
HTTP 请求
网关 / BFF
业务服务
JSON: code message data traceId
前端按 code 处理

不要把所有失败都塞进 HTTP 500 ,也不要让前端只靠 HTTP 状态码区分「密码错误」和「库存不足」------后者应靠业务 code


2. 设计原则

原则 说明
唯一性 一个 code 只表示一种错误,禁止复用
机器可读 程序只认 code / name 常量,不认自然语言
人类可读 message 给展示与 i18n,不参与核心分支逻辑
不泄露内部 禁止向前端返回堆栈、SQL、内网 IP、未脱敏路径
可追踪 响应与日志带同一 traceId (及可选 spanId
可扩展 分段或命名空间,新模块新区间,避免撞号
中心化 定义在统一 YAML/Proto/仓库,禁止各服务私造「通用码」

进阶字段(可选):

字段 用途
type BUSINESS / SYSTEM / INFRA / AUTH / THIRD_PARTY
retryable 前端/客户端是否应退避重试
suggest_action 如跳转登录、展示表单错误

3. 统一响应结构

推荐 JSON 信封(字段名可按团队微调,但语义保持一致):

json 复制代码
{
  "code": 20002,
  "message": "密码错误",
  "data": null,
  "traceId": "a1b2c3d4e5f6"
}

path(请求路径)可选 :调试环境可返回;生产环境不少团队只在日志中记录 path,响应体不暴露,避免轻微泄露内部路由结构。

字段 说明
code 整数或字符串常量;成功见下「成功约定」
message 默认提示文案;可配合 i18n 键
data 成功时业务数据;失败时常为 null 或结构化错误详情
traceId 全链路 ID,用户报障与后端排障必备
path 可选;生产宜仅写日志

成功约定 :本文示例以 code: 0 表示成功(国内前后端协作极常见)。Google Cloud API、AWS 等更常采用 HTTP 200 + 业务体直接返回 (无额外 code)或字符串 OK。选定一种后全公司统一即可,避免同一产品两套信封。

成功示例:

json 复制代码
{
  "code": 0,
  "message": "ok",
  "data": { "userId": "u_123" },
  "traceId": "a1b2c3d4e5f6"
}

4. 错误码如何编号

4.1 分段数字(国内常见)

text 复制代码
0           成功

1xxxx       通用(参数、鉴权、系统)
  1000      参数校验失败
  1001      未登录 / Token 过期  → 常配合 HTTP 401
  1002      无权限               → 常配合 HTTP 403
  1003      资源不存在           → 常配合 HTTP 404
  1004      请求频率超限         → 常配合 HTTP 429
  1999      系统内部错误         → 常配合 HTTP 500

2xxxx       用户模块
  20001     用户不存在
  20002     密码错误

3xxxx       订单模块
  30001     库存不足
  30002     订单已取消

小团队可简化为:0 + 按模块预留区间(如用户 10001--10999、订单 20001--20999)。

4.2 结构化长码

格式示意:[级别][模块][序号],例如 2011002 → 业务(2) + 用户(01) + 密码错误(002)。

4.3 字符串码(可读性优先)

B-USER-PASSWORD_WRONGORDER_STOCK_NOT_ENOUGH。适合 OpenAPI 文档与跨语言常量名对齐;注意大小写与命名规范统一。


5. 后端:全局异常与分层

各语言框架实现不同,但职责分层一致:

异常类型 典型映射 对外注意
参数校验失败 code=1000,HTTP 400 或 200 可带字段级 data.errors
业务异常(BizException) 携带注册过的 code + message 多为 HTTP 200 或 400
未捕获异常 记完整日志,对外 1999 或通用系统码 绝不返回堆栈
网关鉴权/限流 401 / 403 / 429 由网关或统一过滤器返回标准 JSON
下游超时/不可用 502 / 503 与业务「库存不足」区分开

伪代码(语言无关):

text 复制代码
try:
  return success(data)
catch ValidationError as e:
  return envelope(code=INVALID_PARAM, http=400)
catch BizError as e:
  return envelope(code=e.code, message=e.message, http=200)
catch Exception as e:
  log.error(traceId, e)
  return envelope(code=INTERNAL_ERROR, http=500)

禁止 在业务代码里散落 magic number;应引用中心化定义的 ErrorCode 常量。


6. 前端:拦截器与分支策略

原则:HTTP 层业务层分开处理。

javascript 复制代码
// 示意:axios 响应拦截
function onResponse(response) {
  const body = response.data;
  if (body.code === 0) {
    return body.data;
  }
  switch (body.code) {
    case 1001:
      redirectToLogin();
      break;
    case 20002:
      showToast('密码错误'); // 或 i18n(body.message)
      break;
    default:
      showToast(body.message || '操作失败');
  }
  if (body.retryable) {
    // 可选:指数退避重试,注意幂等与上限
    scheduleRetry(requestConfig, body);
  }
  return Promise.reject(body);
}

function onHttpError(error) {
  // 网络断开、502、504 等 --- 非业务 code
  showToast('网络异常,请稍后重试');
}
规则 说明
codeswitch 不要 if (message.includes('密码'))
非 2xx 进 HTTP 错误处理 与 body 里 code 两条线
上报带 traceId 用户反馈时复制 traceId 给支持/后端

7. 两种流派与生产折中

流派 做法 优点 缺点
全 200 + body code 成功/失败 HTTP 均为 200 前端统一读 body;国内联调习惯 丢失 HTTP 语义;网关难按状态过滤
HTTP + body code 参数错 400、未登录 401;业务细码在 body REST 清晰;CDN/网关友好 部分旧客户端对非 200 处理麻烦

生产常见折中

  • 网关 / 鉴权 / 限流 / 路由不存在 → 正确 HTTP 状态码
  • 可预期的业务失败(余额不足、重复提交)→ HTTP 200 + code≠0
  • 文档中写清每个接口的 HTTP 与 code 组合,避免前后端各自猜测

8. 微服务:挑战与传递规范

8.1 特殊挑战

挑战 后果
各服务自定规则 BFF/前端大量映射 if-else
中间层「吞」下游错误 前端只见「系统异常」,无法排障
责任不清 工单在网关、本服务、下游之间流转混乱
缺统一 trace 多服务日志无法拼成一次用户请求
熔断与业务混淆 用户不知应重试还是改参数

8.2 传递原则

谁出错,谁定义;中间层只包装,不重写语义。

跨服务错误体建议保留:

json 复制代码
{
  "code": 30001,
  "message": "库存不足",
  "service": "inventory-service",
  "original_code": 30001,
  "original_service": "inventory-service",
  "trace_id": "abc123",
  "span_id": "def456"
}

❌ 反例:下游库存不足,上游统一成 { "code": 500, "message": "系统异常" }

8.3 gRPC / Thrift 与 HTTP 归一化

内部微服务大量采用 gRPC (或 Thrift 等 RPC),错误模型与 HTTP JSON 不同,但业务语义应同源

协议 常见载体 建议
gRPC status(如 INVALID_ARGUMENT)+ message + details details / error_details 中放 业务 coderetryabletrace_id (可用 google.rpc.Status + 自定义 Proto)
Thrift 框架异常 + 应用错误码字段 与中心化 YAML/Proto 生成的常量对齐
对外 REST JSON 信封 BFF / API 网关 将 RPC 错误 归一化 为本文 §3 结构,勿让前端直接解析 gRPC status

原则不变:RPC 层保留 original_code / original_service;中间服务不吞下游业务语义。

8.4 HTTP 状态码由谁决定

场景 HTTP 说明
网关鉴权失败 401 网关直接返回
路由不存在 404 网关 / API 网关
限流 429 网关
业务失败 200 body.code ≠ 0
下游不可用 502 / 503 网关或 BFF

不要让每个微服务各自发明一套「HTTP 策略」;内部走 RPC 时由 status + details 表达,对外由 BFF/网关归一化。

8.5 错误分类:业务、系统、基础设施

类型 type(示意) 可重试 用户可见 示例
业务 BUSINESS 库存不足、格式错误
系统 SYSTEM 有时 通常否 DB 连接失败
基础设施 INFRA 可选提示 熔断、降级、下游超时

熔断/降级归类为 INFRA / SYSTEM,而非业务错误。宜独立码段,例如:

text 复制代码
9001  熔断(Circuit Breaker Open)
9002  降级(Fallback)
9003  下游超时

8.6 BFF / 网关归一化

  • 统一 JSON 字段名与 code 体系
  • 将内部服务名、技术细节转为用户可读 message
  • 不要inventory-service panic 原文返回给前端

9. 中心化错误码治理

错误码是 API Schema 的一部分,不是某个仓库里的私有魔法数字。
#mermaid-svg-NwLLvxxLj82sO6L2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NwLLvxxLj82sO6L2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NwLLvxxLj82sO6L2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NwLLvxxLj82sO6L2 .error-icon{fill:#552222;}#mermaid-svg-NwLLvxxLj82sO6L2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NwLLvxxLj82sO6L2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NwLLvxxLj82sO6L2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NwLLvxxLj82sO6L2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NwLLvxxLj82sO6L2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NwLLvxxLj82sO6L2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NwLLvxxLj82sO6L2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NwLLvxxLj82sO6L2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NwLLvxxLj82sO6L2 .marker.cross{stroke:#333333;}#mermaid-svg-NwLLvxxLj82sO6L2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NwLLvxxLj82sO6L2 p{margin:0;}#mermaid-svg-NwLLvxxLj82sO6L2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NwLLvxxLj82sO6L2 .cluster-label text{fill:#333;}#mermaid-svg-NwLLvxxLj82sO6L2 .cluster-label span{color:#333;}#mermaid-svg-NwLLvxxLj82sO6L2 .cluster-label span p{background-color:transparent;}#mermaid-svg-NwLLvxxLj82sO6L2 .label text,#mermaid-svg-NwLLvxxLj82sO6L2 span{fill:#333;color:#333;}#mermaid-svg-NwLLvxxLj82sO6L2 .node rect,#mermaid-svg-NwLLvxxLj82sO6L2 .node circle,#mermaid-svg-NwLLvxxLj82sO6L2 .node ellipse,#mermaid-svg-NwLLvxxLj82sO6L2 .node polygon,#mermaid-svg-NwLLvxxLj82sO6L2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NwLLvxxLj82sO6L2 .rough-node .label text,#mermaid-svg-NwLLvxxLj82sO6L2 .node .label text,#mermaid-svg-NwLLvxxLj82sO6L2 .image-shape .label,#mermaid-svg-NwLLvxxLj82sO6L2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-NwLLvxxLj82sO6L2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NwLLvxxLj82sO6L2 .rough-node .label,#mermaid-svg-NwLLvxxLj82sO6L2 .node .label,#mermaid-svg-NwLLvxxLj82sO6L2 .image-shape .label,#mermaid-svg-NwLLvxxLj82sO6L2 .icon-shape .label{text-align:center;}#mermaid-svg-NwLLvxxLj82sO6L2 .node.clickable{cursor:pointer;}#mermaid-svg-NwLLvxxLj82sO6L2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NwLLvxxLj82sO6L2 .arrowheadPath{fill:#333333;}#mermaid-svg-NwLLvxxLj82sO6L2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NwLLvxxLj82sO6L2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NwLLvxxLj82sO6L2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NwLLvxxLj82sO6L2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NwLLvxxLj82sO6L2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NwLLvxxLj82sO6L2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NwLLvxxLj82sO6L2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NwLLvxxLj82sO6L2 .cluster text{fill:#333;}#mermaid-svg-NwLLvxxLj82sO6L2 .cluster span{color:#333;}#mermaid-svg-NwLLvxxLj82sO6L2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NwLLvxxLj82sO6L2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NwLLvxxLj82sO6L2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-NwLLvxxLj82sO6L2 .icon-shape,#mermaid-svg-NwLLvxxLj82sO6L2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NwLLvxxLj82sO6L2 .icon-shape p,#mermaid-svg-NwLLvxxLj82sO6L2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NwLLvxxLj82sO6L2 .icon-shape .label rect,#mermaid-svg-NwLLvxxLj82sO6L2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NwLLvxxLj82sO6L2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NwLLvxxLj82sO6L2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NwLLvxxLj82sO6L2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} errors.yaml / Proto 定义
CI 校验唯一性区间
代码生成 Java Go TS
OpenAPI 错误示例
error-meta 告警映射
各微服务引用常量

9.1 元数据示例(YAML)

yaml 复制代码
version: "1.0"

domains:
  common:
    range: 1000-1999
    codes:
      - code: 1000
        name: INVALID_PARAM
        http_status: 400
        type: BUSINESS
        retryable: false
        summary_zh: 参数校验失败
      - code: 1001
        name: UNAUTHORIZED
        http_status: 401
        type: AUTH
        retryable: false
        summary_zh: 未登录或 Token 过期

  order:
    range: 30000-30999
    codes:
      - code: 30001
        name: STOCK_NOT_ENOUGH
        http_status: 200
        type: BUSINESS
        retryable: false
        summary_zh: 库存不足

9.2 治理方式选型

规模 方式
小团队 Git + YAML + MR 评审 + CI 生成
中大型 管理后台 + DB,定期 export 到 Git
约束 禁止手写 magic number;CI 扫描硬编码
SDK 使用 各服务只依赖 生成的常量 / SDK禁止 用反射、运行时解析 message 或动态字符串做分支------error-meta 变更会导致逻辑静默失效

9.3 版本

  • 错误码规范带 version;服务声明依赖的 spec 版本
  • 新增 code:向后兼容
  • 修改语义 / 删除:major version bump,通知客户端

10. 与 OpenAPI、监控联动

10.1 OpenAPI

在接口 responses 中声明错误示例,而不仅是 200

yaml 复制代码
responses:
  '200':
    description: 业务成功或业务失败(见 code)
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/ApiEnvelope'
        examples:
          success:
            value: { code: 0, message: ok, data: {} }
          stock_error:
            value: { code: 30001, message: 库存不足, data: null }

每个接口文档应列出可能出现的业务错误码及处理建议(类似云厂商 OpenAPI 错误码表)。

10.2 监控与告警

导出 error-meta.json 供 Prometheus / Grafana 使用:

json 复制代码
{
  "30001": { "type": "BUSINESS", "severity": "INFO", "alert": false },
  "5000":  { "type": "SYSTEM", "severity": "CRITICAL", "alert": true }
}
  • 指标:api_error_total{code="30001",service="order"}
  • 业务错误 (库存不足)→ 统计转化率,半夜 PagerDuty
  • 系统 / INFRA 错误 → 按阈值告警
  • SLO 视角 :业务 code 通常 不应计入 SLI (如可用性、成功率);系统错误码才是 SLO 与**错误预算(Error Budget)**的主要依据------避免「库存不足」吃掉可用性指标

日志与 APM:每条错误日志带 trace_iderror_codeservice;Span 标记 ERROR + code。


11. 业界方案速览

没有类似 MySQL 的「统一错误码标准产品」;大厂多 自研 Registry + Schema + CI 生成

思路 代表 特点
Schema + 生成器 自建 YAML + CI;Rust biz-error;Go error-framework 无运行时依赖,最常用
管理平台 err0、ErrorHub Web UI + i18n + SDK 拉取;需考虑高可用
框架内置 斗鱼 Jupiter(Go)、go-zero 示例 错误码分段 + 文档/监控标签
大厂闭源参考 字节 Protobuf error_code、腾讯多协议 Registry gRPC/HTTP/Thrift 映射一致
团队规模 建议
≤20 服务 Git YAML + CI 生成(Java/Go/TS)
多语言 + 要 UI 评估 err0 / ErrorHub
已有 gRPC 体系 Proto 定义 error details + 代码生成

12. 反模式

反模式 后果
前端用 message 字符串匹配 i18n/改文案即逻辑 bug
同一 code 多种含义 监控与客户端行为混乱
下游错误包装成 500 排障地狱
业务错误触发系统告警 库存不足半夜叫醒 oncall
各服务私造 1001「未登录」 网关映射爆炸
响应带堆栈/SQL 安全风险
反射/动态字符串判断 code error-meta 变更后逻辑静默错误

13. 落地阶段建议

阶段 周期(示意) 内容
1--2 周 errors.yaml + CI 校验 + 多语言常量生成;新接口强制用常量
1--2 月 OpenAPI 注入错误示例;日志/监控按 BUSINESS/SYSTEM 拆分
持续 管理平台、i18n 文案流水线、告警模板、前端 SDK

14. 名词速查卡

text 复制代码
┌────────────────────────────────────────────────────────────┐
│ HTTP 状态码 = 协议/基础设施;body.code = 业务语义          │
│ 成功 code=0;逻辑分支看 code,不看 message                 │
│ traceId 贯穿响应、日志、APM                                │
│ 微服务:不吞 original_code;BFF 归一化对外文案             │
│ 错误码 = Schema → CI 生成 → 文档 + 监控                    │
└────────────────────────────────────────────────────────────┘

延伸阅读

资源 链接
HTTP 状态码 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
OpenAPI 规范 https://spec.openapis.org/
OpenTelemetry 追踪 https://opentelemetry.io/docs/
Google API 错误模型(参考) https://cloud.google.com/apis/design/errors
gRPC 状态码 https://grpc.io/docs/guides/status-codes/

一句话 :好的错误码体系 = HTTP 管传输、code 管业务、信封统一、trace 可追溯 ;在微服务里还要 不吞语义、中心化治理,让文档、代码与监控读同一套定义。