Go 语言编程 — validator 数据校验工具

目录

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 标记。[][]stringvalidate:"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。

字符串约束

下面列举常用的字符串约束:

扫描二维码关注公众号,回复: 11867713 查看本文章
  • 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 允许定义跨字段的约束,即:约束某个字段与其他字段之间的关系。这种约束实际上分为两种:

  1. 一种是参数字段就是同一个结构体中的平级字段。
  2. 另一种是参数字段为结构中其他字段的字段。

约束语法很简单,如果是约束同一个结构中的字段,则在基础的 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

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/108838776