数据验证 #

一、验证概述 #

1.1 什么是数据验证 #

数据验证是确保用户输入数据符合预期规则的过程:

text
用户输入 → 验证规则 → 通过/失败 → 处理/错误

1.2 Gin验证机制 #

Gin使用 go-playground/validator 进行数据验证:

go
type User struct {
    Name  string `binding:"required"`     // 必填
    Email string `binding:"required,email"` // 必填且邮箱格式
    Age   int    `binding:"gte=0,lte=150"`  // 0-150之间
}

二、内置验证规则 #

2.1 必填验证 #

go
type Request struct {
    Name     string `binding:"required"`              // 必填
    Email    string `binding:"required,email"`        // 必填且邮箱格式
    Password string `binding:"required,min=6"`        // 必填且最小6字符
    Age      int    `binding:"required,gte=0,lte=150"` // 必填且范围0-150
}

2.2 字符串验证 #

go
type StringValidation struct {
    // 长度验证
    MinLen    string `binding:"min=3"`      // 最小长度3
    MaxLen    string `binding:"max=10"`     // 最大长度10
    Len       string `binding:"len=5"`      // 长度必须为5
    RangeLen  string `binding:"min=3,max=10"` // 长度3-10
    
    // 格式验证
    Email     string `binding:"email"`      // 邮箱格式
    URL       string `binding:"url"`        // URL格式
    URI       string `binding:"uri"`        // URI格式
    
    // 内容验证
    Alpha     string `binding:"alpha"`      // 只含字母
    Alphanum  string `binding:"alphanum"`   // 只含字母和数字
    Numeric   string `binding:"numeric"`    // 数字字符串
    AlphaNum  string `binding:"alphanumunicode"` // 字母数字Unicode
    
    // 前缀后缀
    StartWith string `binding:"startswith=hello"` // 以hello开头
    EndWith   string `binding:"endswith=world"`   // 以world结尾
    Contains  string `binding:"contains=test"`    // 包含test
    
    // 正则
    Regex     string `binding:"regex=^[a-z]+$"`   // 正则匹配
}

2.3 数值验证 #

go
type NumberValidation struct {
    // 比较验证
    Min       int `binding:"min=10"`       // 最小值10
    Max       int `binding:"max=100"`      // 最大值100
    Gte       int `binding:"gte=0"`        // 大于等于0
    Lte       int `binding:"lte=100"`      // 小于等于100
    Gt        int `binding:"gt=0"`         // 大于0
    Lt        int `binding:"lt=100"`       // 小于100
    
    // 范围验证
    Range     int `binding:"gte=0,lte=100"` // 0-100之间
    
    // 相等验证
    Eq        int `binding:"eq=10"`        // 等于10
    Ne        int `binding:"ne=0"`         // 不等于0
    
    // 浮点数
    FloatMin  float64 `binding:"min=0.1"`  // 最小值0.1
    FloatMax  float64 `binding:"max=99.9"` // 最大值99.9
}

2.4 枚举验证 #

go
type EnumValidation struct {
    // 枚举值
    Status    string `binding:"oneof=active inactive pending"`
    Priority  int    `binding:"oneof=1 2 3"`
    Role      string `binding:"oneof=admin user guest"`
    
    // 排除值
    NotStatus string `binding:"ne=deleted"`
}

2.5 日期验证 #

go
type DateValidation struct {
    Date      string `binding:"datetime=2006-01-02"`           // 日期格式
    DateTime  string `binding:"datetime=2006-01-02 15:04:05"` // 日期时间格式
    Time      string `binding:"datetime=15:04:05"`            // 时间格式
}

2.6 可选字段 #

go
type OptionalValidation struct {
    // omitempty: 字段为空时跳过验证
    Name      string `binding:"omitempty,min=3"`    // 可选,但有值时最小长度3
    Email     string `binding:"omitempty,email"`    // 可选,但有值时必须是邮箱格式
    Age       int    `binding:"omitempty,gte=0"`    // 可选,但有值时必须>=0
    Phone     string `binding:"omitempty,len=11"`   // 可选,但有值时长度必须为11
}

2.7 唯一性验证 #

go
type UniqueValidation struct {
    // 切片元素唯一
    Tags      []string `binding:"unique"`           // 元素唯一
    IDs       []int    `binding:"unique"`           // 元素唯一
}

三、结构体验证 #

3.1 嵌套验证 #

go
type Address struct {
    City    string `binding:"required"`
    Street  string `binding:"required"`
    ZipCode string `binding:"required,len=6"`
}

type User struct {
    Name    string  `binding:"required"`
    Email   string  `binding:"required,email"`
    Address Address `binding:"required"` // 嵌套结构体会自动验证
}

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()
}

3.2 切片验证 #

go
type Order struct {
    Items []Item `binding:"required,min=1,dive"` // dive进入切片验证每个元素
}

type Item struct {
    ProductID uint `binding:"required"`
    Quantity  int  `binding:"required,gt=0"`
    Price     float64 `binding:"required,gt=0"`
}

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

