数据验证 #
一、验证概述 #
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" |
| 邮箱格式 | 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