目录
文章目录
- 目录
- Validator
- Quick start
- 约束类型
-
- 特殊约束
- 格式约束
- 数据结构类型约束
- 范围约束
- 字符串约束
- 唯一性约束
- 跨字段约束
- 自定义约束
- 错误处理
- 中文错误信息
- 参考文档
Validator
Validator 是一个 Golang 的第三方库,用于对数据进行校验,常用于 API 的开发中,对客户端发出的请求数据进行严格校验,防止恶意请求。
- Github:https://github.com/go-playground/validator
安装:
go get gopkg.in/go-playground/validator.v10
使用:
import "github.com/go-playground/validator/v10"
NOTE:validator 当前最新的版本是 v10,各个版本之间有一些差异,在使用的时候要注意区分。
Quick start
validator 应用了 Golang 的 Struct Tag 和 Reflect 机制,基本思想是:在 Struct Tag 中为不同的字段定义各自类型的约束,然后通过 Reflect 获取这些约束的类型信息并在校验器中进行数据校验。如下例:
package main
import (
"fmt"
"gopkg.in/go-playground/validator.v10"
)
type User struct {
Name string `validate:"min=6,max=10"`
Age int `validate:"min=1,max=100"`
}
func main() {
validate := validator.New()
u1 := User{ Name: "fanguiju", Age: 18}
err := validate.Struct(u1)
fmt.Println(err)
u2 := User{ Name: "fgj", Age: 101}
err = validate.Struct(u2)
fmt.Println(err)
}
上述例子中,我们定义了结构体 User,有 Name 和 Age 成员。通过 validator 的 min 和 max 约束,分别约束了 Name 的字符串长度 [6, 10],Age 的数字范围为 [1,100]。
使用 validator 的第一步需要 New(构造)一个 “校验器”,然后调用其 Struct 方法对结构体实例进行校验。如果满足约束则返回 nil,否则返回相应的错误信息。
约束类型
特殊约束
- -:跳过该字段,不检验;
- |:使用多个约束,只需要满足其中一个,例如:rgb | rgba;
- required:必选约束,不能为默认值;
- omitempty:如果字段未设置,则忽略它。
格式约束
- email:限制字段必须是邮件格式。
- url:限制字段必须是 URL 格式。
- uri:限制字段必须是 URI 格式。
- ip、ipv4、ipv6:限制字段必须是 IP 格式。
- uuid:限制字段必须是 UUID 格式。
- datetime:限制字段必须是 Datatime 格式。
- json:字符串值是否为有效的 JSON。
- file:符串值是否包含有效的文件路径,以及该文件是否存在于计算机上。
数据结构类型约束
- structonly:仅验证结构体,不验证任何结构体字段。Struct:
validate:"structonly"
。 - nostructlevel:不运行任何结构体级别的验证。Struct:
validate:"nostructlevel"
。 - dive:向下延伸验证,多层向下需要多个 dive 标记。
[][]string
:validate:"gt=0,dive,len=1,dive,required"
。 - dive Keys & EndKeys:与 dive 同时使用,用于对 Map 对象的键的和值的验证,keys 为键,endkeys 为值。map[string]string:
validate:"gt=0,dive,keys,eq=1\|eq=2,endkeys,required"
。
范围约束
- numeric 约束:约束数值范围;
- String 约束:约束字符串长度;
- Slice、Array、Map 约束:约束其元素的个数。
Tags:
- len:长度等于参数值,例如:len=10;
- eq:数值等于参数值。与 len 不同,对于 String:eq 约束字符串本身的值,而 len 则约束字符串长度,例如:eq=10;
- max:数值小于等于参数值。
- min:数值大于等于参数值。
- ne:不等于参数值。
- gt:大于参数值。
- gte:大于等于参数值。
- lt:小于参数值。
- lte:小于等于参数值。
- oneof:只能是枚举值中的一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,则使用单引号包围。例如:oneof=red green。
字符串约束
下面列举常用的字符串约束:
- contains=:包含参数子串,例如:contains=email;
- containsany:包含参数中任意的 UNICODE 字符,例如:containsany=abcd;
- containsrune:包含参数表示的 rune 字符,例如:containsrune=;
- excludes:不包含参数子串,例如:excludes=email;
- excludesall:不包含参数中任意的 UNICODE 字符,例如:excludesall=abcd;
- excludesrune:不包含参数表示的 rune 字符,例如:excludesrune=;
- startswith:以参数子串为前缀,例如:startswith=hello;
- endswith:以参数子串为后缀,例如:endswith=bye。
- numeric:限制字符串值只包含基本的数值。
唯一性约束
唯一性(unique)约束,对不同类型的处理如下:
- 对于 Slice、Array,unique 约束没有重复的元素;
- 对于 Map,unique 约束没有重复的值;
- 对于元素类型为结构体的 Slice,unique 约束结构体实例的某个字段不重复,通过 unqiue=field 可以指定这个字段名。
跨字段约束
validator 允许定义跨字段的约束,即:约束某个字段与其他字段之间的关系。这种约束实际上分为两种:
- 一种是参数字段就是同一个结构体中的平级字段。
- 另一种是参数字段为结构中其他字段的字段。
约束语法很简单,如果是约束同一个结构中的字段,则在基础的 Tags 后面添加一个 field 后缀,例如:eqfield 定义字段间的相等(eq)约束。如果是更深层次的字段,在 field 之前还需要加上 cs(Cross-Struct),eq 就变为了 eqcsfield。
示例:
type RegisterForm struct {
Name string `validate:"min=2"`
Age int `validate:"min=18"`
Password string `validate:"min=10"`
Password2 string `validate:"eqfield=Password"`
}
即:他们组成就是 “比较符号 + 是否跨 Struct(cross struct) + field”:
- eqfield=Field:必须等于 Field 的值。
- nefield=Field:必须不等于 Field 的值。
- gtfield=Field:必须大于 Field 的值。
- gtefield=Field: 必须大于等于 Field 的值。
- ltfield=Field:必须小于 Field 的值。
- ltefield=Field:必须小于等于 Field 的值。
- eqcsfield=Other.Field:必须等于 struct Other 中 Field 的值。
- necsfield=Other.Field:必须不等于 struct Other 中 Field 的值。
- gtcsfield=Other.Field:必须大于 struct Other 中 Field 的值;
- gtecsfield=Other.Field:必须大于等于 struct Other 中 Field 的值。
- ltcsfield=Other.Field:必须小于 struct Other 中 Field 的值。
- ltecsfield=Other.Field:必须小于等于 struct Other 中 Field 的值。
另外还有几个挺有用的 Tag:
- required_with=Field1 Field2:在 Field1 或者 Field2 存在时,必须;
- required_with_all=Field1 Field2:在 Field1 与 Field2 都存在时,必须;
- required_without=Field1 Field2:在 Field1 或者 Field2 不存在时,必须;
- required_without_all=Field1 Field2:在 Field1 与 Field2 都存在时,必须;
自定义约束
除了使用 validator 提供的内建约束外,还可以定义自己的约束。首先定义一个类型为 func (validator.FieldLevel) bool 的函数检查约束是否满足,可以通过 FieldLevel 取出要检查的字段的信息。然后,调用校验器的 RegisterValidation() 方法将该约束注册到指定的名字上。最后我们就可以在结构体中使用该约束了。
示例:
type RegisterForm struct {
Name string `validate:"palindrome"`
Age int `validate:"min=18"`
}
func reverseString(s string) string {
runes := []rune(s)
for from, to := 0, len(runes)-1; from < to; from, to = from+1, to-1 {
runes[from], runes[to] = runes[to], runes[from]
}
return string(runes)
}
func CheckPalindrome(fl validator.FieldLevel) bool {
value := fl.Field().String()
return value == reverseString(value)
}
func main() {
validate := validator.New()
validate.RegisterValidation("palindrome", CheckPalindrome)
f1 := RegisterForm{
Name: "djd",
Age: 18,
}
err := validate.Struct(f1)
if err != nil {
fmt.Println(err)
}
f2 := RegisterForm{
Name: "dj",
Age: 18,
}
err = validate.Struct(f2)
if err != nil {
fmt.Println(err)
}
}
错误处理
validator 返回的错误有两种,一种是参数错误,一种是校验错误,它们都实现了 error 接口。
- 参数错误时,返回 InvalidValidationError 类型;
- 校验错误时,返回 ValidationErrors 类型。ValidationErrors 是一个错误切片,保存了每个字段违反的每个约束信息。
所以 validator 校验返回的结果只有 3 种情况:
- nil:没有错误;
- InvalidValidationError:输入参数错误;
- ValidationErrors:字段违反约束。
我们可以在程序中判断 err != nil 时,可以依次将 err 转换为 InvalidValidationError 和 ValidationErrors 以获取更详细的信息:
func processErr(err error) {
if err == nil {
return
}
invalid, ok := err.(*validator.InvalidValidationError)
if ok {
fmt.Println("param error:", invalid)
return
}
validationErrs := err.(validator.ValidationErrors)
for _, validationErr := range validationErrs {
fmt.Println(validationErr)
}
}
func main() {
validate := validator.New()
err := validate.Struct(1)
processErr(err)
err = validate.VarWithValue(1, 2, "eqfield")
processErr(err)
}
中文错误信息
需要安装两个包:
go get github.com/go-playground/universal-translator
go get github.com/go-playground/locales
示例:
package main
import (
"fmt"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
)
type Users struct {
Name string `form:"name" json:"name" validate:"required"`
Age uint8 `form:"age" json:"age" validate:"required,gt=18"`
Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
Code string `form:"code" json:"code" validate:"required,len=6"`
}
func main() {
users := &Users{
Name: "admin",
Age: 12,
Passwd: "123",
Code: "123456",
}
// 中文翻译器
uni := ut.New(zh.New())
trans, _ := uni.GetTranslator("zh")
// 校验器
validate := validator.New()
// 注册翻译器到校验器
err := zh_translations.RegisterDefaultTranslations(validate, trans)
if err!=nil {
fmt.Println(err)
}
err = validate.Struct(users)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
fmt.Println(err.Translate(trans))
return
}
}
return
}
参考文档
https://blog.csdn.net/qq_26273559/article/details/107164846