3.3 Map验证 #

go
type Config struct {
    Settings map[string]string `binding:"required,dive,keys,min=1,endkeys,required"`
}

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

四、条件验证 #

4.1 required_if #

go
type Request struct {
    Type     string `binding:"required,oneof=personal company"`
    Company  string `binding:"required_if=Type company"` // Type为company时必填
    TaxID    string `binding:"required_if=Type company"` // Type为company时必填
}

4.2 required_without #

go
type Request struct {
    Email    string `binding:"required_without=Phone"`
    Phone    string `binding:"required_without=Email"`
    // Email和Phone至少填一个
}

4.3 required_with #

go
type Request struct {
    Password        string `binding:"required"`
    ConfirmPassword string `binding:"required_with=Password,eqfield=Password"`
    // 有Password时必须填写ConfirmPassword,且两者相等
}

4.4 eqfield/nefield #

go
type PasswordForm struct {
    Password        string `binding:"required,min=6"`
    ConfirmPassword string `binding:"required,eqfield=Password"` // 必须等于Password
    NewPassword     string `binding:"required,nefield=Password"` // 不能等于Password
}

五、验证错误处理 #

5.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()
}

5.2 解析验证错误 #

go
import "github.com/go-playground/validator/v10"

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) {
                errors := make(map[string]string)
                for _, fe := range verr {
                    errors[fe.Field()] = getErrorMsg(fe)
                }
                c.JSON(400, gin.H{"errors": errors})
                return
            }
            
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

func getErrorMsg(fe validator.FieldError) string {
    switch fe.Tag() {
    case "required":
        return "此字段为必填项"
    case "email":
        return "无效的邮箱格式"
    case "min":
        return fmt.Sprintf("最小长度为 %s", fe.Param())
    case "max":
        return fmt.Sprintf("最大长度为 %s", fe.Param())
    case "gte":
        return fmt.Sprintf("最小值为 %s", fe.Param())
    case "lte":
        return fmt.Sprintf("最大值为 %s", fe.Param())
    default:
        return "验证失败"
    }
}

5.3 中文错误消息 #

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) {
                errors := make(map[string]string)
                for _, fe := range verr {
                    errors[getFieldName(fe.Field())] = getChineseErrorMsg(fe)
                }
                c.JSON(400, gin.H{"errors": errors})
                return
            }
            
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, user)
    })
    
    r.Run()
}

func getFieldName(field string) string {
    fieldNames := map[string]string{
        "Name":     "姓名",
        "Email":    "邮箱",
        "Password": "密码",
        "Age":      "年龄",
    }
    return fieldNames[field]
}

func getChineseErrorMsg(fe validator.FieldError) string {
    field := getFieldName(fe.Field())
    
    switch fe.Tag() {
    case "required":
        return fmt.Sprintf("%s不能为空", field)
    case "email":
        return fmt.Sprintf("%s格式不正确", field)
    case "min":
        return fmt.Sprintf("%s最小长度为%s", field, fe.Param())
    case "max":
        return fmt.Sprintf("%s最大长度为%s", field, fe.Param())
    case "gte":
        return fmt.Sprintf("%s不能小于%s", field, fe.Param())
    case "lte":
        return fmt.Sprintf("%s不能大于%s", field, fe.Param())
    default:
        return fmt.Sprintf("%s验证失败", field)
    }
}

六、验证规则汇总 #

6.1 字符串规则 #

规则 说明 示例
required 必填 binding:"required"
min 最小长度 binding:"min=3"
max 最大长度 binding:"max=10"
len 固定长度 binding:"len=11"
email 邮箱格式 binding:"email"
url URL格式 binding:"url"
alpha 只含字母 binding:"alpha"
alphanum 字母数字 binding:"alphanum"
numeric 数字字符串 binding:"numeric"

6.2 数值规则 #

规则 说明 示例
min 最小值 binding:"min=0"
max 最大值 binding:"max=100"
gt 大于 binding:"gt=0"
gte 大于等于 binding:"gte=0"
lt 小于 binding:"lt=100"
lte 小于等于 binding:"lte=100"
eq 等于 binding:"eq=10"
ne 不等于 binding:"ne=0"

6.3 条件规则 #

规则 说明
required_if 条件必填
required_unless 条件非必填
required_with 有其他字段时必填
required_without 无其他字段时必填
eqfield 等于其他字段
nefield 不等于其他字段

七、总结 #

7.1 核心要点 #

要点 说明
binding标签 定义验证规则
内置规则 required、min、max等
嵌套验证 自动验证嵌套结构体
错误处理 解析ValidationErrors

7.2 最佳实践 #

实践 说明
合理验证 验证必要的数据
友好提示 提供中文错误消息
前后端一致 前后端验证规则一致
安全考虑 服务端必须验证

7.3 下一步 #

现在你已经掌握了数据验证,接下来让我们学习 自定义验证,编写自己的验证规则!

最后更新:2026-03-28