背景介绍
lego框架提供了强大的参数校验功能,主要依赖于go-playground/validator/v10库。本文提供一些常用的参数检验例子。
参数校验基础
在结构体字段上使用binding标签可以定义校验规则:
type Login struct {
User string `json:"user" binding:"required"`
Password string `json:"password" binding:"required"`
}
常用校验标签
| 标签 | 功能描述 |
|---|---|
required | 字段必须设置,不能为零值 |
min/max | 数值或字符串的最小/最大值 |
len | 检查字符串的固定长度 |
gte/lte | 检查数值是否大于等于或小于等于某个值 |
email | 检查是否是有效的电子邮件地址 |
oneof=value1 value2 | 字段值必须是指定值之一 |
eqfield | 等于其它字段,如密码和确认密码一致 |
dive | 用于嵌套结构体或集合类型,递归校验内部字段 |
1. required - 必填字段
type RequiredExample struct {
Username string `json:"username" binding:"required"` // 必须提供username字段
Age int `json:"age" binding:"required"` // 必须提供age字段
}
测试用例:
// 有效请求
{
"username": "john",
"age": 25
}
// 无效请求 - 缺少username
{
"age": 25
}
⚠️⚠️⚠️如果前端未传递某字段,Go语言本身的特性,未显式初始化的类型字段会被自动赋值为零值。
需要注意两种形式的区别,拿bool类型为例子,其他数据类型类似
1. 非指针形式(bool)
type ChannelReplyResp struct {
Checked bool `json:"checked" binding:"required"` // 不能为空
Templates []ChatBotTemplateResp `json:"templates" binding:"max=3,dive"` // 最多传3个
}
特点:
required标签会检查字段是否存在且值不为零值- 对于bool类型,零值是
false - 这意味着前端必须传递
checked字段且值必须为true,传递false或者不传递会导致校验失败
2. 指针形式(*bool)
type ChannelReplyResp struct {
Checked *bool `json:"checked" binding:"required"` // 不能为空
Templates []ChatBotTemplateResp `json:"templates" binding:"max=3,dive"` // 最多传3个
}
特点:
required标签会检查指针是否为nil(即字段是否被传递)- 可以区分"未传递"、"传递了false"和"传递了true"三种情况
- 前端必须传递
checked字段,但可以传递true或false
2. min/max - 数值或字符串的最小/最大值
type MinMaxExample struct {
Name string `json:"name" binding:"min=3,max=10"` // 字符串长度3-10
Score float64 `json:"score" binding:"min=0,max=100"` // 分数0-100
PageSize int `json:"pageSize" binding:"min=1,max=50"` // 每页数量1-50
}
测试用例:
// 有效请求
{
"name": "john",
"score": 85.5,
"pageSize": 10
}
// 无效请求 - name太短
{
"name": "jo",
"score": 85.5,
"pageSize": 10
}
3. len - 固定长度字符串
type LenExample struct {
Phone string `json:"phone" binding:"len=11"` // 手机号必须11位
PostalCode string `json:"postalCode" binding:"len=6"` // 邮编必须6位
}
测试用例:
// 有效请求
{
"phone": "13800138000",
"postalCode": "100000"
}
// 无效请求 - 邮编长度不对
{
"phone": "13800138000",
"postalCode": "10000"
}
4. gte/lte - 大于等于/小于等于
type GteLteExample struct {
Age int `json:"age" binding:"gte=18,lte=60"` // 年龄18-60岁
Quantity int `json:"quantity" binding:"gte=1,lte=100"` // 数量1-100
Username string `json:"username" binding:"gte=6,lte=20"` // 用户名长度6-20个字符
}
测试用例:
// 有效请求
{
"age": 25,
"quantity": 10,
"username": "john_doe123" // 长度11
}
// 无效请求 - 年龄太小
{
"age": 16,
"quantity": 10,
"username": "john", // 长度4 < 6
}
对比gt/lt, gt/le是大于(表示严格大于,不包括本身)/小于(表示严格小于,不包括本身)
5. oneof - 指定值之一
type OneOfExample struct {
Gender string `json:"gender" binding:"oneof=male female other"` // 必须是这三个值之一
Status string `json:"status" binding:"oneof=active inactive banned"` // 状态必须是这三个之一
}
测试用例:
// 有效请求
{
"gender": "male",
"status": "active"
}
// 无效请求 - 性别值不在选项中
{
"gender": "unknown",
"status": "active"
}
6. eqfield - 等于其他字段
type EqFieldExample struct {
Password string `json:"password" binding:"required"`
ConfirmPassword string `json:"confirmPassword" binding:"required,eqfield=Password"` // 必须等于Password字段
}
测试用例:
// 有效请求
{
"password": "123456",
"confirmPassword": "123456"
}
// 无效请求 - 两次密码不一致
{
"password": "123456",
"confirmPassword": "654321"
}
7. dive - 嵌套结构体或集合校验
⚠️参数校验默认不会递归校验嵌套数据结构或集合内部进行校验,因此再以下场景中需要检验内部数据结构必须添加dive
1. 切片/数组元素校验
当需要对切片或数组中的每个元素进行验证时使用dive。
type User struct {
Emails []string `json:"emails" binding:"required,dive,email"` // 必须提供且每个元素都必须是email格式
}
2. Map的键值对校验
对map类型的键和值分别进行验证。
type Config struct {
Settings map[string]string `json:"settings" binding:"dive,keys,min=3,endkeys,required,max=100"`
// 键名至少3个字符,值必须存在且不超过100个字符
}
3. 嵌套结构体验证
验证嵌套结构体内部的字段。
type Address struct {
Street string `json:"street" binding:"required"`
City string `json:"city" binding:"required"`
}
type User struct {
Name string `json:"name" binding:"required"`
Address Address `json:"address" binding:"required,dive"` // 必须提供且验证内部字段
Friends []string `json:"friends" binding:"dive,alpha"` // 每个朋友名必须是字母
}
4. 多维数组/切片验证
对多维数组的每一层进行验证。
type Matrix struct {
Grid [][]int `json:"grid" binding:"dive,dive,gt=0"` // 每个数字必须大于0
}
一些类型快捷转换方式(待商榷讨论:是否能直接这样使用?)
场景一:
在Go语言中,当需要处理前端因JavaScript数字精度限制(安全范围为±9007199254740991)而传递的字符串类型大整数时,可以通过JSON标签的string选项实现自动类型转换。
type Request struct {
ContentId *int64 `json:"contentId,string"` // 自动将字符串转为int64
UserId int64 `json:"userId,string"` // 非指针类型同样适用
}
func main() {
// 示例1:JSON中contentId为字符串
jsonData1 := `{"contentId": "9223372036854775807", "userId": "12345"}`
var req1 Request
if err := json.Unmarshal([]byte(jsonData1), &req1); err != nil {
fmt.Println("解析错误:", err)
} else {
fmt.Printf("ContentId: %d, UserId: %d\n", *req1.ContentId, req1.UserId)
// 输出: ContentId: 9223372036854775807, UserId: 12345
}
// 示例2:JSON中contentId为数值(兼容处理)
jsonData2 := `{"contentId": 9223372036854775807, "userId": 12345}`
var req2 Request
if err := json.Unmarshal([]byte(jsonData2), &req2); err == nil {
fmt.Printf("ContentId: %d\n", *req2.ContentId) // 输出同上
}
}
自动类型转换
在结构体字段的JSON标签中添加,string后缀,指示JSON包将字符串类型的数值自动转换为目标类型(如int64)。无需手动调用strconv.ParseInt,减少冗余代码。
兼容性设计
该方式同时兼容JSON中数值和字符串两种格式的输入(如"contentId": 123和"contentId": "123"),避免因前端传参格式不一致导致的解析失败
⚠️
错误处理局限
若字符串格式非法(如非数字字符、空字符串),解析错误可能被框架捕获为通用错误,需通过日志或调试确认具体原因,不如手动strconv.ParseInt的错误信息直观。
且这个转化逻辑是否应该显现,便于后续维护?
使用这个快捷转化现有工具是否能兼容?