错误处理是 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(),
})
}
}
}
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"`
}
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"})
}
错误中间件的核心功能:
使用错误中间件可以让业务代码更专注于正常流程,错误处理逻辑集中管理,代码更清晰、更易维护。