在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、规则校验通过,进入自定义代码中
可以看到错误都被提取出来了,是不是进行了全局统一错误处理。
最后按照惯例,附上本案例的源码,登陆后即可下载。
还没有评论,来说两句吧...