Go中最常用的数据校验库

125 阅读13分钟

项目地址: github.com/go-playground/validator/v10

标记标记说明
required必填Field或Struct validate:"required"
omitempty空时忽略Field或Struct validate:"omitempty"
len长度Field validate:"len=0"
eq等于Field validate:"eq=0"
gt大于Field validate:"gt=0"
gte大于等于Field validate:"gte=0"
lt小于Field validate:"lt=0"
lte小于等于Field validate:"lte=0"
eqfield同一结构体字段相等Field validate:"eqfield=Field2"
nefield同一结构体字段不相等Field validate:"nefield=Field2"
gtfield大于同一结构体字段Field validate:"gtfield=Field2"
gtefield大于等于同一结构体字段Field validate:"gtefield=Field2"
ltfield小于同一结构体字段Field validate:"ltfield=Field2"
ltefield小于等于同一结构体字段Field validate:"ltefield=Field2"
eqcsfield跨不同结构体字段相等Struct1.Field validate:"eqcsfield=Struct2.Field2"
necsfield跨不同结构体字段不相等Struct1.Field validate:"necsfield=Struct2.Field2"
gtcsfield大于跨不同结构体字段Struct1.Field validate:"gtcsfield=Struct2.Field2"
gtecsfield大于等于跨不同结构体字段Struct1.Field validate:"gtecsfield=Struct2.Field2"
ltcsfield小于跨不同结构体字段Struct1.Field validate:"ltcsfield=Struct2.Field2"
ltecsfield小于等于跨不同结构体字段Struct1.Field validate:"ltecsfield=Struct2.Field2"
min最大值Field validate:"min=1"
max最小值Field validate:"max=2"
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"
required_with其他字段其中一个不为空且当前字段不为空Field validate:"required_with=Field1 Field2"
required_with_all其他所有字段不为空且当前字段不为空Field validate:"required_with_all=Field1 Field2"
required_without其他字段其中一个为空且当前字段不为空Field `validate:"required_without=Field1 Field2"
required_without_all其他所有字段为空且当前字段不为空Field validate:"required_without_all=Field1 Field2"
isdefault是默认值Field validate:"isdefault=0"
oneof其中之一Field validate:"oneof=5 7 9"
containsfield字段包含另一个字段Field validate:"containsfield=Field2"
excludesfield字段不包含另一个字段Field validate:"excludesfield=Field2"
unique是否唯一,通常用于切片或结构体Field validate:"unique"
alphanum字符串值是否只包含 ASCII 字母数字字符Field validate:"alphanum"
alphaunicode字符串值是否只包含 unicode 字符Field validate:"alphaunicode"
alphanumunicode字符串值是否只包含 unicode 字母数字字符Field validate:"alphanumunicode"
numeric字符串值是否包含基本的数值Field validate:"numeric"
hexadecimal字符串值是否包含有效的十六进制Field validate:"hexadecimal"
hexcolor字符串值是否包含有效的十六进制颜色Field validate:"hexcolor"
lowercase符串值是否只包含小写字符Field validate:"lowercase"
uppercase符串值是否只包含大写字符Field validate:"uppercase"
email字符串值包含一个有效的电子邮件Field validate:"email"
json字符串值是否为有效的 JSONField validate:"json"
file符串值是否包含有效的文件路径,以及该文件是否存在于计算机上Field validate:"file"
url符串值是否包含有效的 urlField validate:"url"
uri符串值是否包含有效的 uriField validate:"uri"
base64字符串值是否包含有效的 base64值Field validate:"base64"
contains字符串值包含子字符串值Field validate:"contains=@"
containsany字符串值包含子字符串值中的任何字符Field validate:"containsany=abc"
containsrune字符串值包含提供的特殊符号值Field validate:"containsrune=☢"
excludes字符串值不包含子字符串值Field validate:"excludes=@"
excludesall字符串值不包含任何子字符串值Field validate:"excludesall=abc"
excludesrune字符串值不包含提供的特殊符号值Field validate:"containsrune=☢"
startswith字符串以提供的字符串值开始Field validate:"startswith=abc"
endswith字符串以提供的字符串值结束Field validate:"endswith=abc"
ip字符串值是否包含有效的 IP 地址Field validate:"ip"
ipv4字符串值是否包含有效的 ipv4地址Field validate:"ipv4"
datetime字符串值是否包含有效的 日期Field validate:"datetime"

单字段校验


对前端传参进行校验

单字段多个条件 校验


package main

import (
	"fmt"
	"strings"

	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"

	zhtrans "github.com/go-playground/validator/v10/translations/zh"
	// entrans "github.com/go-playground/validator/v10/translations/en"
)

/*
https://www.cnblogs.com/jiujuan/p/13823864.html

https://www.liwenzhou.com/posts/Go/validator-usages/

https://juejin.cn/post/7056823502640250893

https://juejin.cn/post/6847902214279659533
*/
type Student struct {
	Name  string `validate:required`
	Email string `validate:"email"`
	Age   int    `validate:"max=30,min=12"`
}

func main() {
	en := en.New() //英文翻译器
	zh := zh.New() //中文翻译器

	// 第一个参数是必填,如果没有其他的语言设置,就用这第一个
	// 后面的参数是支持多语言环境(
	// uni := ut.New(en, en) 也是可以的
	// uni := ut.New(en, zh, tw)
	uni := ut.New(en, zh)
	trans, _ := uni.GetTranslator("zh") //获取需要的语言

	student := Student{
		Name:  "tom",
		Email: "testemal",
		Age:   40,
	}
	validate := validator.New()

	zhtrans.RegisterDefaultTranslations(validate, trans)

	err := validate.Struct(student)
	if err != nil {
		// fmt.Println(err)

		errs := err.(validator.ValidationErrors)
		fmt.Println(removeStructName(errs.Translate(trans)))
	}
}

func removeStructName(fields map[string]string) map[string]string {
	result := map[string]string{}

	for field, err := range fields {
		result[field[strings.Index(field, ".")+1:]] = err
	}
	return result
}

在线代码

输出:

map[Age:Age必须小于或等于30 Email:Email必须是一个有效的邮箱]


package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type User struct {
	FirstName string               `validate:"required"`
	LastName  string               `validate:"required"`
	Age       uint8                `validate:"gte=0,lte=130"`
	Email     string               `validate:"required,email"`
	Test      string               `validate:"len=0|min=6,max=24,len=0|alphanum"` // 或者 条件之一, 使用|。 但每个,都是独立的一个逻辑,之间是&的关系(有一个不满足就报错"Error:Field validation"),且条件没有传递,所以要在alphanum前面也加一个len=0|。 而max=24和长度为0不冲突,所以不需要加
	Products  []CreateOrderProduct `validate:"min=1"`                             // 产品列表
}

type CreateOrderProduct struct {
	SkuCode  string `json:"skuCode"`  // sku编码
	Quantity int64  `json:"quantity"` // 商品数量
}

//  oneof:只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号包围,validate:"oneof=red green"
// https://www.cnblogs.com/jiujuan/p/13823864.html

func main() {

	user := &User{
		FirstName: "Badger",
		LastName:  "Smith",
		Age:       115,
		Email:     "Badger.Smith@gmail.com",
		Test:      "",
		Products:  []CreateOrderProduct{},
	}

	validate := validator.New()
	err := validate.Struct(user)
	if err != nil {
		fmt.Println("=== error msg ====")
		fmt.Println(err)

		//if _, ok := err.(*validator.InvalidValidationError); ok {
		//	fmt.Println(err)
		//	return
		//}
		//
		//fmt.Println("\r\n=========== error field info ====================")
		//for _, err := range err.(validator.ValidationErrors) {
		//	// 列出效验出错字段的信息
		//	fmt.Println("Namespace: ", err.Namespace())
		//	fmt.Println("Fild: ", err.Field())
		//	fmt.Println("StructNamespace: ", err.StructNamespace())
		//	fmt.Println("StructField: ", err.StructField())
		//	fmt.Println("Tag: ", err.Tag())
		//	fmt.Println("ActualTag: ", err.ActualTag())
		//	fmt.Println("Kind: ", err.Kind())
		//	fmt.Println("Type: ", err.Type())
		//	fmt.Println("Value: ", err.Value())
		//	fmt.Println("Param: ", err.Param())
		//	fmt.Println()
		//}

		// from here you can create your own error messages in whatever language you wish
		return
	}
}

在线代码

输出:

=== error msg ====
Key: 'User.Products' Error:Field validation for 'Products' failed on the 'min' tag



跨字段验证


eqfield 同一结构体字段验证相等


eqfield=Field:必须等于 Field 的值

最常见的就是输入2次密码验证

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

// 多字段联合校验

// eqfield:同一结构体字段验证相等,最常见的就是输入2次密码验证

type Account struct {
	Name      string `validate:"lte=16"`
	Age       int    `validate:"min=20"`
	Password  string `validate:"min=8"`
	Password2 string `validate:"eqfield=Password"`
}

func main() {
	account := &Account{
		Name:      "Badger",
		Age:       115,
		Password:  "qwert12345",
		Password2: "111111",
	}

	validate := validator.New()
	err := validate.Struct(account)
	if err != nil {
		fmt.Println("=== error msg ====")
		fmt.Println(err)

		if _, ok := err.(*validator.InvalidValidationError); ok {
			fmt.Println(err)
			return
		}

		fmt.Println("\r\n=========== error field info ====================")
		for _, err := range err.(validator.ValidationErrors) {
			// 列出效验出错字段的信息
			fmt.Println("Namespace: ", err.Namespace())
			fmt.Println("Fild: ", err.Field())
			fmt.Println("StructNamespace: ", err.StructNamespace())
			fmt.Println("StructField: ", err.StructField())
			fmt.Println("Tag: ", err.Tag())
			fmt.Println("ActualTag: ", err.ActualTag())
			fmt.Println("Kind: ", err.Kind())
			fmt.Println("Type: ", err.Type())
			fmt.Println("Value: ", err.Value())
			fmt.Println("Param: ", err.Param())
			fmt.Println()
		}

		// from here you can create your own error messages in whatever language you wish
		return
	}
}

在线运行

输出:

=== error msg ====
Key: 'Account.Password2' Error:Field validation for 'Password2' failed on the 'eqfield' tag

=========== error field info ====================
Namespace:  Account.Password2
Fild:  Password2
StructNamespace:  Account.Password2
StructField:  Password2
Tag:  eqfield
ActualTag:  eqfield
Kind:  string
Type:  string
Value:  111111
Param:  Password

nefield:同一结构体字段验证不相等


nefield=Field:必须不等于 Field 的值

例如,验证密码不能和用户名相同

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

// 多字段联合校验

// eqfield:同一结构体字段验证相等,最常见的就是输入2次密码验证

type Account struct {
	Name     string `validate:"lte=16"`
	Age      int    `validate:"min=20"`
	Password string `validate:"min=1,nefield=Name"`
}

func main() {
	account := &Account{
		Name:     "Badger",
		Age:      115,
		Password: "Badger",
	}

	validate := validator.New()
	err := validate.Struct(account)
	if err != nil {
		fmt.Println("=== error msg ====")
		fmt.Println(err)

		if _, ok := err.(*validator.InvalidValidationError); ok {
			fmt.Println(err)
			return
		}

		fmt.Println("\r\n=========== error field info ====================")
		for _, err := range err.(validator.ValidationErrors) {
			// 列出效验出错字段的信息
			fmt.Println("Namespace: ", err.Namespace())
			fmt.Println("Fild: ", err.Field())
			fmt.Println("StructNamespace: ", err.StructNamespace())
			fmt.Println("StructField: ", err.StructField())
			fmt.Println("Tag: ", err.Tag())
			fmt.Println("ActualTag: ", err.ActualTag())
			fmt.Println("Kind: ", err.Kind())
			fmt.Println("Type: ", err.Type())
			fmt.Println("Value: ", err.Value())
			fmt.Println("Param: ", err.Param())
			fmt.Println()
		}

		// from here you can create your own error messages in whatever language you wish
		return
	}
}

输出:

=== error msg ====
Key: 'Account.Password' Error:Field validation for 'Password' failed on the 'nefield' tag

=========== error field info ====================
Namespace:  Account.Password
Fild:  Password
StructNamespace:  Account.Password
StructField:  Password
Tag:  nefield
ActualTag:  nefield
Kind:  string
Type:  string
Value:  Badger
Param:  Name

类似的还有

  • gtfield=Field:必须大于 Field 的值。
  • gtefield=Field: 必须大于等于 Field 的值。
  • ltfield=Field:必须小于 Field 的值。
  • ltefield=Field:必须小于等于 Field 的值。



eqcsfield=Other.Field:必须等于 struct Other 中 Field 的值。


用于验证跨结构体的两个字段是否相等,需要指定另一个字段的名称或路径作为参数,比如 eqcsfield=Other.Field 中的 Other.Field 就是指定的另一个字段。

在使用该选项时,会比较当前字段和指定的另一个字段的值是否相等,如果相等则验证通过,否则验证失败。这个选项通常用于验证密码和确认密码等类似的场景。


package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type Struct1 struct {
	Field1  string `validate:"eqcsfield=Struct2.Field2""`
	Struct2 struct {
		Field2 string
	}
}

