2、错误码设计原则:HTTP 状态码与业务错误码的区别、错误码分段规则、错误码可读性设计

在构建 GoBFF(Go Backend For Frontend)层时,错误码设计是接口契约中最容易被忽视却又影响最大的环节。一个设计良好的错误码体系,能让前端断言、监控告警、问题排查都变得清晰高效;反之,混乱的错误码会导致前端逻辑臃肿、后端定位困难。本章从三个核心维度展开:HTTP 状态码与业务错误码的职责分离、错误码分段规则、错误码可读性设计。

2.1 HTTP 状态码与业务错误码的区别

很多团队在早期直接将 HTTP 状态码当作业务错误码使用,例如用 400 表示“参数错误”,用 500 表示“服务内部错误”。这种做法在单体应用时代尚可接受,但在微服务 + BFF 架构下会带来严重问题:HTTP 状态码只描述传输层的语义,而业务错误码描述应用层的具体错误原因。

维度 HTTP 状态码 业务错误码
职责 标识请求的传输结果(成功/失败/重定向/服务端错误) 标识业务逻辑的具体失败原因(如余额不足、订单已取消)
粒度 粗粒度,约 60 个标准码 细粒度,可自定义数千个
变更频率 几乎不变 随业务迭代频繁增减
前端处理 决定是否重试、是否跳转登录页 决定具体提示文案、交互逻辑
监控告警 区分 4xx/5xx 比例 区分具体错误类型,如“库存不足”告警阈值
核心原则:HTTP 状态码只使用 200(成功)、400(客户端错误)、401(未认证)、403(无权限)、500(服务端错误)等少数几个。所有业务细节通过响应体中的业务错误码传递。BFF 层永远不要返回 200 以外的状态码来表示业务失败——前端只看 HTTP 200 表示“请求已到达并处理”,具体成功与否由业务码决定。
HTTP 状态码 vs 业务错误码:职责分离示意 HTTP 状态码(传输层) 200 OK 400 Bad Request 401 Unauthorized 403 Forbidden 500 Internal Server Error 仅 5 个状态码覆盖所有场景 业务错误码(应用层) 100001 参数校验失败 100002 用户不存在 200001 余额不足 200002 订单已取消 300001 库存不足 可扩展数千个,精确描述业务失败原因 响应体携带

2.2 错误码分段规则

业务错误码通常是一个整数或字符串,为了便于管理和快速定位,需要按照分段规则进行设计。推荐使用 6 位数字码,分为三段:AB C DDD。

段位 位数 含义 示例
AB 2 位 业务域标识(如 10=用户域、20=订单域、30=库存域) 10xxxx
C 1 位 错误类型(1=参数错误、2=权限错误、3=资源不存在、4=状态冲突、5=系统异常) x1xxxx
DDD 3 位 具体错误序号(从 001 开始递增) xxx001
分段示例:
  • 101001 → 用户域(10) + 参数错误(1) + 用户名为空(001)
  • 202003 → 订单域(20) + 权限错误(2) + 无权限取消订单(003)
  • 304001 → 库存域(30) + 状态冲突(4) + 商品已下架(001)
6 位业务错误码分段结构 AB 业务域标识 10=用户域 / 20=订单域 C 错误类型 1=参数 / 2=权限 / 3=资源 DDD 具体错误序号 001~999 递增 完整示例:10 1 001 → 用户域参数错误:用户名为空 前端可直接根据错误码分段决定提示级别和交互行为
避坑提示:不要使用过长的错误码(如 12 位以上),也不要在错误码中混入字母和特殊符号。纯数字码在日志解析、监控聚合、前端 switch-case 中性能最优。另外,错误码一旦发布,禁止删除或复用——废弃的码应标记为“已废弃”并保留在文档中,避免新旧版本混淆。

2.3 错误码可读性设计

错误码不仅要让机器能解析,更要让人能快速理解。可读性设计包含三个层面:错误消息模板、错误码与消息的映射、国际化支持。

2.3.1 错误消息模板

每个错误码对应一个固定的消息模板,模板中可包含占位符用于动态填充上下文信息。例如:

// GoBFF 错误码定义示例
type BizError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Detail  string `json:"detail,omitempty"`
}

// 错误码常量
const (
    ErrUserNotFound      = 103001 // 用户不存在
    ErrOrderAlreadyPaid  = 204002 // 订单已支付
    ErrInsufficientStock = 305001 // 库存不足
)

// 错误消息映射
var errorMessages = map[int]string{
    ErrUserNotFound:      "用户不存在",
    ErrOrderAlreadyPaid:  "订单 %s 已支付,请勿重复操作",
    ErrInsufficientStock: "商品 %s 库存不足,当前剩余 %d 件",
}

2.3.2 错误码与消息的映射

在 GoBFF 层,建议使用 集中式错误码注册表,所有错误码在启动时加载到内存中,避免每次请求都查询数据库或配置文件。前端 SDK 也可以同步维护一份精简的错误码映射表,用于本地化提示。

错误码 消息模板 前端提示示例
101001 参数 %s 不能为空 “请输入手机号”
103001 用户不存在 “账号未注册,请先注册”
204002 订单 %s 已支付 “该订单已完成支付,无需重复操作”
305001 商品 %s 库存不足 “商品库存不足,请选择其他规格”
500001 系统繁忙,请稍后重试 “服务异常,请稍后再试”

2.3.3 国际化支持

BFF 层通常需要面向多语言前端。错误码本身不携带语言信息,但 message 字段应根据请求头中的 Accept-Language 动态返回对应语言版本。Go 中可以使用 golang.org/x/text 或自定义的 locale 映射表实现。

// 国际化错误消息示例
var errorMessagesI18n = map[int]map[string]string{
    ErrUserNotFound: {
        "zh-CN": "用户不存在",
        "en-US": "User not found",
        "ja-JP": "ユーザーが見つかりません",
    },
    ErrOrderAlreadyPaid: {
        "zh-CN": "订单 %s 已支付,请勿重复操作",
        "en-US": "Order %s has been paid, do not repeat",
        "ja-JP": "注文 %s は既に支払われています",
    },
}
最佳实践:错误码的 message 字段始终返回当前语言版本,同时额外提供一个 code 字段供前端做精确判断。前端不要依赖 message 做逻辑分支,只用于展示。这样即使后端修改了文案,前端逻辑也不会受影响。