Gin 捕获异常中间件、封装返回自定义格式数据 | Go主题月

5,384 阅读2分钟

问题

在使用 Go gin 搭建 api 服务,当服务出现异常,或是业务自定义异常,如何区分异常类型?

栗子:

  • 比如:user api ,当添加 user 失败时,返回
{
  code: 10001,
  message: "添加用户失败"
}
  • 比如: order api ,当获取订单详情异常
{
  code: 20001,
  message: "获取订单详情异常"
}

可以结合 Go 语言特性,结合 painc、recover 来处理异常,返回统一数据格式

结合使用 gin 中间件来处理

Gin 框架自带中间件

在启动 Gin 服务,有 log 输出

  • gin 启动已经初始化 Logger 、Recovery 中间件

image-20210330224416122

  • 查看源码

image-20210330224705386

从源码中发现

  • gin 加载中间件使用 Use

    func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {}
    
  • 中间件类型

    type HandlerFunc func(*Context)
    
  • Gin 框架已经使用 painc 、recover 使用了一个 Recovery 中间件

自定义中间件

创建 middleware 目录存放中间件

栗子:在访问系统,需要登录授权,携带 token 才能访问

定义 AuthRequired 中间件

package middleware

import (
	"fmt"

	"github.com/gin-gonic/gin"
)

func AuthRequired() gin.HandlerFunc {
	return func(c *gin.Context) {
		token, _ := c.GetQuery("token")
		fmt.Println("auth check token..." + token)
		c.Next()
	}
}
  • gin 在执行完这个中间,需要调用 Next() 方法,标示执行下一个中间件
  • 如果中间件处理业务出现异常,可以使用 Abort(),终止掉,不再执行后面的逻辑,立即返回

使用中间件

  • 在 main.go 中添加
app.Use(middleware.AuthRequired())

数据格式问题

现在流行前后端分离,那么前端在调用后端接口,返回的格式必须需要统一

在处理业务异常时,主动抛出异常,方便排查

自定义一个全局异常捕获

catch_error.go

package middleware

import (
	"encoding/json"
	"log"
	"moose-go/api"
	"moose-go/common"
	"net/http"

	"github.com/gin-gonic/gin"
)

func CatchError() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				url := c.Request.URL
				method := c.Request.Method
				log.Printf("| url [%s] | method | [%s] | error [%s] |", url, method, err)
				var exception api.Exception
				err := json.Unmarshal([]byte(string(err.(string))), &exception)
				if err != nil {
					common.Failed(c, http.StatusBadRequest, "未知错误,请联系管理员!")
					c.Abort()
					return
				}
				// 没有定义
				errorMessage, ok := api.StatusText(exception.Code)
				if !ok {
					errorMessage = "系统异常"
				}
				common.Failed(c, exception.Code, errorMessage)
				c.Abort()
			}
		}()
		c.Next()
	}
}

exception.go

package api

import (
	"encoding/json"
	"log"
)

type Exception struct {
	Code    int    `json:"code"`
}

func NewException(code int) string {
	e := Exception{Code: code}
	data, err := json.Marshal(e)
	if err != nil {
		log.Panic(err)
	}
	return string(data)
}

result_code.go

package api

const (
	AddUserFail = 10001
)

var resultCodeText = map[int]string{
	AddUserFail: "添加用户失败",
}

func StatusText(code int) (string, bool) {
	message, ok := resultCodeText[code]
	return message, ok
}

测试

  • 判断在添加用户Service AddUser,如果用户名为 test,抛出异常
func (us *UserService) AddUser(userName string) {
	if userName == "test" {
		log.Panic(api.NewException(api.AddUserFail))
		return
	}
	userInfo := model.UserInfo{
		UserName:    userName,
		UserId:      "1",
		AccountId:   "1",
		AccountName: "JiangJing",
		Gender:      "1",
		Phone:       "15798980298",
		Avatar:      "https://www.gitee.com/shizidada",
		Email:       "jiangjing@163,com",
		Address:     "中国",
		Description: "我是江景啊",
	}
	userDao := dao.UserDao{DbEngine: engine.GetOrmEngine()}
	_, err := userDao.InsertUser(&userInfo)
	log.Print(err)
	if err != nil {
		log.Panic(api.NewException(api.AddUserFail))
	}
}

image-20210330232835176