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状态码的映射关系:

错误码分类与HTTP状态码映射 错误码分类与HTTP状态码映射 系统级错误码 (1xxxxx) 基础设施 / 中间件 / 运行时 104001 超时 105001 限流 106001 熔断 101001 参数错 业务级错误码 (2xxxxx) 业务规则 / 状态校验 / 资源约束 202001 状态冲突 203001 资源缺失 201001 金额超限 204001 库存不足 HTTP 状态码映射层 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found 409 Conflict 429 Too Many 映射规则 系统级错误 → 5xx / 429 / 400 | 业务级错误 → 4xx | 安全级错误 → 401 / 403 一个错误码只能映射一个HTTP状态码,一个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接口契约中推荐的标准做法。