3. 错误码设计原则:HTTP状态码与业务错误码的区别,RESTful API错误响应体设计规范
3.1 核心概念辨析:HTTP状态码 vs 业务错误码
在RESTful API设计中,错误码体系分为两个层次:传输层状态码(HTTP Status Code)与应用层业务码(Business Error Code)。二者职责不同,不能混用,也不能互相替代。
| 维度 | HTTP状态码 | 业务错误码 |
|---|---|---|
| 所属层次 | 传输层(HTTP协议) | 应用层(业务逻辑) |
| 语义范围 | 通用、协议级(如404表示资源不存在) | 特定、业务级(如"USER_ACCOUNT_LOCKED") |
| 粒度 | 粗粒度(约60个标准码) | 细粒度(可定义数千个) |
| 客户端处理方式 | 决定重试、重定向、降级 | 决定展示文案、补偿逻辑、跳转路径 |
| 是否可扩展 | 不建议自定义(违反RFC) | 完全由API设计者定义 |
| 典型示例 | 400 Bad Request | "INVALID_PARAMETER_FORMAT" |
核心原则:HTTP状态码只描述"请求能否被正常处理";业务错误码描述"处理结果的具体业务含义"。一个400状态码可以对应数十种不同的业务错误码。
3.2 分层映射关系
HTTP状态码与业务错误码之间存在一对多的映射关系。设计时应遵循以下映射规则:
- 2xx 成功族:业务错误码为"SUCCESS"或"OK",或省略不传。
- 4xx 客户端错误族:对应参数校验失败、鉴权失败、资源冲突、请求被限流等业务场景。
- 5xx 服务端错误族:对应内部异常、依赖超时、数据不一致等不可控场景。
3.3 标准错误响应体设计规范
一个结构清晰、可扩展的错误响应体是API契约的核心。推荐遵循 RFC 7807 Problem Details 规范,并结合业务需求进行适度扩展。
3.3.1 最小必要字段
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
code |
string | 是 | 业务错误码,如 INSUFFICIENT_BALANCE |
message |
string | 是 | 人类可读的错误描述,用于日志和调试 |
status |
integer | 是 | HTTP状态码,方便客户端快速判断 |
trace_id |
string | 推荐 | 请求追踪ID,用于关联日志和排查问题 |
3.3.2 推荐扩展字段
| 字段名 | 类型 | 说明 |
|---|---|---|
details |
array | 字段级校验错误列表,每个元素包含 field、reason、rejected_value |
documentation_url |
string | 指向该错误码的详细文档链接 |
retry_after |
integer | 限流或临时性错误时,建议客户端等待的秒数 |
3.3.3 完整响应体示例
{
"status": 400,
"code": "INVALID_PARAMETER",
"message": "请求参数校验失败,请检查输入",
"trace_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"details": [
{
"field": "email",
"reason": "邮箱格式不正确",
"rejected_value": "user@invalid"
},
{
"field": "age",
"reason": "年龄必须在18-120之间",
"rejected_value": 15
}
],
"documentation_url": "https://api.example.com/docs/errors/INVALID_PARAMETER"
}
设计建议:错误响应体中的
message 字段应面向开发者,而非最终用户。面向用户的错误提示应在前端根据 code 字段进行国际化映射。
3.4 错误码命名规范
业务错误码的命名应具备自解释性和可搜索性,推荐采用以下约定:
- 全大写 + 下划线:如
USER_NOT_FOUND、ORDER_ALREADY_PAID - 前缀分组:按模块或资源类型加前缀,如
USER_、ORDER_、PAYMENT_ - 动词 + 名词:描述错误性质,如
MISSING_REQUIRED_FIELD、EXCEEDED_RATE_LIMIT - 避免数字编码:不要使用纯数字错误码(如
E1001),降低可读性
3.5 常见设计反模式
反模式1:将业务错误码直接映射为HTTP状态码,如用
499 表示"用户余额不足"。这违反了HTTP协议语义,且客户端框架可能无法正确处理非标准状态码。
反模式2:所有错误统一返回
200 OK,仅在响应体中用 code 区分。这导致客户端无法通过状态码快速判断请求是否成功,增加了处理复杂度。
反模式3:错误响应体结构不统一,不同接口返回不同字段名(如一个接口用
error_code,另一个用 errCode)。这破坏了API契约的一致性。
3.6 OpenAPI 中的错误响应定义
在OpenAPI 3.0规范中,应使用 oneOf 或 discriminator 来定义统一的错误响应模型,避免在每个操作中重复定义。
components:
schemas:
ApiError:
type: object
required:
- status
- code
- message
properties:
status:
type: integer
description: "HTTP状态码"
code:
type: string
description: "业务错误码"
message:
type: string
description: "错误描述"
trace_id:
type: string
format: uuid
description: "请求追踪ID"
details:
type: array
items:
$ref: '#/components/schemas/FieldError'
FieldError:
type: object
properties:
field:
type: string
reason:
type: string
rejected_value:
type: string
paths:
/api/users/{id}:
get:
responses:
'200':
description: "成功"
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: "用户不存在"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'default':
description: "其他错误"
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
3.7 错误码生命周期管理
业务错误码是API契约的一部分,应纳入版本管理。建议遵循以下生命周期策略:
3.8 本章小结
- HTTP状态码与业务错误码是两个独立层次的标识,前者描述传输结果,后者描述业务语义。
- 错误响应体应包含
status、code、message三个必填字段,并推荐加入trace_id和details。 - 业务错误码命名应自解释、可搜索、带前缀分组,避免纯数字编码。
- 在OpenAPI文档中,使用
$ref引用统一的ApiErrorschema,确保所有接口的错误响应结构一致。 - 错误码应纳入生命周期管理,废弃时需在文档中标注并保留兼容期。