在用 Kratos 写后端服务的时候,你可能已经注意到:默认情况下,Kratos 会直接把你的返回值转成 JSON 发出去,错误的话就用它自带的 errors 包处理。这其实挺方便的,但现实项目里往往没那么简单。
比如,前端同学可能会说:"能不能所有接口都返回一样的结构?这样我写拦截器和错误提示就不用猜了。"或者老系统对接时要求:"必须带 success 字段,不然我们解析不了。"
这时候,我们就得自己动手,定制一套统一的响应格式了。
可能是我打开的方式不对,网上没有现成的代码可以复制,自己摸索了一些时间,给后来人省点力气,文章末尾有代码,应该能复制调试使用,希望你不用再像我一样翻半天源码、试各种写法,最后发现原来就差一个
http.ResponseEncoder配置。
想要这样的返回
成功的响应:
json
{
"code": 200,
"message": "OK",
"success": true,
"data": { /* 真正的业务数据 */ }
}
出错的时候也别乱来,统一成这样:
json
{
"code": 400,
"message": "参数错误",
"success": false,
"data": null
}
这样前端一看就知道:哦,success 是 false,那肯定出问题了;message 能直接拿来 toast 提示;data 有东西就是正常数据,没东西就跳过。大家都省心!
Kratos 怎么让我们自定义
Kratos 很贴心,早就留了两个"钩子"给我们:
ResponseEncoder:专门处理正常返回的数据。ErrorEncoder:专门处理出错的情况。
这两个东西本质上就是两个函数,我们可以告诉 Kratos:"以后成功了按这个格式包,失败了按那个格式包",它就会乖乖照做。
而且它们是在创建 HTTP Server 的时候通过配置项传进去的,非常干净,不污染业务逻辑。
具体操作
定义万能包装结构体
在我们的 server 包里,建个结构体,用来装所有返回内容:
go
type httpUnifiedResponse struct {
Code int32 `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
Data any `json:"data"` // any 就是 interface{},啥都能塞
}
成功时的函数(ResponseEncoder)
go
func encodeHTTPResponse(w nethttp.ResponseWriter, r *nethttp.Request, v any) error {
// 如果我们想支持重定向(比如登录跳转),可以加个判断
if rd, ok := v.(interface{ Redirect() (string, int) }); ok {
url, code := rd.Redirect()
nethttp.Redirect(w, r, url, code)
return nil
}
// 正常情况:包一层再返回
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(httpUnifiedResponse{
Code: 200,
Message: "OK",
Success: true,
Data: v, // 把我们 handler 返回的东西塞进 data
})
}
这里的
code: 200是业务意义上的"成功码",不是 HTTP 状态码(虽然碰巧一样)。HTTP 状态码框架默认还是 200,没问题。
出错时的函数(ErrorEncoder)
go
func encodeHTTPError(w nethttp.ResponseWriter, r *nethttp.Request, err error) {
// 把 error 转成 Kratos 标准错误
se := errors.FromError(err)
if se == nil {
// 万一不是标准错误,兜个底
se = errors.New(errors.UnknownCode, errors.UnknownReason, "")
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(int(se.Code)) // 这里才真正设置 HTTP 状态码(比如 400、500)
_ = json.NewEncoder(w).Encode(httpUnifiedResponse{
Code: se.Code, // 用错误里的 code
Message: se.Message, // 错误信息
Success: false,
Data: nil,
})
}
这样一来,哪怕我们代码里 return nil, errors.BadRequest("xxx", "yyy"),前端收到的也不是一堆乱七八糟的文本,而是一个规规矩矩的 JSON!
把两个函数配置进 Server
回到我们创建 HTTP Server 的地方(比如 NewHTTPServer 函数),加上这两行配置:
go
var opts = []http.ServerOption{
http.PathPrefix("/api"),
http.Middleware(
recovery.Recovery(),
logging.Server(logger),
),
http.ResponseEncoder(encodeHTTPResponse), // ← 成功走这里
http.ErrorEncoder(encodeHTTPError), // ← 失败走这里
// ... 其他配置照常
}
搞定!从此以后,这个 Server 下的所有接口,都会自动套上我们定义的格式。
实际例子感受下
假设我们的 handler 是这样的:
go
return &accessv1.LoginResponse{Token: "abc123"}, nil
前端收到的就是:
json
{
"code": 200,
"message": "OK",
"success": true,
"data": {
"token": "abc123"
}
}
如果中间报错了,比如:
go
return nil, errors.BadRequest("USER_NOT_FOUND", "用户不存在")
那前端看到的是:
json
{
"code": 400,
"message": "用户不存在",
"success": false,
"data": null
}
而且浏览器 Network 面板里状态码也是 400,完全符合规范,前端调试起来贼舒服。
完整代码
带注释,放心抄
目录结构
text
server/
├── grpc.go
├── http.go
├── http_encoder.go
└── server.go
1 directory, 4 files
go
package server
import (
"encoding/json"
nethttp "net/http"
"github.com/go-kratos/kratos/v2/errors"
)
type httpUnifiedResponse struct {
Code int32 `json:"code"`
Message string `json:"message"`
Success bool `json:"success"`
Data any `json:"data"`
}
func encodeHTTPResponse(w nethttp.ResponseWriter, r *nethttp.Request, v any) error {
if rd, ok := v.(interface{ Redirect() (string, int) }); ok {
url, code := rd.Redirect()
nethttp.Redirect(w, r, url, code)
return nil
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(httpUnifiedResponse{
Code: 200,
Message: "OK",
Success: true,
Data: v,
})
}
func encodeHTTPError(w nethttp.ResponseWriter, r *nethttp.Request, err error) {
se := errors.FromError(err)
if se == nil {
se = errors.New(errors.UnknownCode, errors.UnknownReason, "")
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(int(se.Code))
_ = json.NewEncoder(w).Encode(httpUnifiedResponse{
Code: se.Code,
Message: se.Message,
Success: false,
Data: nil,
})
}
200 效果:

错误效果:
