Kratos通过.proto中ErrorReason枚举及(errors.code)标签实现错误码映射,生成Go代码自动转换为带HTTP状态码的error,业务层用errors.Is判断;HTTP响应体{code,message,reason}由transport层自动组装,gRPC端则映射为status.Code和Details。

proto 里定义 ErrorReason 枚举并启用 errors.code
Kratos 的统一返回格式不是靠中间件或全局 wrapper 实现的,而是由 proto 编译时注入的错误映射机制驱动。关键动作是在 .proto 文件中声明 ErrorReason 枚举,并为每个成员打上 (errors.code) 标签:
enum ErrorReason {
SUCCESS = 0 [(errors.code) = 200];
USER_NOT_FOUND = 1 [(errors.code) = 404];
INVALID_PARAM = 2 [(errors.code) = 400];
INTERNAL_ERROR = 3 [(errors.code) = 500];
}
生成的 Go 代码会自动把 USER_NOT_FOUND 转成带 HTTP 状态码 404 的 error 实例,业务层只需返回 errors.New("xxx") 或调用 v1.ErrorReason_USER_NOT_FOUND 对应的 error 工厂函数。
- 不加
(errors.code)标签 → 框架默认返回 500,且无 status_code 字段 - 标签值非标准 HTTP 码(如 999)→ 返回仍为 500,框架会忽略非法值
- 同一个 error code 被多个枚举成员使用 → 运行时报 panic,生成阶段不报错
HTTP 响应体结构由 transport 层自动组装
你不需要手写 JSON 封装逻辑。Kratos 的 http.Server 在拦截到 error 后,会根据 errors.Is(err, v1.ErrorReason_USER_NOT_FOUND) 判断类型,再查表取出对应 status code 和 message,最终输出类似这样的响应体:
{ "code": 404, "message": "user not found", "reason": "USER_NOT_FOUND" }
这个结构是硬编码在框架里的,不可配置。如果想加 request_id 或 timestamp,得靠自定义 middleware 插入 context 并修改 response writer,但要注意:不能覆盖已写入的 status code 和 body。
- 业务 handler 返回
nil→ 自动设 status 200,body 为原始 reply 结构(无外层包装) - handler 返回非 proto 定义的 error(如
fmt.Errorf)→ 默认 status 500,message是 error.Error(),reason为空字符串 - 用
http.Error()手动写响应 → 会绕过 Kratos 的统一格式,trace_id、metrics 等元信息也会丢失
gRPC 端不走 JSON 包装,但 status code 和 details 仍受控
gRPC 协议本身不序列化成 JSON,所以没有 “统一响应体” 这一说。但 Kratos 仍能保证错误语义一致:ErrorReason 枚举会被映射为 gRPC 的 status.Code 和 status.Details。客户端用 status.FromError(err) 可拿到 code 和原始 proto error message。
- HTTP 和 gRPC 共享同一套
ErrorReason定义 → 前后端不用维护两套错误码表 - gRPC 客户端收到
CodeNotFound→ 对应 proto 中USER_NOT_FOUND成员,而非字面意思的 404 - 若 proto 里没定义某个 error,又想在 gRPC 中返回特定 code → 只能用
status.Error(codes.NotFound, "xxx"),但这样 HTTP 端就无法复用该语义
别动 internal/server 下的 interceptor 注册逻辑
统一格式生效的前提是 Kratos 的 error interceptor 被正确挂载。这个逻辑藏在 internal/server/http.go 和 internal/server/grpc.go 里,典型写法是:
srv := http.NewServer(http.Address(":8000"))
srv.Use(middleware.Recovery(), middleware.Transport(), middleware.Errors())
其中 middleware.Errors() 是关键。新手常删掉这行,或把它移到 Use() 链末尾 —— 后者会导致其他中间件(比如 auth)抛出的 error 不被拦截,直接 fallback 到 net/http 默认 500 页面。
- 删掉
middleware.Errors()→ 所有 error 都变成 500 + 空 body 或 panic 日志 - 把它放在
middleware.Transport()之前 → trace_id、span_id 等字段不会写入 error 响应 - 自定义 error middleware 时,必须调用
next()后再检查 err,不能提前 return
统一格式的边界很明确:它只接管 proto 定义的 error 和 handler 显式返回的 error。任何绕过框架 error 体系的操作(比如直接 writeHeader、panic、第三方库 error)都会导致格式断裂。这点在联调和日志排查时最容易被忽略。











