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 表示“请求已到达并处理”,具体成功与否由业务码决定。
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)
避坑提示:不要使用过长的错误码(如 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 做逻辑分支,只用于展示。这样即使后端修改了文案,前端逻辑也不会受影响。