func main() {

	s := &Struct1{
		Field1:  "必须一致",
		Struct2: struct{ Field2 string }{Field2: "没有一致"},
	}

	validate := validator.New()
	err := validate.Struct(s)
	if err != nil {
		fmt.Println("=== error msg ====")
		fmt.Println(err)

		if _, ok := err.(*validator.InvalidValidationError); ok {
			fmt.Println(err)
			return
		}

		fmt.Println("\r\n=========== error field info ====================")
		for _, err := range err.(validator.ValidationErrors) {
			// 列出效验出错字段的信息
			fmt.Println("Namespace: ", err.Namespace())
			fmt.Println("Fild: ", err.Field())
			fmt.Println("StructNamespace: ", err.StructNamespace())
			fmt.Println("StructField: ", err.StructField())
			fmt.Println("Tag: ", err.Tag())
			fmt.Println("ActualTag: ", err.ActualTag())
			fmt.Println("Kind: ", err.Kind())
			fmt.Println("Type: ", err.Type())
			fmt.Println("Value: ", err.Value())
			fmt.Println("Param: ", err.Param())
			fmt.Println()
		}

		// from here you can create your own error messages in whatever language you wish
		return
	}
}

输出:

=== error msg ====
Key: 'Struct1.Field1' Error:Field validation for 'Field1' failed on the 'eqcsfield' tag

