3. 异常与错误码设计:错误码编码规范、错误码分段规则、错误码与HTTP状态码映射
在企业级系统中,错误码是异常信息的“数字指纹”。一套严谨的错误码体系,能让运维人员、客户端、监控系统在毫秒级定位问题根因,避免“查日志猜错误”的低效模式。本章从编码规范、分段规则、HTTP 状态码映射三个维度,构建可落地的错误码骨架。
3.1 错误码编码规范:三段式结构
推荐采用 “系统标识 + 模块编号 + 错误序号” 的三段式结构,总长度固定为 8 位数字,便于机器解析和人工记忆。
| 段位 | 长度 | 说明 | 示例 |
|---|---|---|---|
| 系统标识 | 2 位 | 区分核心业务域,如 01=交易、02=用户、03=库存 | 01 |
| 模块编号 | 3 位 | 标识子系统或功能模块,如 001=订单、002=支付 | 001 |
| 错误序号 | 3 位 | 具体错误类型,从 001 开始顺序分配 | 015 |
完整示例:01001015 表示“交易系统-订单模块-库存不足”。
编码原则:
- 唯一性:全局不重复,废弃错误码不再复用。
- 可扩展:每个段位预留 20% 的编号空间,避免后期重构。
- 可读性:错误序号按严重程度分组:001-099 为业务警告,100-199 为系统错误,200-299 为外部依赖错误。
3.2 错误码分段规则:按严重程度与来源划分
错误码不仅标识“发生了什么”,还要隐含“有多严重”和“谁导致的”。分段规则将错误码划分为三大区间:
实践建议:在错误码枚举类中,通过注释标注每个错误码的分段归属。例如:
// 业务警告 - 订单模块 - 库存不足。这能让 IDE 在编码阶段就提示严重程度,减少误用。
3.3 错误码与HTTP状态码映射
错误码是业务层面的标识,HTTP 状态码是传输层面的协议。两者需要建立明确的映射关系,避免客户端收到 500 却不知道具体业务含义,或收到 200 但 body 里藏着错误。
| HTTP 状态码 | 含义 | 适用错误码分段 | 典型场景 |
|---|---|---|---|
| 400 Bad Request | 客户端请求格式错误 | 业务警告 (001-099) | 参数校验失败、JSON 解析错误 |
| 401 Unauthorized | 未认证或认证过期 | 业务警告 (001-099) | Token 无效、签名错误 |
| 403 Forbidden | 无权限访问 | 业务警告 (001-099) | 角色不足、IP 白名单拒绝 |
| 404 Not Found | 资源不存在 | 业务警告 (001-099) | 订单号不存在、用户未找到 |
| 409 Conflict | 资源状态冲突 | 业务警告 (001-099) | 重复提交、乐观锁版本冲突 |
| 429 Too Many Requests | 请求频率超限 | 业务警告 (001-099) | API 限流、防刷 |
| 500 Internal Server Error | 服务器内部错误 | 系统错误 (100-199) | 数据库异常、空指针、配置错误 |
| 502 Bad Gateway | 上游服务不可用 | 外部依赖 (200-299) | RPC 超时、下游宕机 |
| 503 Service Unavailable | 服务暂时过载 | 系统错误 (100-199) | 连接池耗尽、CPU 满载 |
| 504 Gateway Timeout | 上游响应超时 | 外部依赖 (200-299) | 第三方接口超时、DNS 解析失败 |
常见反模式:所有错误都返回
200,仅在 body 中放错误码。这会导致网关层无法根据状态码做熔断、限流、重试,且客户端必须解析 body 才能判断成败。正确的做法是:HTTP 状态码反映传输层成败,错误码反映业务层细节。
3.4 工程落地:错误码枚举与响应体结构
在 Java 工程中,推荐使用枚举类集中管理错误码,避免散落在各处魔法数字。
public enum ErrorCode {
// 业务警告 - 订单模块
ORDER_STOCK_INSUFFICIENT("01001015", "库存不足"),
ORDER_PRICE_CHANGED("01001016", "价格已变动"),
// 系统错误 - 订单模块
ORDER_DB_TIMEOUT("01001101", "订单数据库超时"),
ORDER_CACHE_UNAVAILABLE("01001102", "订单缓存不可用"),
// 外部依赖 - 支付模块
PAY_GATEWAY_TIMEOUT("01002201", "支付网关超时"),
PAY_THIRD_PARTY_REJECT("01002202", "第三方支付拒绝");
private final String code;
private final String message;
ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() { return code; }
public String getMessage() { return message; }
}
统一响应体结构示例:
{
"success": false,
"code": "01001015",
"message": "库存不足",
"timestamp": 1712345678000,
"path": "/api/order/create",
"traceId": "a1b2c3d4e5f6"
}
关键设计点:
success字段让客户端无需解析 code 即可判断成败。traceId关联日志链路,便于快速定位完整请求路径。path记录请求入口,方便监控系统按接口聚合错误分布。
3.5 错误码生命周期管理
错误码不是一次性定义就结束的,需要纳入变更管理流程:
- 新增:必须经过架构评审,确认不与其他错误码语义重叠。
- 废弃:标记为
@Deprecated,保留至少两个大版本后再移除。 - 文档化:每个错误码在 Wiki 或 API 文档中记录触发条件、处理建议、示例日志。
- 监控告警:系统错误和外部依赖段的错误码出现时,自动触发 P0/P1 告警。
通过以上规范,错误码从“一串数字”进化为“可编程的异常语义单元”,为后续的监控、告警、自动化运维打下坚实基础。