Kratos错误处理必须使用status.Errorf或errors.WithCode包装业务错误,因原生errors.New无状态码和HTTP映射,导致响应始终为200/OK;业务错误码须定义在.proto中并经make api生成,否则IsXXX()判定失效。

Kratos 的错误处理不是靠 http.Error 或手写 JSON 响应,而是统一走 errors.New + status.Errorf + 中间件拦截的链路。不按这个模式,HTTP 返回码永远是 200,gRPC 状态码永远是 OK。
为什么直接 return errors.New("xxx") 不生效
Kratos 默认不拦截裸 error,它只识别符合 status.Error 或实现了 error 接口且含 GRPCStatus() 方法的错误。原生 errors.New 没有状态码、没有 HTTP 映射,会被当成普通字符串塞进响应体,状态码仍是 200。
- HTTP handler 里写
return errors.New("bad request")→ 响应体是"bad request",状态码 200 - gRPC handler 里写
return errors.New("not found")→ 客户端收到codes.Unknown,不是codes.NotFound - 正确做法是用
status.Errorf(codes.NotFound, "user %d not exist", id)
如何定义业务错误码并生成校验方法
Kratos 要求错误码必须来自 .proto 文件里的 enum,不能硬编码数字。工具会自动生成 IsXXX(err) 判定函数,用于中间件或重试逻辑中做类型判断。
- 在
api/v1/error.proto中定义:enum ErrorCode { CODE_OK = 0; CODE_USER_NOT_FOUND = 10001; CODE_INVALID_PARAM = 10002; } - 执行
make api后,生成api/v1/error.pb.go,含IsUserNotFound()等函数 - 在 service 层抛错:
return status.Error(codes.NotFound, "user not found"),或更推荐:return errors.WithCode(errors.New("user not found"), v1.ErrorCode_CODE_USER_NOT_FOUND)
HTTP 错误响应格式怎么统一控制
Kratos 自带 transport/http.Server 会自动把 status.Error 转成 JSON,但字段名和结构由 transport/http.DefaultErrorEncoder 决定,不能直接改返回结构体。
- 默认响应长这样:
{"code":5,"message":"Not Found","details":[]} - 若要改成
{"code":404,"msg":"user not found"},需自定义 encoder:http.Server( http.ErrorEncoder(func(ctx context.Context, err error, w http.ResponseWriter) { code := http.StatusInternalServerError msg := "Internal Server Error" if s, ok := status.FromError(err); ok { code = int(httpCodeFromGRPCCode(s.Code())) msg = s.Message() } w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) json.NewEncoder(w).Encode(map[string]interface{}{"code": code, "msg": msg}) }) ) - 注意:
httpCodeFromGRPCCode需自己实现映射表,gRPCcodes.NotFound→ HTTP 404,codes.InvalidArgument→ 400,等等
中间件里怎么统一捕获并转换错误
所有 handler 抛出的 status.Error 都会经过 transport/http.Server 的 middleware 链。你可以在 recovery 或自定义中间件里做最后兜底,但别覆盖已有的 status.Error —— 它们已经带了语义。
- 错误中间件示例:
func ErrorHandler() http.Middleware { return func(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if r := recover(); r != nil { // panic 场景兜底 w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"code": "500", "msg": "server panic"}) } }() handler.ServeHTTP(w, r) }) } } - 关键点:不要在中间件里对非
status.Error做二次包装,否则 gRPC client 无法用status.FromError解析;也不要试图在中间件里修改status.Error的 message 字段,破坏原始语义
最易忽略的一点:错误码定义必须在 api/ 目录下,且被 make api 正确生成,否则 errors.WithCode 传入的枚举值在运行时是无效整数,不会触发任何校验——它只是个普通 int,不会报错,但后续 IsXXX() 判定永远为 false。