=========== error field info ====================
Namespace:  Struct1.Field1
Fild:  Field1
StructNamespace:  Struct1.Field1
StructField:  Field1
Tag:  eqcsfield
ActualTag:  eqcsfield
Kind:  string
Type:  string
Value:  必须一致
Param:  Struct2.Field2

看起来只支持嵌套结构体,不支持两个独立的结构体之间某个字段的比较


eqfieldeqcsfield 的区别在于它们用于比较的字段的位置不同:eqfield 比较的是同一个结构体中的两个字段的值,而 eqcsfield 比较的是当前结构体中的某个字段和另一个(子?)结构体中的字段的值


类似的还有

  • 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 的值。

如何比较两个独立结构体中某两个字段的值?




required_with=Field1 Field2:在 Field1 或者 Field2 存在时,必须;

required_with=Field2:在 Field2被填写(即不为空)时,Field1也必须不能为空


package main

import (
    "fmt"
    "github.com/go-playground/validator/v10"
)

type User struct {
    Name     string `validate:"required"`
    Email    string `validate:"required_with=Phone"`
    Phone    string
}

func main() {
    user1 := User{
        Name:     "John",
        Email:    "",
        Phone:    "",
    }

    user2 := User{
        Name:     "Mary",
        Email:    "mary@example.com",
        Phone:    "",
    }

    validate := validator.New()

    err1 := validate.Struct(user1)
    if err1 != nil {
        fmt.Println(err1)
    }

    err2 := validate.Struct(user2)
    if err2 != nil {
        fmt.Println(err2)
    }
}

