全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-18 8 分钟 ✍️ juanwangdev

Gin错误处理中间件

错误处理是Web应用稳定性的关键,Gin提供了多种错误处理机制。

Gin内置错误处理

Recovery中间件

Go
func Recovery() HandlerFunc {
    return func(c *Context) {
        defer func() {
            if err := recover(); err != nil {
                // 检查连接断开
                if ne, ok := err.(*net.OpError); ok {
                    if se, ok := ne.Err.(*os.SyscallError); ok {
                        if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
                           strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
                            c.Abort()
                            return
                        }
                    }
                }

                // 记录panic日志
                log.Printf("[Recovery] panic recovered: %v\n%s", err, debug.Stack())

                // 返回500错误
                c.AbortWithStatus(500)
            }
        }()
        c.Next()
    }
}

Context错误收集

Error方法

Go
// 添加错误到Context
func (c *Context) Error(err error) *Error {
    typedError, ok := err.(*Error)
    if !ok {
        typedError = &Error{
            Err:  err,
            Type: ErrorTypePrivate,
        }
    }

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

// Error结构
type Error struct {
    Err  error
    Type ErrorType
    Meta any
}

type ErrorType uint64

const (
    ErrorTypePublic    ErrorType = 1 << 0  // 公开错误
    ErrorTypePrivate   ErrorType = 1 << 1  // 私有错误
    ErrorTypeBinding   ErrorType = 1 << 2  // 绑定错误
)

使用示例

Go
func handler(c *gin.Context) {
    if err := validateInput(c); err != nil {
        // 添加绑定错误
        c.Error(err).SetType(gin.ErrorTypeBinding)
        c.AbortWithStatusJSON(400, gin.H{"error": err.Error()})
        return
    }

    if err := processBusiness(c); err != nil {
        // 添加私有错误(不对外暴露详情)
        c.Error(err).SetType(gin.ErrorTypePrivate)
        c.AbortWithStatusJSON(500, gin.H{"error": "internal error"})
        return
    }
}

自定义错误处理中间件

统一错误响应

Go
func ErrorHandlerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()

        // 处理收集的错误
        if len(c.Errors) > 0 {
            err := c.Errors.Last()

            // 根据错误类型返回不同响应
            switch err.Type {
            case gin.ErrorTypeBinding:
                c.JSON(400, gin.H{
                    "code":    400,
                    "message": "参数验证失败",
                    "detail":  err.Error(),
                })
            case gin.ErrorTypePublic:
                c.JSON(500, gin.H{
                    "code":    500,
                    "message": err.Error(),
                })
            case gin.ErrorTypePrivate:
                // 私有错误不暴露详情
                log.Printf("Private error: %v", err.Err)
                c.JSON(500, gin.H{
                    "code":    500,
                    "message": "服务内部错误",
                })
            }
        }
    }
}

全局异常捕获增强

Go
func EnhancedRecoveryMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                // 获取请求信息
                traceID := c.GetString("traceID")
                path := c.Request.URL.Path
                method := c.Request.Method

                // 记录详细错误信息
                log.Printf("[Panic] traceID=%s path=%s method=%s error=%v stack=%s",
                    traceID, path, method, err, debug.Stack())

                // 发送错误通知
                sendErrorNotification(err)

                // 返回统一错误响应
                if !c.Writer.Written() {
                    c.AbortWithStatusJSON(500, gin.H{
                        "code":    500,
                        "message": "服务内部错误",
                        "traceID": traceID,
                    })
                }
            }
        }()
        c.Next()
    }
}

业务错误处理

自定义错误类型

Go
// 业务错误定义
type BusinessError struct {
    Code    int
    Message string
    Detail  string
}

func (e *BusinessError) Error() string {
    return e.Message
}

// 错误构造
func NewBusinessError(code int, message string) *BusinessError {
    return &BusinessError{
        Code:    code,
        Message: message,
    }
}

// 使用
func handler(c *gin.Context) {
    if err := processOrder(); err != nil {
        if be, ok := err.(*BusinessError); ok {
            c.Error(err).SetMeta(be)
            c.AbortWithStatusJSON(be.Code, gin.H{
                "code":    be.Code,
                "message": be.Message,
            })
            return
        }
        // 其他错误
        c.AbortWithStatusJSON(500, gin.H{"error": "internal error"})
    }
}

错误码标准化

Go
// 错误码定义
const (
    ErrCodeBadRequest    = 400001
    ErrCodeUnauthorized  = 401001
    ErrCodeForbidden     = 403001
    ErrCodeNotFound      = 404001
    ErrCodeInternal      = 500001
)

// 统一响应结构
type ErrorResponse struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    TraceID string `json:"traceID,omitempty"`
}

func SendError(c *gin.Context, code int, message string) {
    traceID := c.GetString("traceID")
    c.JSON(code, ErrorResponse{
        Code:    code,
        Message: message,
        TraceID: traceID,
    })
    c.Abort()
}

错误处理最佳实践

错误类型处理方式响应状态码
参数验证错误gin.ErrorTypeBinding400
认证失败AbortWithStatusJSON401
权限不足AbortWithStatusJSON403
业务逻辑错误自定义BusinessError400/409
内部错误gin.ErrorTypePrivate500
PanicRecovery捕获500

中间件顺序

Go
r := gin.New()

// 1. Recovery必须在最外层
r.Use(EnhancedRecoveryMiddleware())

// 2. 错误响应中间件在最后
r.Use(ErrorHandlerMiddleware())

// 3. 其他中间件
r.Use(LoggerMiddleware())

// 业务路由
r.GET("/api", handler)

注意:Recovery必须作为第一个中间件,ErrorHandler在最后统一处理。

要点总结

  • Recovery捕获panic,防止服务崩溃
  • c.Error()收集错误,支持错误类型标记
  • 错误类型区分公开/私有/绑定,控制对外暴露
  • 自定义BusinessError实现业务错误标准化
  • Recovery最外层,ErrorHandler最后执行

📝 发现内容有误?点击此处直接编辑

← 上一篇 Gin日志记录与性能监控中间件
下一篇 → Gin自定义路由匹配
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库