gin 全局的异常处理
编辑于 2023-07-31 21:50:48 阅读 1889
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())