验证通过~

在这个例子中,User 结构体包含 Name、Email 和 Phone 字段。Email 字段被标记为 required_with=Phone,这意味着当 Phone 字段被填写时,Email 字段也必须被填写。

而如果把user1改为:

user1 := User{
		Name:  "John",
		Email: "",
		Phone: "123",
	}

则会报错:

Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required_with' tag

验证不通过




required_with_all=Field1 Field2:在 Field1 与 Field2 都存在时,必须;(仅当所有其他指定的字段都存在时,验证字段才必须存在)


要么有这个tag的全部为空,如果有一个不为空,那所有其他的也都不能为空~

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type User6 struct {
	Name  string `validate:"required"`
	Email string `validate:"required_with_all=Phone"`
	Phone string `validate:"required_with_all=Email"`
}

func main() {
	user := User6{
		Name:  "John",
		Email: "",
		Phone: "",
	}

	validate := validator.New()

	err := validate.Struct(user)
	if err != nil {
		fmt.Println(err)
	}
}

Email 和 Phone 字段都被标记为 required_with_all,

这意味着当 Email 和 Phone

  • 要么全都为空

  • 如果其中的任何一个被填写时,另一个也必须被填写(不为空即可,可以不一样)

所以上面代码可以验证通过


如下也是合法的:

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type User6 struct {
	Name  string `validate:"required"`
	Email string `validate:"required_with_all=Phone"`
	Phone string `validate:"required_with_all=Email"`
}

