Gin 使用 validator 实现参数校验

2023-10-12 16:01:13 浏览数 (2)

0.前言

大咖好呀,我是恋喵大鲤鱼。

编写接口时,你还在为接口入参编写类似如下繁琐的校验逻辑吗?

代码语言:javascript复制
type MyAPIReq struct {
	a int
	b string
	c []float64
	...
}

func MyAPI(req *MyAPIReq) error {
	if req.a == 0 {
		return errors.New("Bad request parameter, a is nil.")
	}
	if b != "foo" && b != "bar" && b !="baz" {
		return errors.New("Bad request parameter, b must be foo, bar or baz.")
	}
	if len(c) == 0 {
		return errors.New("Bad request parameter, c length is zero.")
	}
	...
}

实现接口时,一个好的习惯是基于不信任原则,对入参进行合法性校验。

第一时间,你想到的便是自己手写代码完成接口入参校验。但是手写这些重复繁琐的校验代码,不仅低效,而且还使代码变得臃肿难看,不够美观。

这种所有开发人员都要做的事情应该被收敛到一个地方完成,避免重复开发。这便是参数验证器要做的事情。

1.可用的参数验证器

在 Go 语言中,有一些流行且常用的验证库可以帮助你进行数据验证和验证规则的定义。以下是几个常用的 Go 验证器库:

  • go-playground/validator

这是一个功能强大且广泛使用的验证器库,支持结构体字段级别的验证、自定义验证规则和本地化错误消息等。它提供了丰富的验证规则和选项,可以与结构体标签一起使用。GitHub 仓库。

  • asaskevich/govalidator

这是一个轻量级的验证器库,专注于字符串验证和格式验证。它提供了一系列函数来验证字符串的长度、格式、邮箱、URL等。虽然它主要用于字符串验证,但也支持其他类型的验证。GitHub 仓库。

从 Github 仓库提交记录来看,该库已经多年没有更新迭代了,不建议使用。

  • go-ozzo/ozzo-validation

这是另一个流行的验证器库,提供了简洁且易于使用的 API。它支持结构体字段级别的验证、自定义验证规则和本地化错误消息等功能。该库还提供了一些方便的验证规则和错误处理功能。GitHub 仓库。

从 Github 仓库提交记录来看,该库已经多年没有更新迭代了,不建议使用。

2.Gin 使用 validator

如果使用 Gin 作为 Web 框架开发 Web 应用,那么 Gin 已经集成了 go-playground/validator。

Gin 框架使用 validator 在模型绑定时进行参数校验,目前已经支持 go-playground/validator/v10 了。所以我们不用自己手写参数校验的代码,只需要在定义结构体时使用 binding tag 标识相关校验规则,就可以进行参数校验了,很方便。

以前文手写参数校验代码为例,我们使用 validator 在定义 struct 时添加相关的 tag 便可自动完成校验。

代码语言:javascript复制
type MyAPIReq struct {
	a int `binding:"required"`
	b string `binding:"required,oneof=foo bar baz"`
	c []float64 `binding:"required,gt=0"`
	...
}

required 表示字段不能为对应类型的零值。

oneof 用于限制字段取值必须是指定的多个值中的一个,多个值之间使用空格分隔。如果字符串本身包含空格,可以使用单引号括起来。

代码语言:javascript复制
oneof=red green
oneof='red green' 'blue yellow'
oneof=5 7 9

gt 表示 greater than 大于。对于数字,这将确保值大于给定的值。对于字符串,它检查字符串长度是否大于给定的值。对于切片、数组和映射,验证元素的数量。

常用的 tag 还有:

代码语言:javascript复制
lte:小于等于参数值,"lte=3" (小于等于3)
gte:大于等于参数值,"lte=120,gte=0" (大于等于0小于等于120)
lt:小于参数值,"lt=3" (小于3)
ne:不等于,"ne=2" (不等于2)
len:等于参数值,"len=2"
max:最大值,小于等于参数值,"max=20" (小于等于20)
min:最小值,大于等于参数值,"min=2,max=20" (大于等于2小于等于20)

可以查看 validator 文档查看其支持的所有 tag。

3.dive 标签的使用

先看一个实际的请求结构体。

代码语言:javascript复制
type PostAttributeValuesReq struct {
	// 创建者ID
	CreatorID string `binding:"required"`
	// 属性值数组
	Values []struct {
		// 属性值
		Value string `binding:"required"`
		// 天数(计费模式使用)
		Days uint
	} `binding:"required,gt=0"`
}

假设使用 JSON 传参,本以为下面的入参是可以被识别出来,但实际上并没有。

代码语言:javascript复制
{
    "creatorID":"dablelv",
    "values":[{}]
}

也就是说切片元素 struct 的字段的 required 并没有生效。

查看官方文档发现有一个 dive 标签,这告诉验证器深入到切片、数组或映射中,并使用元素的验证标签来验证切片、数组或映射的元素。

代码语言:javascript复制
type PostAttributeValuesReq struct {
	// 创建者ID
	CreatorID string `binding:"required"`
	// 属性值数组
	Values []struct {
		// 属性值
		Value string `binding:"required"`
		// 天数(计费模式使用)
		Days uint
	} `binding:"required,gt=0,dive"`
}

那么在绑定模型时将会按照预期报如下错误:

代码语言:javascript复制
"Key: 'PostAttributeValuesReq.Values[0].Value' Error:Field validation for 'Value' failed on the 'required' tag"

参考文献

Model binding and validation - Gin Web Framework How to validate that an inner slice of structs conforms to custom validation logic 学会gin参数校验之validator库,看这一篇就足够了 - 稀土掘金

0 人点赞