gin 全局的异常处理

编辑于 2023-07-31 13:50:48 阅读 870

panic类比其他语言中的异常处理

有的人把 Go 中的 panic/recover类比 PHP 中的 throw/try catch,类比 Python 中的raise/try except,类比 Java 中的 throw/try catch

当然也有的人不这么认为。比如:“用户名密码错误”时,在PHP中使用throw语句来抛出异常,大家都觉得很合理,属于“遇到无法处理的错误或异常”

但在Go语言中,"用户名密码错误"这样的预料之中的错误,使用 panic 来处理并不是一个好的选择。panic 适用于无法恢复的严重错误或异常情况,它会立即停止程序执行并触发异常处理机制。而"用户名密码错误"这样的错误通常是一种可预见的情况,并且可以通过合适的错误处理来解决。

虽然我也觉得大量用 panic 不合适,但是给出代码,记录一下探索过程

#创建文件 middlewares/recover.go
package middlewares

func Recover(c *gin.Context) {
	defer func() {
		if r := recover(); r != nil {
			c.JSON(http.StatusOK, gin.H{
				"code": "1",
				"msg":  errorToString(r),
				"data": nil,
			})
			c.Abort()
		}
	}()
	c.Next()
}

func errorToString(r interface{}) string {
	switch v := r.(type) {
	case error:
		return v.Error()
	default:
		return r.(string)
	}
}

使用

router := gin.Default()
//注意 Recover 要尽量放在第一个被加载
router.Use(middlewares.Recover)

注意,子协程里的 panic 拦截不了

Gin 框架内置了 Recovery 中间件,用于处理 panic 异常和未知异常

参考

https://learnku.com/articles/58952

https://blog.csdn.net/u014155085/article/details/106733391

https://www.tizi365.com/question/1611.html

gin 建议的处理方式

gin提供了一个全局的错误列表c.Errors,遇到错误只需调用c.Error把错误对象推到列表,然后在合适的时机读取错误列表,做相应的处理即可。

gin源代码中 Context.go 文件:

type Context struct {
        //......
	// Errors is a list of errors attached to all the handlers/middlewares who used this context.
	Errors errorMsgs
        //......
}

/************************************/
/********* ERROR MANAGEMENT *********/
/************************************/

// Error attaches an error to the current context. The error is pushed to a list of errors.
// It's a good idea to call Error for each error that occurred during the resolution of a request.
// A middleware can be used to collect all the errors and push them to a database together,
// print a log, or append it in the HTTP response.
// Error will panic if err is nil.
func (c *Context) Error(err error) *Error {
	if err == nil {
		panic("err is nil")
	}

	parsedError, ok := err.(*Error)
	if !ok {
		parsedError = &Error{
			Err:  err,
			Type: ErrorTypePrivate,
		}
	}

	c.Errors = append(c.Errors, parsedError)
	return parsedError
}

下面看如何实现

第一步,自定义错误

package errors

type CustomError struct {
	Err int
	Msg string
}

func (e *CustomError) Error() string {
	return e.Msg
}

func New(err int, msg string) *CustomError {
	return &CustomError{
		Err: err,
		Msg: msg,
	}
}

第二步,把自定义错误追加到错误列表

package user

import (
	customError "enterprise-api/app/models/errors"
	"github.com/gin-gonic/gin"
)

func CreateTest(c *gin.Context) {
	c.Error(customError.New(3, "用户名密码错误"))
	return
}

第三步,中间件拦截处理

定义中间件

package middlewares

import (
	"enterprise-api/app/models/errors"
	"github.com/gin-gonic/gin"
	"net/http"
)

func Error() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Next() // 先调用c.Next()执行后面的中间件
		// 所有中间件及router处理完毕后从这里开始执行
		// 检查c.Errors中是否有错误
		for _, errorItem := range c.Errors {
			err := errorItem.Err
			// 若是自定义的错误则将err、msg返回
			if customErr, ok := err.(*errors.CustomError); ok {
				c.JSON(http.StatusOK, gin.H{
					"err": customErr.Err,
					"msg": customErr.Msg,
				})
			} else {
				// 非自定义错误则返回详细错误信息err.Error()
				c.JSON(http.StatusOK, gin.H{
					"err": 500,
					"msg": err.Error(), //服务器异常
				})
			}
			return // 检查一个错误就行
		}
	}
}

使用中间件

router := gin.Default()
router.Use(middlewares.Error())

参考

https://juejin.cn/post/7064770224515448840

广而告之,我的新作品《语音助手》上架Google Play了,欢迎下载体验