在java web开发过程中我们可以使用@ControllerAdvice来捕获全局异常,当程序发生异常的时候,我们拿到对应的异常信息,然后对message进行提取,再通过自定义的数据返回结构返回给客户端。这样就可以达到全局异常的捕获了,非常的方便。
在go-kratos编写的微服务项目中,我们可以看到非常多的err,此时如果我们挨个去改写return 的err信息的话就会非常不方便,同时还会存在不同的err信息,以下我们打印两个error的格式,示例如下:
#第一种错误格式 error: code = 200 reason = 自定义错误 message = 自定义错误 metadata = map[] cause = <nil> #第二种错误格式 error: code = 400 reason = VALIDATOR message = invalid LoginRequest.Username: value length must be 5 runes metadata = map[] cause = invalid LoginRequest.Username: value length must be 5 runes
出现上诉两种情况的原因主要是由于使用了不同的error结构体,但是他们都是Error的子类,所以导致出现这种情况。那么我们如果想要达到类似java web中的全局异常处理这种怎么办呢?方法还是使用中间件来实现,下面我们来演示下:
一、定义统一返回格式
一般来说我们喜欢给客户端返回的格式是:
{
"code": 200,
"msg": "请求成功",
"data": null
}所以这里我们需要定义一个返回的格式,在internal/biz目录下创建一个http的文件夹,再创建一个http.go的文件,内容填充如下:
package http
type BaseResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}二、添加中间件
这里的全局错误处理仅支持http格式,不支持grpc格式。所以我们改写的是internal/server/http.go文件,在NewHTTPServer方法里面注册ErrorEncoder中间件,然后在这里进行拦截改写。
所以这里我们添加的代码如下:
http.ErrorEncoder(
func(writer httpNet.ResponseWriter, request *httpNet.Request, err error) {
log.Infof("拦截到的错误信息是:%s", err.Error())
message := extractMessageFromError(err)
reply := &http2.BaseResponse{
Code: 400,
Msg: message,
Data: nil,
}
codec := encoding.GetCodec("json")
data, _ := codec.Marshal(reply)
writer.Header().Set("Content-Type", "application/json")
writer.Write(data)
}),这里面由于error对外只有一个err.Error()方法供访问,他是一个string类型,所以我们把他做个解析,提取出message字段出来,因此还需要再internal/server/http.go文件的下方添加如下的代码:
func extractMessageFromError(err error) string {
marshal, err2 := json.Marshal(err)
if err2 != nil {
return "系统错误"
}
var em ErrorMessage
e := json.Unmarshal(marshal, &em)
if e != nil {
return "系统错误"
}
return em.Message
}
type ErrorMessage struct {
Message string `json:"message"`
}最后整个internal/server/http.go文件的内容如下:
package server
import (
"encoding/json"
"github.com/go-kratos/kratos/v2/encoding"
"github.com/go-kratos/kratos/v2/middleware/logging"
mmd "github.com/go-kratos/kratos/v2/middleware/metadata"
"github.com/go-kratos/kratos/v2/middleware/validate"
"github.com/go-kratos/swagger-api/openapiv2"
httpNet "net/http"
v2 "user-center/api/account/v1"
v1 "user-center/api/helloworld/v1"
http2 "user-center/internal/biz/http"
"user-center/internal/conf"
"user-center/internal/service"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/middleware/recovery"
"github.com/go-kratos/kratos/v2/transport/http"
)
// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger, account *service.AccountService) *http.Server {
var opts = []http.ServerOption{
http.Middleware(
validate.Validator(),
logging.Server(log.DefaultLogger),
mmd.Client(),
recovery.Recovery(),
),
http.ErrorEncoder(
func(writer httpNet.ResponseWriter, request *httpNet.Request, err error) {
log.Infof("拦截到的错误信息是:%s", err.Error())
message := extractMessageFromError(err)
reply := &http2.BaseResponse{
Code: 400,
Msg: message,
Data: nil,
}
codec := encoding.GetCodec("json")
data, _ := codec.Marshal(reply)
writer.Header().Set("Content-Type", "application/json")
writer.Write(data)
}),
}
if c.Http.Network != "" {
opts = append(opts, http.Network(c.Http.Network))
}
if c.Http.Addr != "" {
opts = append(opts, http.Address(c.Http.Addr))
}
if c.Http.Timeout != nil {
opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
}
srv := http.NewServer(opts...)
handler := openapiv2.NewHandler()
srv.HandlePrefix("/q/", handler)
v1.RegisterGreeterHTTPServer(srv, greeter)
v2.RegisterAccountHTTPServer(srv, account)
return srv
}
func extractMessageFromError(err error) string {
marshal, err2 := json.Marshal(err)
if err2 != nil {
return "系统错误"
}
var em ErrorMessage
e := json.Unmarshal(marshal, &em)
if e != nil {
return "系统错误"
}
return em.Message
}
type ErrorMessage struct {
Message string `json:"message"`
}以上我们就完成了改写错误信息的代码示例。
三、改造返回错误
目前在go-kratos中返回错误主要会分为两种:
第一种:第三方框架直接抛出错误,不会进入到咱们编写的业务代码上。 第二种:咱们的业务代码return err
基于上诉两种,咱们都做一下错误的案例,第一种主要是通过protoc-gen-validate规则来实现,第二种,咱们在login方法的service实现上添加直接返回错误,示例如下:
四、测试运行
接下来把项目启动起来,测试下错误返回:
1、第一种直接出现规则错误:
2、规则校验通过,进入自定义代码中
可以看到错误都被提取出来了,是不是进行了全局统一错误处理。
最后按照惯例,附上本案例的源码,登陆后即可下载。














还没有评论,来说两句吧...