看到 validator 咱们第一反应会想起啥?见名知意我就可以知道他是一个验证器,如果用过 gin web 框架的同学,自然是用过 gin 里面的 validator,只不过 gin 中使用的关键字是 binding 去做标识
开门见山
Validator 实际上是一个验证工具,属于 golang 的第三方包,这个包中使用了各种反射技巧来提供了各种校验和约束数据的方式方法,非常实用,常用的有这些:
- 基本的字段长度,大小,范围的约束
- len:约束参数长度
- eq:数值等于参数值
- max:数值小于等于参数值
- min:数值大于等于参数值
- ne:不等于参数值
- gt:大于参数值,gte:大于等于参数值
- lt:小于参数值, lte:小于等于参数值
- oneof:只能是枚举值中的一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,则使用单引号包围。例如:oneof=changsha beijing haerbing
- 是否必选,是否跳过,是否忽略
- **-**:跳过该字段
- | :使用多个约束,只需要满足其中一个,例如:xxx| xxx
- required:必选约束,不能为默认值
- omitempty:如果字段未设置,则忽略它
- 各种格式约束如
- url
- ip、ipv4、ipv6
- uuid
- datetime
- json
- file , 参数必须是一个合法的文件路径
常用的大概有上述这些,我们也不需要去背,只需要知道如何去使用,以及咱们需要处理数据校验的时候,能够想到 validator 库就行了,实在记不起来看官方文档或者看本篇文章的例子就可以了,这个是官网:
validator package - github.com/go-playground/validator/v10 - Go Packages
使用
使用 validator 工具, 自然是为了提高我们的开发效率以及让我们写出来的内容更加优雅和健壮
如果我们自己每一个字段都显示的去校验是否符合我们预期,那么代码大概率会很臃肿,来一个简单的 demo,举个栗子
package main | |
import ( | |
"fmt" | |
"github.com/go-playground/validator/v10" | |
) | |
type Data struct { | |
City string `validate:"min=8,max=15"` | |
Name string `validate:"min=6,max=10"` | |
Addr string `validate:"url"` | |
Age int `validate:"gte=18,lte=100"` | |
Tall int `validate:"required"` | |
IpAddr string `validate:"ipv4"` | |
Email string `validate:"email"` | |
Content string `validate:"json"` | |
CreateTime string `validate:"datetime=2006-01-02"` | |
NewPwd string `validate:"min=8"` | |
RePwd string `validate:"eqfield=NewPwd"` | |
} | |
func main() { | |
// 示例 , 基本使用介绍 | |
validate := validator.New() | |
demo1 := Data{ | |
City: "changsha11111111111111", | |
Name: "xiaozhu", | |
Addr: "xxxxxxxxx", | |
Age: 25, | |
Tall: 185, | |
IpAddr: "xxxxxxxxxxx", | |
Email: "helloworld@qq.com", | |
Content: "{"name":"xiaozhu"}", | |
CreateTime: "xxxxx2006-03-02", | |
NewPwd: "12345", | |
RePwd: "123456789xxxxx", | |
} | |
err := validate.Struct(demo1) | |
if err == nil { | |
fmt.Println("params check success") | |
return | |
} | |
invalid, ok := err.(*validator.InvalidValidationError) | |
if ok { | |
fmt.Println("param invalid : ", invalid) | |
return | |
} | |
valiErrs := err.(validator.ValidationErrors) | |
for _, valiErr := range valiErrs { | |
fmt.Println(valiErr) | |
} | |
} |
此处我们可以看到我们在 Data 数据结构中,对其成员进行了不同的约束,相信通过 xdm 看到 Data 结构中的 validate 标识后面的约束,就知道响应字段的约束是啥意思了
例如
Age int `validate:"gte=18,lte=100"`
约束 Age 这个字段,需要满足 大于等于 18 ,小于等于 100 的范围
RePwd string `validate:"eqfield=NewPwd"`
RePwd 字段,需要和 NewPwd
字段相等 ,这个是用 eqfield
做标识的
关于 xxfield 的跨字段约束的相关标识可以查看官网的此处
这里是 valiator 能支持的所有类型,从字段内容,网络方面,字符串,数据结构,比较的字符,其他的标识
另外关于邮箱约束的:
Email string `validate:"email"`
Email
字段,必须是 email 格式的,才能够检验通过
如上,每一个字段,如果需要校验的,校验失败,我们也可以全部打印出来
目前在 validator 中,处理错误信息,分为 2 种错误的情况:
- InvalidValidationError
咱们将我们的 err 转换成 InvalidValidationError ,表示输入参数错误
- ValidationErrors:字段违反约束,错误信息如下
咱们将我们的 err 转换成 ValidationErrors,这是一个切片,所以咱们可以遍历输出,这个是表示不符合约束字符的有错误原因
validator.ValidationErrors 是一个 FieldError 类型的切片
type ValidationErrors []FieldError
FieldError
中包含了关于 error 的全部信息,我们可以调用 FieldError
里面的成员方法进行输出即可
type FieldError interface { | |
Tag() string | |
ActualTag() string | |
Namespace() string | |
StructNamespace() string | |
Field() string | |
StructField() string | |
Value() interface{} | |
Param() string | |
Kind() reflect.Kind | |
Type() reflect.Type | |
Translate(ut ut.Translator) string | |
Error() string | |
} |
关于其他标识的使用就不过多赘述了,使用方式都大同小异,咱们可以参考上述的 demo 即可
自定义约束
当然,如果认为官方提供的支持的标识还不能满足我们的要求,那么我们也是可以自定义咱们的标识的,例如,咱们要定义的标识是 happyhead
,含义就是,咱们定义的字符串,必须是以 happy 开头的,否则就校验不通过
此时咱们就需要使用到 validator 包中的 RegisterValidation 方法,再按照这个方法,提供一个校验实际参数的回调函数即可:
我们就可以这样来写
查看实际效果如下:
demo1 validate failed : Key: 'RegisterFormat.Name' Error:Field validation for 'Name' failed on the 'happyhead' tag | |
demo2 validate success ... |
至此,咱们将 validator 包中的特殊约束,格式约束,错误处理,范围约束,字符串约束,以及自定义约束都简单过了一下,这些东西不需要朗读和背诵,只需要咱们知道有他,需要的时候,能够找到,能够迅速使用起来即可
当然,如果想研究他的实现原理的,可以好好看看 validator 源码包以及官方文档,还是非常有意思的