3、错误码设计原则:错误码分类(系统级/业务级)、错误码命名规范、错误码与HTTP状态码映射
错误码是分布式系统与API契约中最基础的故障信号载体。一个设计良好的错误码体系,能让调用方在毫秒级内判断故障性质、定位问题模块、决定重试策略或触发降级。本章从分类、命名、映射三个维度,建立错误码设计的工程规范。
3.1 错误码分类:系统级 vs 业务级
错误码按影响范围与修复责任,划分为系统级与业务级两大类。二者在定义、处理方式与生命周期上存在本质差异:
| 维度 | 系统级错误码 | 业务级错误码 |
|---|---|---|
| 定义 | 基础设施、中间件、运行时环境故障 | 业务规则校验、状态冲突、资源约束 |
| 典型场景 | 数据库连接超时、磁盘IO错误、服务熔断 | 订单金额超限、用户权限不足、库存不足 |
| 修复责任 | 运维/平台团队 | 业务开发/产品团队 |
| 重试策略 | 通常可重试(指数退避) | 通常不可重试(需修改请求) |
| 错误码范围 | 000001 – 099999 | 100000 – 999999 |
| 国际化需求 | 低(运维人员阅读) | 高(终端用户可见) |
设计原则:系统级错误码由平台层统一分配,业务级错误码由各业务域自行管理,但必须注册到全局错误码注册表,避免冲突。
3.2 错误码命名规范
错误码采用分层数字编码结构,每段数字携带明确的语义信息。推荐格式:
AB CD EFG
A : 错误级别(1=系统级,2=业务级,3=安全级)
B : 模块标识(0=通用,1=用户,2=订单,3=支付,4=搜索,5=存储)
CD : 错误类别(01=参数校验,02=状态冲突,03=资源缺失,04=超时,05=限流)
EFG: 具体错误序号(001-999,按模块递增分配)
命名示例:
101001— 系统级 · 通用模块 · 参数校验 · 请求体格式错误202001— 业务级 · 订单模块 · 状态冲突 · 订单已支付不可取消303001— 业务级 · 支付模块 · 资源缺失 · 账户余额不足104001— 系统级 · 通用模块 · 超时 · 上游服务响应超时
命名避坑:避免使用纯流水号(如 1001、2002),避免在错误码中嵌入时间戳或随机数,避免使用负数或浮点数。错误码一旦发布,禁止删除或复用,只能废弃(deprecated)。
3.3 错误码与HTTP状态码映射
Go服务通常通过HTTP/HTTPS暴露API,错误码需要与HTTP状态码建立一对多的映射关系。同一HTTP状态码可对应多个业务错误码,但一个错误码只能映射到一个HTTP状态码。
标准映射规则:
| HTTP状态码 | 语义 | 适用错误码范围 | 典型场景 |
|---|---|---|---|
| 400 Bad Request | 请求格式错误 | 101001 – 101999 | JSON解析失败、缺少必填字段 |
| 401 Unauthorized | 身份认证失败 | 301001 – 301999 | Token过期、签名无效 |
| 403 Forbidden | 权限不足 | 302001 – 302999 | 角色无操作权限、IP白名单拒绝 |
| 404 Not Found | 资源不存在 | 203001 – 203999 | 用户ID不存在、商品已下架 |
| 409 Conflict | 资源状态冲突 | 202001 – 202999 | 订单状态不允许操作、版本号冲突 |
| 429 Too Many Requests | 限流触发 | 105001 – 105999 | QPS超限、并发数超限 |
| 500 Internal Server Error | 服务端内部错误 | 104001 – 104999 | 数据库宕机、未捕获panic |
| 503 Service Unavailable | 服务暂时不可用 | 106001 – 106999 | 熔断开启、依赖服务下线 |
映射红线:禁止将业务级错误码映射到 5xx 状态码。5xx 表示服务端故障,如果业务参数错误返回 500,会导致客户端错误地触发重试,可能引发数据重复或状态错乱。
3.4 错误码分类与映射关系图
下图展示了系统级与业务级错误码的划分边界,以及它们与HTTP状态码的映射关系:
3.5 Go代码中的错误码断言实践
在Go接口契约中,错误码通常以整型常量或自定义类型的形式定义,并通过接口返回。调用方通过断言错误码来判断故障类型:
// 定义错误码类型
type ErrCode int
const (
ErrCodeSuccess ErrCode = 0
ErrCodeInvalidParam ErrCode = 101001
ErrCodeOrderStateConflict ErrCode = 202001
ErrCodeInsufficientBalance ErrCode = 303001
ErrCodeUpstreamTimeout ErrCode = 104001
)
// 错误码与HTTP状态码映射
var errCodeToHTTP = map[ErrCode]int{
ErrCodeInvalidParam: 400,
ErrCodeOrderStateConflict: 409,
ErrCodeInsufficientBalance: 403,
ErrCodeUpstreamTimeout: 504,
}
// 断言示例
func handleError(err error) {
if err == nil {
return
}
// 通过类型断言获取错误码
if e, ok := err.(interface{ Code() ErrCode }); ok {
code := e.Code()
httpStatus := errCodeToHTTP[code]
// 根据httpStatus决定响应状态码
_ = httpStatus
}
}
断言优势:通过错误码断言,调用方无需解析错误字符串,无需依赖错误消息的文本格式,实现语言无关的契约校验。这是Go接口契约中推荐的标准做法。