在日常工作中,无论是RPC
调用还是HTTP
调用,异或是业务逻辑的处理,业务异常是很常见的,通常我们会为每个异常情况定义一个唯一的error
。同时当发生异常的时候,我们也需要把异常信息放入到接口的响应信息里面,方便页面上做提示。
定义方式大致如下,当然工作中更常见的是使用专门的protoc或者thrift文件定义,这样对接方都可以依据这个协议文件知道错误的含义,从而做出相应的处理。
go
//业务异常
package bizerr
const (
// 定义可预见的异常
UserNotFound = 10001
PasswrodErr = 10002
)
var resultCodeText = map[int]string{
UserNotFound: "用户不存在",
PasswrodErr: "密码错误",
}
func Message(code int) (string, bool) {
message, ok := resultCodeText[code]
return message, ok
}
错误码这里有5
位
1 | 01 | 01 |
---|---|---|
错误级别,如服务级 | 模块级,如用户模块 | 具体的错误码,如用户名错误 |
工作中每个团队定义方式可能不同,但是大致思路是一致的,即使用分段式,每个分段的位数是固定的,不同的段数的数值代表不同的含义,如下:
-
错误级别:服务级错误用
1
,普通错误用2
,通常是用户的非法操作 -
模块级错误码:
2
位数进行表示,比如01
为用户模块;02
为订单模块 -
具体错误码:
2
位数进行表示,比如01
为手机号不合法;02
为密码输入错误
为了让这些错误信息以及正常情况的返回数据都有统一的结构来管理,我们需要先定义一个统一返回的数据结构体。
以Gin
框架作为举例的话,要想实现统一的异常处理,我们可以借助Gin
提供的中间件功能去在返回数据之前,拦截到出现的错误,在这里重新包装成我们定义的统一结构体。
go
package middleware
import (
"net/http"
"github.com/gin-gonic/gin"
)
// Result 表示统一响应的JSON格式
type Result struct {
Code int `json:"code"` // 状态码
Message string `json:"message"` // 响应消息
Data interface{} `json:"data"` // 响应数据
}
接下来我们要给 Result
提供几个常用的方法,比如出现异常时候需要调用的方法,正常情况下需要调用的方法。
go
func Fail(c *gin.Context, code int, message string) {
c.JSON(code, Result{
Code: code,
Message: message,
Data: nil,
})
c.Abort()
}
//异常信息从定义好的bizerr里面获取
func Fail(c *gin.Context, code int) {
message, _ := bizerr.StatusText(code)
c.JSON(code, Result{
Code: code,
Message: message,
Data: nil,
})
c.Abort()
}
//ok 不需要返回数据 data
func Ok(c *gin.Context, code int) {
c.JSON(code, Result{
Code: code,
Message: message,
Data: nil,
})
}
//接口执行正常 需要返回数据 data
func Ok(c *gin.Context, code int, message string,
data interface{} ) {
c.JSON(code, Result{
Code: code,
Message: message,
Data: data,
})
}
//接口执行正常 需要返回数据 data
func Ok(c *gin.Context, code int,
data interface{} )
{
c.JSON(code, Result{
Code: code,
Message: "ok",
Data: data,
})
}
实现拦截返回结果的中间件
go
func GlobalErrorMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
//先执行请求
c.Next()
// 发生了错误
if len(c.Errors) > 0 {
//获取最后一个error 返回
err := c.Errors.Last()
Fail(c, http.StatusInternalServerError, err.Error())
return
}
}
}
使用中间件
go
func main() {
r := gin.New()
r.Use(middleware.GlobalErrorMiddleware())
r.GET("/test", func(c *gin.Context) {
m := map[string]interface{}{
"lang": "go",
}
data:=Result{
Data: m,
}
// 中途如果有其他错误返回了,会执行中间件封装统一的错误结构给前端,否则执行这个OK方法返回错误结构给前端
// 不管有没有出错,前端收到的数据结构是我们开头统一定义的Result结构
middleawre.Ok(http.Status.OK,data)
})
r.Run(":8080")
}
由此,我们看到了Gin提供的中间件的威力,中间件可以帮助我们做很多中间的事情。
通过定义统一的返回结构,使得我们的所有接口都可以以相同的数据结构展示给需要调用接口的人。大大提高了代码的可读性和维护性。