func main() {
	user := User6{
		Name:  "John",
		Email: "1",
		Phone: "2",
	}

	validate := validator.New()

	err := validate.Struct(user)
	if err != nil {
		fmt.Println(err)
	}
}

类似的还有:

required_without=Field1 Field2:在 Field1 或者 Field2 不存在时,必须;

Field1 Field2字段其中(至少)一个为空,则当前字段不能为空

package main

import (
    "fmt"
    "github.com/go-playground/validator/v10"
)

type User struct {
    Name     string `validate:"required"`
    Email    string
    Phone    string
    Address  string `validate:"required_without=Email Phone"`// Email Phone 中间不能加逗号
}

func main() {
    user1 := User{
        Name:     "John",
        Email:    "",
        Phone:    "",
        Address:  "123 Main St.",
    }

    user2 := User{
        Name:     "Mary",
        Email:    "mary@example.com",
        Phone:    "",
        Address:  "",
    }

    validate := validator.New()

    err1 := validate.Struct(user1)
    if err1 != nil {
        fmt.Println("err1:", err1)
    }

    err2 := validate.Struct(user2)
    if err2 != nil {
       fmt.Println("err2:", err2)
    }
}

输出:

err2: Key: 'User7.Address' Error:Field validation for 'Address' failed on the 'required_without' tag

User 结构体包含 Name、Email、Phone 和 Address 字段。Address 字段被标记为 required_without=Email Phone,这意味着当 Email 和 Phone 字段至少一个为空时,Address 字段必须被填写。


required_without_all=Field1 Field2:在 Field1 与 Field2 都存在时,必须; (仅当所有其他指定字段都不存在时,验证字段才必须...)


