Gin 的 Context 提供了错误收集功能,可以在处理过程中累积多个错误,最后统一处理。
使用 c.Error() 添加错误:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/error", func(c *gin.Context) {
c.Error(errors.New("第一个错误"))
c.Error(errors.New("第二个错误"))
c.JSON(http.StatusOK, gin.H{"message": "处理完成"})
})
r.Run(":8080")
}
使用 c.Errors 获取所有错误:
r.GET("/errors", func(c *gin.Context) {
c.Error(errors.New("错误A")).SetType(gin.ErrorTypePrivate)
c.Error(errors.New("错误B")).SetType(gin.ErrorTypePublic)
c.Error(errors.New("错误C")).SetType(gin.ErrorTypePublic)
var publicErrors []string
for _, err := range c.Errors {
if err.Type == gin.ErrorTypePublic {
publicErrors = append(publicErrors, err.Error())
}
}
c.JSON(http.StatusBadRequest, gin.H{
"errors": publicErrors,
})
})
Gin 定义了三种错误类型:
const (
ErrorTypePrivate = 1 << iota
ErrorTypePublic
ErrorTypeAny = ErrorTypePrivate | ErrorTypePublic
)
ErrorTypePrivate - 私有错误,不暴露给用户ErrorTypePublic - 公开错误,可以展示给用户ErrorTypeAny - 所有错误r.GET("/by-type", func(c *gin.Context) {
c.Error(errors.New("私有错误")).SetType(gin.ErrorTypePrivate)
c.Error(errors.New("公开错误1")).SetType(gin.ErrorTypePublic)
c.Error(errors.New("公开错误2")).SetType(gin.ErrorTypePublic)
publicErrors := c.Errors.ByType(gin.ErrorTypePublic)
var messages []string
for _, err := range publicErrors {
messages = append(messages, err.Error())
}
c.JSON(http.StatusBadRequest, gin.H{
"errors": messages,
})
})
r.POST("/validate", func(c *gin.Context) {
var user struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"required,gte=0,lte=150"`
}
if err := c.ShouldBindJSON(&user); err != nil {
c.Error(err).SetType(gin.ErrorTypePublic).SetMeta(gin.H{
"field": "request_body",
"code": "invalid_format",
})
}
if user.Name == "admin" {
c.Error(errors.New("用户名已存在")).SetType(gin.ErrorTypePublic).SetMeta(gin.H{
"field": "name",
"code": "duplicate",
})
}
if len(c.Errors) > 0 {
var errors []gin.H
for _, e := range c.Errors.ByType(gin.ErrorTypePublic) {
errors = append(errors, gin.H{
"message": e.Error(),
"meta": e.Meta,
})
}
c.JSON(http.StatusBadRequest, gin.H{"errors": errors})
return
}
c.JSON(http.StatusOK, gin.H{"message": "验证通过"})
})
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
var errors []gin.H
for _, e := range c.Errors.ByType(gin.ErrorTypePublic) {
err := gin.H{
"message": e.Error(),
}
if e.Meta != nil {
err["meta"] = e.Meta
}
errors = append(errors, err)
}
if len(errors) > 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请求处理失败",
"errors": errors,
})
return
}
}
}
}
r.Use(ErrorHandler())
type ValidationError struct {
Field string `json:"field"`
Message string `json:"message"`
}
func ValidateUser(c *gin.Context, user *User) bool {
valid := true
if user.Name == "" {
c.Error(errors.New("用户名不能为空")).
SetType(gin.ErrorTypePublic).
SetMeta(ValidationError{Field: "name", Message: "required"})
valid = false
}
if user.Email == "" {
c.Error(errors.New("邮箱不能为空")).
SetType(gin.ErrorTypePublic).
SetMeta(ValidationError{Field: "email", Message: "required"})
valid = false
}
if user.Age < 0 || user.Age > 150 {
c.Error(errors.New("年龄必须在0-150之间")).
SetType(gin.ErrorTypePublic).
SetMeta(ValidationError{Field: "age", Message: "range"})
valid = false
}
return valid
}
r.POST("/users", func(c *gin.Context) {
var user User
c.ShouldBindJSON(&user)
if !ValidateUser(c, &user) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "创建成功"})
})
r.GET("/last-error", func(c *gin.Context) {
c.Error(errors.New("错误1"))
c.Error(errors.New("错误2"))
c.Error(errors.New("错误3"))
lastError := c.Errors.Last()
c.String(http.StatusOK, "最后一个错误: %s", lastError.Error())
})
r.GET("/clear-errors", func(c *gin.Context) {
c.Error(errors.New("临时错误"))
c.Errors = c.Errors[0:0]
c.JSON(http.StatusOK, gin.H{"message": "错误已清空"})
})
Gin 的错误收集机制提供了一种优雅的方式来累积和处理多个错误。通过 ErrorType 区分公开和私有错误,通过 Meta 附加额外信息。配合中间件可以实现统一的错误响应格式。