错误处理 #

一、错误处理概述 #

1.1 错误类型 #

类型 说明 示例
绑定错误 请求参数绑定失败 JSON格式错误
验证错误 数据验证失败 必填字段为空
业务错误 业务逻辑错误 用户已存在
系统错误 系统内部错误 数据库连接失败

1.2 错误处理原则 #

text
1. 明确错误类型
2. 返回友好的错误信息
3. 记录错误日志
4. 不暴露敏感信息

二、绑定错误处理 #

2.1 基本处理 #

go
func main() {
    r := gin.Default()
    
    r.POST("/users", func(c *gin.Context) {
        var user User
        if err := c.ShouldBindJSON(&user); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

2.2 区分错误类型 #

go
func main() {
    r := gin.Default()
    
    r.POST("/users", func(c *gin.Context) {
        var user User
        err := c.ShouldBindJSON(&user)
        
        if err != nil {
            // 检查是否是JSON语法错误
            if syntaxErr, ok := err.(*json.SyntaxError); ok {
                c.JSON(400, gin.H{
                    "code":    400,
                    "message": "JSON格式错误",
                    "offset":  syntaxErr.Offset,
                })
                return
            }
            
            // 检查是否是类型错误
            if typeErr, ok := err.(*json.UnmarshalTypeError); ok {
                c.JSON(400, gin.H{
                    "code":    400,
                    "message": "类型错误",
                    "field":   typeErr.Field,
                    "type":    typeErr.Type.String(),
                })
                return
            }
            
            // 检查是否是验证错误
            var verr validator.ValidationErrors
            if errors.As(err, &verr) {
                c.JSON(400, gin.H{
                    "code":    400,
                    "message": "数据验证失败",
                    "errors":  formatValidationErrors(verr),
                })
                return
            }
            
            // 其他错误
            c.JSON(400, gin.H{
                "code":    400,
                "message": "请求参数错误",
            })
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

func formatValidationErrors(errs validator.ValidationErrors) map[string]string {
    errors := make(map[string]string)
    for _, err := range errs {
        errors[err.Field()] = getErrorMsg(err)
    }
    return errors
}

2.3 统一绑定错误处理 #

go
func bindAndValidate(c *gin.Context, req interface{}) error {
    if err := c.ShouldBindJSON(req); err != nil {
        return NewBindError(err)
    }
    return nil
}

type BindError struct {
    Err error
}

func NewBindError(err error) *BindError {
    return &BindError{Err: err}
}

func (e *BindError) Error() string {
    return e.Err.Error()
}

func (e *BindError) Response() gin.H {
    // 处理验证错误
    var verr validator.ValidationErrors
    if errors.As(e.Err, &verr) {
        errors := make(map[string]string)
        for _, fe := range verr {
            errors[fe.Field()] = getErrorMsg(fe)
        }
        return gin.H{
            "code":    400,
            "message": "数据验证失败",
            "errors":  errors,
        }
    }
    
    // 处理JSON错误
    if _, ok := e.Err.(*json.SyntaxError); ok {
        return gin.H{
            "code":    400,
            "message": "JSON格式错误",
        }
    }
    
    return gin.H{
        "code":    400,
        "message": "请求参数错误",
    }
}

func main() {
    r := gin.Default()
    
    r.POST("/users", func(c *gin.Context) {
        var user User
        if err := bindAndValidate(c, &user); err != nil {
            if bindErr, ok := err.(*BindError); ok {
                c.JSON(400, bindErr.Response())
                return
            }
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

三、业务错误处理 #

3.1 定义业务错误 #

go
type BusinessError struct {
    Code    int
    Message string
}

func NewBusinessError(code int, message string) *BusinessError {
    return &BusinessError{
        Code:    code,
        Message: message,
    }
}

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

// 预定义错误
var (
    ErrUserNotFound     = NewBusinessError(1001, "用户不存在")
    ErrUserExists       = NewBusinessError(1002, "用户已存在")
    ErrInvalidPassword  = NewBusinessError(1003, "密码错误")
    ErrPermissionDenied = NewBusinessError(1004, "权限不足")
)

3.2 使用业务错误 #

go
func main() {
    r := gin.Default()
    
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        user, err := getUserByID(id)
        if err != nil {
            if err == ErrUserNotFound {
                c.JSON(404, gin.H{
                    "code":    err.(*BusinessError).Code,
                    "message": err.Error(),
                })
                return
            }
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

3.3 统一业务错误处理 #

go
func main() {
    r := gin.Default()
    
    // 错误处理中间件
    r.Use(ErrorHandlerMiddleware())
    
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        user, err := getUserByID(id)
        if err != nil {
            c.Error(err) // 记录错误
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

func ErrorHandlerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if len(c.Errors) > 0 {
            err := c.Errors.Last().Err
            
            // 处理业务错误
            if businessErr, ok := err.(*BusinessError); ok {
                c.JSON(400, gin.H{
                    "code":    businessErr.Code,
                    "message": businessErr.Message,
                })
                return
            }
            
            // 处理其他错误
            c.JSON(500, gin.H{
                "code":    500,
                "message": "服务器内部错误",
            })
        }
    }
}

四、统一错误响应 #

4.1 响应结构体 #

go
type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
    Errors  interface{} `json:"errors,omitempty"`
}

func Success(c *gin.Context, data interface{}) {
    c.JSON(200, Response{
        Code:    0,
        Message: "success",
        Data:    data,
    })
}

func Fail(c *gin.Context, code int, message string) {
    c.JSON(200, Response{
        Code:    code,
        Message: message,
    })
}

func FailWithErrors(c *gin.Context, code int, message string, errors interface{}) {
    c.JSON(200, Response{
        Code:    code,
        Message: message,
        Errors:  errors,
    })
}

4.2 使用示例 #

go
func main() {
    r := gin.Default()
    
    r.POST("/users", func(c *gin.Context) {
        var user User
        if err := c.ShouldBindJSON(&user); err != nil {
            var verr validator.ValidationErrors
            if errors.As(err, &verr) {
                FailWithErrors(c, 400, "数据验证失败", formatErrors(verr))
                return
            }
            Fail(c, 400, "请求参数错误")
            return
        }
        
        if err := createUser(&user); err != nil {
            Fail(c, 500, "创建用户失败")
            return
        }
        
        Success(c, user)
    })
    
    r.Run()
}

4.3 错误码设计 #

go
const (
    // 通用错误码 1xxx
    CodeSuccess         = 0
    CodeBadRequest      = 1000
    CodeUnauthorized    = 1001
    CodeForbidden       = 1003
    CodeNotFound        = 1004
    CodeInternalError   = 1005
    
    // 用户相关错误码 2xxx
    CodeUserNotFound    = 2001
    CodeUserExists      = 2002
    CodeInvalidPassword = 2003
    CodeTokenExpired    = 2004
    CodeTokenInvalid    = 2005
    
    // 业务相关错误码 3xxx
    CodeOrderNotFound   = 3001
    CodeOrderExpired    = 3002
)

var errorMessages = map[int]string{
    CodeSuccess:         "成功",
    CodeBadRequest:      "请求参数错误",
    CodeUnauthorized:    "未授权",
    CodeForbidden:       "禁止访问",
    CodeNotFound:        "资源不存在",
    CodeInternalError:   "服务器内部错误",
    CodeUserNotFound:    "用户不存在",
    CodeUserExists:      "用户已存在",
    CodeInvalidPassword: "密码错误",
    CodeTokenExpired:    "Token已过期",
    CodeTokenInvalid:    "Token无效",
}

五、错误日志记录 #

5.1 日志中间件 #

go
func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        
        c.Next()
        
        latency := time.Since(start)
        statusCode := c.Writer.Status()
        
        // 记录错误日志
        if len(c.Errors) > 0 {
            for _, err := range c.Errors {
                log.Printf("[ERROR] %s %s %d %v error: %v",
                    c.Request.Method,
                    path,
                    statusCode,
                    latency,
                    err.Err,
                )
            }
        }
    }
}

5.2 错误恢复 #

go
func RecoveryMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                // 记录堆栈信息
                stack := debug.Stack()
                log.Printf("[PANIC] %v\n%s", err, stack)
                
                // 返回错误响应
                c.AbortWithStatusJSON(500, gin.H{
                    "code":    500,
                    "message": "服务器内部错误",
                })
            }
        }()
        
        c.Next()
    }
}

5.3 结构化日志 #

go
import "go.uber.org/zap"

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    
    r := gin.New()
    
    r.Use(func(c *gin.Context) {
        start := time.Now()
        
        c.Next()
        
        logger.Info("request",
            zap.String("method", c.Request.Method),
            zap.String("path", c.Request.URL.Path),
            zap.Int("status", c.Writer.Status()),
            zap.Duration("latency", time.Since(start)),
            zap.String("client_ip", c.ClientIP()),
            zap.String("errors", c.Errors.String()),
        )
    })
    
    r.Run()
}

六、错误处理最佳实践 #

6.1 错误包装 #

go
func main() {
    r := gin.Default()
    
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        
        user, err := getUserByID(id)
        if err != nil {
            // 包装错误,添加上下文
            c.Error(fmt.Errorf("获取用户失败: %w", err))
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

6.2 错误分类 #

go
type AppError struct {
    Code       int
    Message    string
    StatusCode int
    Err        error
}

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

func (e *AppError) Unwrap() error {
    return e.Err
}

var (
    ErrBadRequest = &AppError{
        Code:       1000,
        Message:    "请求参数错误",
        StatusCode: 400,
    }
    
    ErrNotFound = &AppError{
        Code:       1001,
        Message:    "资源不存在",
        StatusCode: 404,
    }
    
    ErrInternal = &AppError{
        Code:       1002,
        Message:    "服务器内部错误",
        StatusCode: 500,
    }
)

6.3 错误处理中间件 #

go
func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if len(c.Errors) == 0 {
            return
        }
        
        err := c.Errors.Last().Err
        
        var appErr *AppError
        if errors.As(err, &appErr) {
            c.JSON(appErr.StatusCode, gin.H{
                "code":    appErr.Code,
                "message": appErr.Message,
            })
            return
        }
        
        c.JSON(500, gin.H{
            "code":    500,
            "message": "服务器内部错误",
        })
    }
}

七、总结 #

7.1 核心要点 #

要点 说明
绑定错误 区分JSON错误和验证错误
业务错误 自定义错误类型
统一响应 标准化错误格式
错误日志 记录关键错误信息

7.2 最佳实践 #

实践 说明
友好提示 不暴露敏感信息
错误码 使用错误码标识错误
日志记录 记录错误上下文
错误恢复 捕获panic

7.3 下一步 #

现在你已经掌握了错误处理,接下来让我们学习 模板基础,了解Gin的模板渲染!

最后更新:2026-03-28