Field1 Field2字段都为空时,则当前字段不能为空

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type User7 struct {
	Name    string `validate:"required"`
	Email   string
	Phone   string
	Address string `validate:"required_without_all=Email Phone"`
}

func main() {
	user1 := User7{
		Name:    "John",
		Email:   "",
		Phone:   "111",
		Address: "123 Main St.",
	}

	user2 := User7{
		Name:    "Mary",
		Email:   "",
		Phone:   "",
		Address: "",
	}

	validate := validator.New()

	err1 := validate.Struct(user1)
	if err1 != nil {
		fmt.Println("err1:", err1)
	}

	err2 := validate.Struct(user2)
	if err2 != nil {
		fmt.Println("err2:", err2)
	}
}

输出:

err2: Key: 'User7.Address' Error:Field validation for 'Address' failed on the 'required_without_all' tag



验证proto

可参考 Go gRPC进阶-proto数据验证(九)




更复杂的判断


type User struct {
Age uint8 validate:"gte=0,lte=130"
Email string validate:"required,email"
Score int validate:"min=1" // 分数
Gender string validate:"required,oneof=男 女"
}

如果满足以下任意一项则认为通过:

  • Gender=男,Age小于35,Score大于60
  • Gender=女,Age小于40,Score大于50

这种用validator/v10能判断吗?..


这种复杂的验证规则超出了validator/v10的基本功能,需要进行自定义验证函数。可以使用validator/v10的Func函数,通过编写自定义的验证函数来实现这种验证规则。

如下是一个示例代码:

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type User struct {
	Age    uint8  `validate:"gte=0,lte=130,customValidation"`
	Email  string `validate:"required,email"`
	Score  int    `validate:"required,gte=0,customValidation"`
	Gender string `validate:"required,oneof=男 女,customValidation"`
}

func validateUser(fl validator.FieldLevel) bool {

	user, ok := fl.Top().Interface().(*User)

	fmt.Println("user is:", user)

	if !ok {
		return false
	}

	if user.Gender == "男" && user.Age < 35 && user.Score > 60 {
		return true
	}
	if user.Gender == "女" && user.Age < 40 && user.Score > 50 {
		return true
	}
	return false
}

// - Gender=男,Age小于35,Score大于60
// - Gender=女,Age小于40,Score大于50

func main() {
	user := &User{
		Age:    36,
		Email:  "example@gmail.com",
		Score:  1711,
		Gender: "男",
	}

	validate := validator.New()
	err := validate.RegisterValidation("customValidation", validateUser)
	if err != nil {
		fmt.Println(err)
		return
	}

	err = validate.Struct(user)

	fmt.Println("err is:", err)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("validation succeeded")
}

输出:

user is: &{36 example@gmail.com 1711 男}
user is: &{36 example@gmail.com 1711 男}
user is: &{36 example@gmail.com 1711 男}
err is: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'customValidation' tag
Key: 'User.Score' Error:Field validation for 'Score' failed on the 'customValidation' tag
Key: 'User.Gender' Error:Field validation for 'Gender' failed on the 'customValidation' tag
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'customValidation' tag
Key: 'User.Score' Error:Field validation for 'Score' failed on the 'customValidation' tag
Key: 'User.Gender' Error:Field validation for 'Gender' failed on the 'customValidation' tag



参考资料:

golang之验证器validator

【Go】数据验证-validator

Go 使用validator进行后端数据校验

gopkg.in/go-playgrou…

结构字段验证--validator.v9

Golang验证器之validator使用详解

Go 每日一库之 validator

golang常用库:字段参数验证库-validator使用


有空还想探究下,这么一个工具怎么可以讲这么多期..

B站视频:论一款强大的验证组件在web开发中的重要性

B站视频:go语言验证框架Validator的6种高端操作

B站视频:go开源验证框架validator必会的3个操作

B站视频:validator验证框架3种自定义验证方法

B站视频:validator快速实现字段格式的验证

B站视频:validator快速搞定字段格式验证

B站视频:validator自定义验证与本地化提示