错误中间件

错误处理是 Web 应用的重要部分。使用中间件统一处理错误,可以让代码更简洁,响应格式更一致。

基本错误中间件

func errorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        // 检查是否有错误
        if len(c.Errors) > 0 {
            err := c.Errors.Last()
            
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": err.Error(),
            })
        }
    }
}

收集和处理错误

使用 c.Error 收集错误

func someMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        if err := validateSomething(); err != nil {
            // 收集错误但不中断请求
            c.Error(err)
        }
        c.Next()
    }
}

func handler(c *gin.Context) {
    if err := doSomething(); err != nil {
        c.Error(err)
    }
    
    // 继续处理...
    c.JSON(200, gin.H{"message": "ok"})
}

分类处理错误

type AppError struct {
    Code    int
    Message string
}

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

func errorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if len(c.Errors) == 0 {
            return
        }
        
        err := c.Errors.Last().Err
        
        // 根据错误类型返回不同响应
        switch e := err.(type) {
        case *AppError:
            c.JSON(e.Code, gin.H{
                "error": e.Message,
            })
        case validator.ValidationErrors:
            c.JSON(http.StatusBadRequest, gin.H{
                "error": "validation failed",
                "details": formatValidationErrors(e),
            })
        default:
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": "internal server error",
            })
        }
    }
}

完整的错误处理方案

package main

import (
    "errors"
    "net/http"
    
    "github.com/gin-gonic/gin"
)

// 自定义错误类型
type AppError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

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

// 预定义错误
var (
    ErrNotFound     = &AppError{Code: 404, Message: "资源不存在"}
    ErrUnauthorized = &AppError{Code: 401, Message: "未授权"}
    ErrForbidden    = &AppError{Code: 403, Message: "禁止访问"}
    ErrBadRequest   = &AppError{Code: 400, Message: "请求参数错误"}
)

// 错误响应结构
type ErrorResponse struct {
    Error   string `json:"error"`
    Code    int    `json:"code"`
    Details string `json:"details,omitempty"`
}

// 错误处理中间件
func errorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if len(c.Errors) == 0 {
            return
        }
        
        // 获取最后一个错误
        ginErr := c.Errors.Last()
        err := ginErr.Err
        
        // 根据错误类型处理
        var appErr *AppError
        switch e := err.(type) {
        case *AppError:
            appErr = e
        default:
            appErr = &AppError{
                Code:    http.StatusInternalServerError,
                Message: "服务器内部错误",
            }
        }
        
        // 返回错误响应
        c.JSON(appErr.Code, ErrorResponse{
            Error:   appErr.Message,
            Code:    appErr.Code,
            Details: ginErr.Meta,
        })
    }
}

// 辅助函数:快速添加错误
func BadRequest(c *gin.Context, message string) {
    c.Error(&AppError{Code: 400, Message: message})
    c.Abort()
}

func NotFound(c *gin.Context, message string) {
    c.Error(&AppError{Code: 404, Message: message})
    c.Abort()
}

func Unauthorized(c *gin.Context, message string) {
    c.Error(&AppError{Code: 401, Message: message})
    c.Abort()
}

// 使用示例
func main() {
    r := gin.New()
    r.Use(gin.Recovery())
    r.Use(errorHandler())
    
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        user, err := findUser(id)
        if err != nil {
            if errors.Is(err, ErrNotFound) {
                NotFound(c, "用户不存在")
                return
            }
            c.Error(err)
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run(":8080")
}

func findUser(id string) (*User, error) {
    // 模拟查找用户
    return nil, ErrNotFound
}

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

结合 Panic Recovery

func recoveryMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if r := recover(); r != nil {
                // 将 panic 转换为错误
                var err error
                switch v := r.(type) {
                case error:
                    err = v
                case string:
                    err = errors.New(v)
                default:
                    err = errors.New("unknown panic")
                }
                
                c.Error(err).SetMeta(gin.H{"panic": true})
                c.AbortWithStatusJSON(500, gin.H{
                    "error": "internal server error",
                })
            }
        }()
        c.Next()
    }
}

请求级别的错误收集

func handler(c *gin.Context) {
    // 收集多个错误
    var errors []string
    
    if c.Query("name") == "" {
        errors = append(errors, "name is required")
    }
    
    if c.Query("email") == "" {
        errors = append(errors, "email is required")
    }
    
    if len(errors) > 0 {
        for _, e := range errors {
            c.Error(errors.New(e))
        }
        c.JSON(400, gin.H{"errors": errors})
        return
    }
    
    c.JSON(200, gin.H{"message": "ok"})
}

小结

错误中间件的核心功能:

  • 统一收集请求处理过程中的错误
  • 分类处理不同类型的错误
  • 返回一致的错误响应格式
  • 结合 Panic Recovery 处理意外错误

使用错误中间件可以让业务代码更专注于正常流程,错误处理逻辑集中管理,代码更清晰、更易维护。