3. 错误码设计原则:HTTP 状态码类比、业务错误码分类、错误码的层级结构

在构建搜索服务的断言接口契约时,错误码是客户端与服务器之间最直接的沟通语言。一套清晰、可扩展的错误码体系,能显著降低前后端联调成本,提升系统可观测性。本章从 HTTP 状态码的语义映射出发,逐步深入到业务错误码的分类与层级结构设计。

3.1 HTTP 状态码类比:从传输层到业务层

HTTP 状态码定义了请求的传输结果(如 200 成功、404 未找到、500 服务器错误)。但在微服务架构中,仅靠 HTTP 状态码无法表达业务层面的具体失败原因。因此,我们需要在 HTTP 状态码之上构建一套业务错误码体系,形成“传输层 + 业务层”的双层语义。

HTTP 状态码 语义 业务错误码映射原则
2xx 成功 业务正常返回,错误码为 0 或空
4xx 客户端错误 参数校验失败、资源不存在、权限不足等
5xx 服务端错误 内部异常、依赖超时、数据库故障等
设计原则:业务错误码不应直接复用 HTTP 状态码的数字,而应使用独立的编码体系(如 10001、20003),避免与传输层状态码混淆。

3.2 业务错误码分类:四大核心类别

根据搜索服务的典型场景,我们将业务错误码划分为以下四大类别:

  • 参数错误(P类):请求参数格式、类型、范围不合法。例如:缺少必填字段、排序字段不存在、分页参数超出上限。
  • 资源错误(R类):请求的资源不存在、已删除或状态异常。例如:索引不存在、文档 ID 无效、搜索模板未发布。
  • 权限错误(A类):认证失败或授权不足。例如:API Key 无效、IP 白名单未通过、操作超出角色权限。
  • 系统错误(S类):服务内部故障或依赖异常。例如:Elasticsearch 连接超时、内存溢出、配置加载失败。
编码示例:
P-1001: 参数缺失
R-2003: 索引不存在
A-3002: API Key 已过期
S-4005: 搜索后端超时

3.3 错误码的层级结构:三段式编码

为了支持大规模系统的错误码管理,我们推荐采用 三段式层级结构:

错误码 = 服务域(2位) + 错误类别(2位) + 具体错误(4位)
示例:01-P-1001
  ├── 01: 搜索服务域
  ├── P: 参数错误类别
  └── 1001: 具体错误编号

这种结构具备以下优势:

  • 可扩展:每个段独立增长,不会因新增错误而打乱整体编号。
  • 可过滤:监控系统可按服务域或错误类别快速聚合告警。
  • 可读性强:开发人员从错误码前四位即可定位问题范围。
错误码三段式层级结构 服务域 (2位) 错误类别 (2位) 具体错误 (4位) 示例:01-P-1001 01=搜索服务域 | P=参数错误 | 1001=缺少必填字段 优势:可扩展、可过滤、可读性强 每个段独立增长,不因新增错误打乱整体编号

3.4 错误码断言接口契约

在 Go 搜索服务的 API 响应中,错误码应作为结构化字段返回,而非仅依赖 HTTP 状态码。以下是一个标准的错误响应契约:

{
  "code": "01-P-1001",
  "message": "缺少必填字段: query",
  "details": {
    "field": "query",
    "reason": "required"
  },
  "trace_id": "abc-123-def"
}
常见陷阱:不要在错误消息中直接拼接用户输入,防止注入攻击;不要将内部堆栈信息暴露给客户端。

3.5 错误码与 HTTP 状态码的映射矩阵

在实际网关层,需要将业务错误码映射回合适的 HTTP 状态码,以便客户端框架(如浏览器、移动端 SDK)能正确触发重试或错误处理逻辑。

业务错误码类别 示例错误码 映射 HTTP 状态码 说明
参数错误 (P) 01-P-1001 400 Bad Request 客户端请求格式错误
资源错误 (R) 01-R-2003 404 Not Found 请求的资源不存在
权限错误 (A) 01-A-3002 401 Unauthorized 认证失败或凭证过期
系统错误 (S) 01-S-4005 503 Service Unavailable 后端依赖不可用
错误码 → HTTP 状态码映射流程 客户端请求 API 网关 搜索服务 HTTP 请求 RPC 调用 网关映射规则 业务错误码 → HTTP 状态码 P类→400 | R类→404 | A类→401 | S类→503

3.6 错误码管理最佳实践

  • 集中注册:所有错误码在项目初期统一注册到 errors.go 或独立的错误码配置文件,避免散落在业务代码中。
  • 版本化:错误码一旦发布,不应修改其语义;如需废弃,标记为 deprecated 并保留至少一个大版本周期。
  • 文档化:每个错误码附带中文描述、触发条件、建议处理方式,生成 API 文档时自动包含。
  • 可测试:编写单元测试断言每个错误码的生成路径,确保重构时不会遗漏或误改。
Go 代码示例:错误码定义与断言
// errors/codes.go
const (
    ErrMissingField    = "01-P-1001"
    ErrIndexNotFound   = "01-R-2003"
    ErrAPIKeyExpired   = "01-A-3002"
    ErrBackendTimeout  = "01-S-4005"
)

// 测试断言示例
func TestErrorCode(t *testing.T) {
    resp := searchAPI.Search(context.Background(), &SearchRequest{})
    assert.Equal(t, "01-P-1001", resp.Error.Code)
    assert.Contains(t, resp.Error.Message, "query")
}