lego框架参数校验指南

32 阅读6分钟

背景介绍

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字段,但可以传递truefalse

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的错误信息直观。

且这个转化逻辑是否应该显现,便于后续维护?

使用这个快捷转化现有工具是否能兼容?