当前位置 博文首页 > golang常用库之字段参数验证库-validator使用详解

    golang常用库之字段参数验证库-validator使用详解

    作者:九卷 时间:2021-02-15 15:30

    golang常用库:gorilla/mux-http路由库使用
    golang常用库:配置文件解析库-viper使用
    golang常用库:操作数据库的orm框架-gorm基本使用
    golang常用库:字段参数验证库-validator使用

    一、背景

    在平常开发中,特别是在web应用开发中,为了验证输入字段的合法性,都会做一些验证操作。比如对用户提交的表单字段进行验证,或者对请求的API接口字段进行验证,验证字段的合法性,保证输入字段值的安全,防止用户的恶意请求。

    一般的做法是用正则表达式,一个字段一个字段的进行验证。一个一个字段验证的话,写起来比较繁琐。那有没更好的方法,进行字段的合法性验证?有, 这就是下面要介绍的 validator 这个验证组件。

    代码地址:
    https://github.com/go-playground/validator

    文档地址:
    https://github.com/go-playground/validator/blob/master/README.md

    二、功能介绍

    这个验证包 github.com/go-playground/validator 验证功能非常多。

    标记之间特殊符号说明

    • 逗号( , ):把多个验证标记隔开。注意:隔开逗号之间不能有空格, validate:"lt=0,gt=100",逗号那里不能有空格,否则panic
    • 横线( - ):跳过该字段不验证
    • 竖线( | ):使用多个验证标记,但是只需满足其中一个即可
    • required:表示该字段值必输设置,且不能为默认值
    • omitempty:如果字段未设置,则忽略它

    范围比较验证

    doc: https://github.com/go-playground/validator/blob/master/README.md#comparisons

    范围验证: 切片、数组和map、字符串,验证其长度;数值,验证大小范围

    • lte:小于等于参数值,validate:"lte=3" (小于等于3)
    • gte:大于等于参数值,validate:"lte=0,gte=120" (大于等于0小于等于120)
    • lt:小于参数值,validate:"lt=3" (小于3)
    • gt:大于参数值,validate:"lt=0,gt=120" (大于0小于120)
    • len:等于参数值,validate:"len=2"
    • max:大于等于参数值,validate:"max=2" (大于等于2)
    • min:小于等于参数值,validate:"min=2,max=10" (大于等于2小于等于10)
    • ne:不等于,validate:"ne=2" (不等于2)
    • oneof:只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号包围,validate:"oneof=red green"

    例子:

    type User struct {
     Name string `json:"name" validate:"min=0,max=35"`
     Age unit8 `json:"age" validate:"lte=0,gte=90"`
    }

    更多功能请参看文档 validator comparisons doc

    字符串验证

    doc: https://github.com/go-playground/validator/blob/master/README.md#strings

    • contains:包含参数子串,validate:"contains=tom" (字段的字符串值包含tom)
    • excludes:包含参数子串,validate:"excludes=tom" (字段的字符串值不包含tom)
    • startswith:以参数子串为前缀,validate:"startswith=golang"
    • endswith:以参数子串为后缀,validate:"startswith=world"

    例子:

    type User struct { 
     Name string `validate:"contains=tom"` 
     Age int `validate:"min=1"`
    }

    更多功能请参看文档 validator strings doc

    字段验证

    doc: https://github.com/go-playground/validator/blob/master/README.md#fields

    eqcsfield:跨不同结构体字段验证,比如说 Struct1 Filed1,与结构体Struct2 Field2相等,

    type Struct1 struct {
     Field1 string `validate:eqcsfield=Struct2.Field2`
     Struct2 struct {
     Field2 string 
     }
    }
    • necsfield:跨不同结构体字段不相等
    • eqfield:同一结构体字段验证相等,最常见的就是输入2次密码验证
    type User struct { 
     Name string `validate:"lte=4"` 
     Age int `validate:"min=20"` 
     Password string `validate:"min=10"`
     Password2 string `validate:"eqfield=Password"`
    }

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

    type User struct {
     Name string `validate:"lte=4"` 
     Age int `validate:"min=20"` 
     Password string `validate:"min=10,nefield=Name"`
    }
    • gtefield:大于等于同一结构体字段,validate:"gtefiled=Field2"
    • ltefield:小于等于同一结构体字段

    更多功能请参看文档:validator Fields DOC

    网络验证

    doc: https://github.com/go-playground/validator/blob/master/README.md#network

    • ip:字段值是否包含有效的IP地址,validate:"ip"
    • ipv4:字段值是否包含有效的ipv4地址,validate:"ipv4"
    • ipv6:字段值是否包含有效的ipv6地址,validate:"ipv6"
    • uri:字段值是否包含有效的uri,validate:"uri"
    • url:字段值是否包含有效的uri,validate:"url"

    更多功能请参看文档:validator network DOC

    Format

    doc: https://github.com/go-playground/validator/blob/master/README.md#format

    base64:字段值是否包含有效的base64值

    更多功能请参看文档 validator strings doc

    其他

    请参看文档: https://github.com/go-playground/validator/blob/master/README.md#other

    三、安装

    go get:

    go get github.com/go-playground/validator/v10

    在文件中引用validator包:

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

    四、validator使用

    文档:https://github.com/go-playground/validator/blob/master/README.md#examples

    例子1:验证单个字段变量值

    validation1.go

    package main
    
    import (
    	"fmt"
    
    	"github.com/go-playground/validator/v10"
    )
    
    func main() {
    	validate := validator.New()
    
    	var boolTest bool
    	err := validate.Var(boolTest, "required")
    	if err != nil {
    		fmt.Println(err)
    	}
    	var stringTest string = ""
    	err = validate.Var(stringTest, "required")
    	if err != nil {
    		fmt.Println(err)
    	}
    
    	var emailTest string = "test@126.com"
    	err = validate.Var(emailTest, "email")
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println("success") // 输出: success。 说明验证成功
    	}
    
    	emailTest2 := "test.126.com"
    	errs := validate.Var(emailTest2, "required,email")
    	if errs != nil {
    		fmt.Println(errs) // 输出: Key: "" Error:Field validation for "" failed on the "email" tag。验证失败
    	}
    
    	fmt.Println("\r\nEnd!!")
     
    }

    运行输出:

    go run simple1.go
    Key: '' Error:Field validation for '' failed on the 'required' tag
    Key: '' Error:Field validation for '' failed on the 'required' tag
    success
    Key: '' Error:Field validation for '' failed on the 'email' tag

    End!!

    例子2:验证结构体struct

    from:struct validate

    validation_struct.go,这个程序还列出了效验出错字段的一些信息,

    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"`
    	Addresses []*Address `validate:"required,dive,required"`
    }
    
    type Address struct {
    	Street string `validate:"required"`
    	City string `validate:"required"`
    	Planet string `validate:"required"`
    	Phone string `validate:"required"`
    }
    
    func main() {
    	address := &Address{
    		Street: "Eavesdown Docks",
    		Planet: "Persphone",
    		Phone: "none",
    	}
    
    	user := &User{
    		FirstName: "Badger",
    		LastName: "Smith",
    		Age: 135,
    		Email: "Badger.Smith@gmail.com",
    		Addresses: []*Address{address},
    	}
    
    	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
    	}
    }

    运行 输出:

    $ go run validation_struct.go
    === error msg ====
    Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
    Key: 'User.Addresses[0].City' Error:Field validation for 'City' failed on the 'required' tag

    =========== error field info ====================
    Namespace: User.Age
    Fild: Age
    StructNamespace: User.Age
    StructField: Age
    Tag: lte
    ActualTag: lte
    Kind: uint8
    Type: uint8
    Value: 135
    Param: 130

    Namespace: User.Addresses[0].City
    Fild: City
    StructNamespace: User.Addresses[0].City
    StructField: City
    Tag: required
    ActualTag: required
    Kind: string
    Type: string
    Value:
    Param:

    还可以给字段加一些其他tag信息,方面form,json的解析,如下:

    type User struct {
     FirstName string `form:"firstname" json:"firstname" validate:"required"`
    	LastName string `form:"lastname" json:"lastname" validate:"required"`
    	Age uint8 ` form:"age" json:"age"validate:"gte=0,lte=130"`
    	Email string ` form:"email" json:"email" validate:"required,email"`
    }

    用户自定义函数验证

    用户自定义函数验证字段是否合法,效验是否正确。

    例子3: 通过字段tag自定义函数

    validate.RegisterValidation

    customer_tag.go:

    package main
    
    import (
    	"fmt"
    
    	"github.com/go-playground/validator/v10"
    )
    
    type User struct {
    	Name string `form:"name" json:"name" validate:"required,CustomerValidation"` //注意:required和CustomerValidation之间不能有空格,否则panic。CustomerValidation:自定义tag-函数标签
    	Age uint8 ` form:"age" json:"age" validate:"gte=0,lte=80"` //注意:gte=0和lte=80之间不能有空格,否则panic
    }
    
    var validate *validator.Validate
    
    func main() {
    	validate = validator.New()
    	validate.RegisterValidation("CustomerValidation", CustomerValidationFunc) //注册自定义函数,前一个参数是struct里tag自定义,后一个参数是自定义的函数
    
    	user := &User{
    		Name: "jimmy",
    		Age: 86,
    	}
    
    	fmt.Println("first value: ", user)
    	err := validate.Struct(user)
    	if err != nil {
    		fmt.Printf("Err(s):\n%+v\n", err)
    	}
    
    	user.Name = "tom"
    	user.Age = 29
    	fmt.Println("second value: ", user)
    	err = validate.Struct(user)
    	if err != nil {
    		fmt.Printf("Err(s):\n%+v\n", err)
    	}
    }
    
    // 自定义函数
    func CustomerValidationFunc(f1 validator.FieldLevel) bool {
     // f1 包含了字段相关信息
     // f1.Field() 获取当前字段信息
     // f1.Param() 获取tag对应的参数
     // f1.FieldName() 获取字段名称
     
    	return f1.Field().String() == "jimmy"
    }

    运行输出:

    $ go run customer.go
    first value: &{jimmy 86}
    Err(s):
    Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
    second value: &{tom 29}
    Err(s):
    Key: 'User.Name' Error:Field validation for 'Name' failed on the 'CustomerValidation' tag

    **注意

    上面代码user struct定义中 ,validate里的required和CustomerValidation之间不能有空格,否则运行时报panic错误:panic: Undefined validation function ' CustomerValidation' on field 'Name'

    例子4:自定义函数-直接注册函数1

    不通过字段tag自定义函数,直接注册函数。

    RegisterStructValidation

    https://github.com/go-playground/validator/blob/master/_examples/struct-level/main.go

    customer1.go

    package main
    
    import (
    	"fmt"
    
    	"github.com/go-playground/validator/v10"
    )
    
    type User struct {
    	FirstName string `json:firstname`
    	LastName string `json:lastname`
    	Age uint8 `validate:"gte=0,lte=130"`
    	Email string `validate:"required,email"`
    	FavouriteColor string `validate:"hexcolor|rgb|rgba"`
    }
    
    var validate *validator.Validate
    
    func main() {
    	validate = validator.New()
    
    	validate.RegisterStructValidation(UserStructLevelValidation, User{})
    
    	user := &User{
    		FirstName: "",
    		LastName: "",
    		Age: 30,
    		Email: "TestFunc@126.com",
    		FavouriteColor: "#000",
    	}
    
    	err := validate.Struct(user)
    	if err != nil {
    		fmt.Println(err)
    	}
    }
    
    func UserStructLevelValidation(sl validator.StructLevel) {
    	user := sl.Current().Interface().(User)
    
    	if len(user.FirstName) == 0 && len(user.LastName) == 0 {
    		sl.ReportError(user.FirstName, "FirstName", "firstname", "firstname", "")
    		sl.ReportError(user.LastName, "LastName", "lastname", "lastname", "")
    	}
    }

    运行输出:

    $ go run customer1.go
    Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'firstname' tag
    Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'lastname' tag

    例子5:自定义函数-直接注册函数2

    RegisterCustomTypeFunc

    https://github.com/go-playground/validator/blob/master/_examples/custom/main.go

    validate.RegisterCustomTypeFunc:验证类型的自定义函数

    customer2.go:

    package main
    
    import (
    	"database/sql"
    	"database/sql/driver"
    	"fmt"
    	"reflect"
    
    	"github.com/go-playground/validator/v10"
    )
    
    type DbBackedUser struct {
    	Name sql.NullString `validate:"required"`
    	Age sql.NullInt64 `validate:"required"`
    }
    
    var validate *validator.Validate
    
    func main() {
    	validate = validator.New()
    
    	validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})
    
    	// build object for validation
    	x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}
    
    	err := validate.Struct(x)
    	if err != nil {
    		fmt.Printf("Err(s):\n%+v\n", err)
    	}
    }
    
    func ValidateValuer(field reflect.Value) interface{} {
    	if valuer, ok := field.Interface().(driver.Valuer); ok {
    		val, err := valuer.Value()
    		if err == nil {
    			return val
    		}
    		// handle the error how you want
    	}
    	return nil
    }

    运行输出:

    $ go run customer.go
    Err(s):
    Key: 'DbBackedUser.Name' Error:Field validation for 'Name' failed on the 'required' tag
    Key: 'DbBackedUser.Age' Error:Field validation for 'Age' failed on the 'required' tag

    注意,这个函数
    RegisterCustomTypeFunc,它上面有2行注释:

    // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
    //
    // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation

    它是一个验证数据类型自定义函数,NOTE:这个方法不是线程安全的

    五、参考

    https://github.com/go-playground/validator/blob/master/README.mdhttps://github.com/go-playground/validator/tree/master/_examples

    总结

    js