官方地址https://github.com/go-playground/validator
开发文档:https://godoc.org/github.com/go-playground/validator
中文开发参考文档:https://s0godoc0org.icopy.site/gopkg.in/go-playground/validator.v9
开发参考用例:https://github.com/go-playground/validator/tree/master/_examples
HelloWorld
package mainimport ("fmt""gopkg.in/go-playground/validator.v9")// User contains user informationtype User struct {FirstName string `validate:"required"`LastName string `validate:"required"`Age uint8 `validate:"gte=0,lte=130"`Email string `validate:"required,email"`FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla'Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...}// Address houses a users address informationtype Address struct {Street string `validate:"required"`City string `validate:"required"`Planet string `validate:"required"`Phone string `validate:"required"`}// use a single instance of Validate, it caches struct infovar validate *validator.Validatefunc main() {validate = validator.New()validateStruct()validateVariable()}func validateStruct() {address := &Address{Street: "Eavesdown Docks",Planet: "Persphone",Phone: "none",}user := &User{FirstName: "Badger",LastName: "Smith",Age: 135,Email: "Badger.Smith@gmail.com",FavouriteColor: "#000-",Addresses: []*Address{address},}// returns nil or ValidationErrors ( []FieldError )err := validate.Struct(user)if err != nil {// this check is only needed when your code could produce// an invalid value for validation such as interface with nil// value most including myself do not usually have code like this.if _, ok := err.(*validator.InvalidValidationError); ok {fmt.Println(err)return}for _, err := range err.(validator.ValidationErrors) {fmt.Println(err.Namespace())fmt.Println(err.Field())fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered orfmt.Println(err.StructField()) // by passing alt name to ReportError like belowfmt.Println(err.Tag())fmt.Println(err.ActualTag())fmt.Println(err.Kind())fmt.Println(err.Type())fmt.Println(err.Value())fmt.Println(err.Param())}return}// save user to database}func validateVariable() {myEmail := "joeybloggs.gmail.com"errs := validate.Var(myEmail, "required,email")if errs != nil {fmt.Println(errs.Error()) // output: Key: "" Error:Field validation for "" failed on the "email" tagreturn}}
打印结果
GOROOT=/usr/local/Cellar/go/1.13.4/libexec #gosetupGOPATH=/Users/baxiang/go #gosetup/usr/local/Cellar/go/1.13.4/libexec/bin/go build -o /private/var/folders/61/2qtr44g130x_pp0xn3ym8qv80000gn/T/___go_build_github_com_baxiang_go_note_validator github.com/baxiang/go-note/validator #gosetup/private/var/folders/61/2qtr44g130x_pp0xn3ym8qv80000gn/T/___go_build_github_com_baxiang_go_note_validator #gosetupUser.AgeAgeUser.AgeAgeltelteuint8uint8135130User.FavouriteColorFavouriteColorUser.FavouriteColorFavouriteColoriscolorhexcolor|rgb|rgba|hsl|hslastringstring#000-User.Addresses[0].CityCityUser.Addresses[0].CityCityrequiredrequiredstringstringKey: '' Error:Field validation for '' failed on the 'email' tagProcess finished with exit code 0
其中一个区别是v8版本没有指定默认的TagName,需要在每次声明validator实例的时候配置;在v9中设置了默认的TagName就是validate。我们的gin框架在使用的时候设置的TagName为”binding”。
validator中给的例子代码
var validate *validator.Validateconfig := &validator.Config{TagName: "validate"}validate = validator.New(config)
gin中设置的TagName,文件gin/binding/default_validator.go
func (v *defaultValidator) lazyinit() {v.once.Do(func() {config := &validator.Config{TagName: "binding"}v.validate = validator.New(config)})}
这也就是为啥gin框架里面example中的定义的struct都是binding开头
其他区别例如实例化不同等
type Booking struct {CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`}
常用标签字段
validator使用的struct的tag有以下这么多,其中有一些是通过正则表达式实现类型判断的,例如email、rgb、uuid等;有一些是通过第三方库实现的,例如ipv4、cidr等。
// validator/baked_in.gobakedInValidators = map[string]Func{"required": hasValue,"isdefault": isDefault,"len": hasLengthOf,"min": hasMinOf,"max": hasMaxOf,"eq": isEq,"ne": isNe,"lt": isLt,"lte": isLte,"gt": isGt,"gte": isGte,"eqfield": isEqField,"eqcsfield": isEqCrossStructField,"necsfield": isNeCrossStructField,"gtcsfield": isGtCrossStructField,"gtecsfield": isGteCrossStructField,"ltcsfield": isLtCrossStructField,"ltecsfield": isLteCrossStructField,"nefield": isNeField,"gtefield": isGteField,"gtfield": isGtField,"ltefield": isLteField,"ltfield": isLtField,"alpha": isAlpha,"alphanum": isAlphanum,"alphaunicode": isAlphaUnicode,"alphanumunicode": isAlphanumUnicode,"numeric": isNumeric,"number": isNumber,"hexadecimal": isHexadecimal,"hexcolor": isHEXColor,"rgb": isRGB,"rgba": isRGBA,"hsl": isHSL,"hsla": isHSLA,"email": isEmail,"url": isURL,"uri": isURI,"file": isFile,"base64": isBase64,"base64url": isBase64URL,"contains": contains,"containsany": containsAny,"containsrune": containsRune,"excludes": excludes,"excludesall": excludesAll,"excludesrune": excludesRune,"isbn": isISBN,"isbn10": isISBN10,"isbn13": isISBN13,"eth_addr": isEthereumAddress,"btc_addr": isBitcoinAddress,"btc_addr_bech32": isBitcoinBech32Address,"uuid": isUUID,"uuid3": isUUID3,"uuid4": isUUID4,"uuid5": isUUID5,"ascii": isASCII,"printascii": isPrintableASCII,"multibyte": hasMultiByteCharacter,"datauri": isDataURI,"latitude": isLatitude,"longitude": isLongitude,"ssn": isSSN,"ipv4": isIPv4,"ipv6": isIPv6,"ip": isIP,"cidrv4": isCIDRv4,"cidrv6": isCIDRv6,"cidr": isCIDR,"tcp4_addr": isTCP4AddrResolvable,"tcp6_addr": isTCP6AddrResolvable,"tcp_addr": isTCPAddrResolvable,"udp4_addr": isUDP4AddrResolvable,"udp6_addr": isUDP6AddrResolvable,"udp_addr": isUDPAddrResolvable,"ip4_addr": isIP4AddrResolvable,"ip6_addr": isIP6AddrResolvable,"ip_addr": isIPAddrResolvable,"unix_addr": isUnixAddrResolvable,"mac": isMAC,"hostname": isHostnameRFC952, // RFC 952"hostname_rfc1123": isHostnameRFC1123, // RFC 1123"fqdn": isFQDN,"unique": isUnique,"oneof": isOneOf,"html": isHTML,"html_encoded": isHTMLEncoded,"url_encoded": isURLEncoded,}
required表示字段必须有值,并且不为默认值,例如bool默认值为false、string默认值为””、int默认值为0。如果有些字段是可填的,并且需要满足某些规则的,那么需要使用omitempty
package mainimport ("fmt""gopkg.in/go-playground/validator.v9")func main() {validate := validator.New()var a =0err := validate.Var(a,"required")if err != nil {fmt.Println(err)}var b boolerr = validate.Var(b,"required")if err != nil {fmt.Println(err)}var s string =""err = validate.Var(s,"required")if err != nil {fmt.Println(err)}}
执行结果
Key: '' Error:Field validation for '' failed on the 'required' tagKey: '' Error:Field validation for '' failed on the 'required' tagKey: '' Error:Field validation for '' failed on the 'required' tag
dive
tag中出现的dive的使用,dive一般用在slice、array、map、嵌套的struct验证中,作为分隔符表示进入里面一层的验证规则
package mainimport ("fmt""gopkg.in/go-playground/validator.v9")type Container struct {Array []string `validate:"required,gt=0,dive,required"`Map map[string]string `validate:"required,gt=0,dive,keys,max=10,endkeys,required,max=100"`}func main() {validate := validator.New()var empty Containerif err := validate.Struct(empty);err!=nil{fmt.Println(err.Error())fmt.Println()}vContainer := Container{Array:[]string{""},Map:map[string]string{"test > than 10": ""}}if err := validate.Struct(vContainer);err!=nil{fmt.Println(err.Error())}}
打印结果如下
Key: 'Container.Array' Error:Field validation for 'Array' failed on the 'required' tagKey: 'Container.Map' Error:Field validation for 'Map' failed on the 'required' tagKey: 'Container.Array[0]' Error:Field validation for 'Array[0]' failed on the 'required' tagKey: 'Container.Map[test > than 10]' Error:Field validation for 'Map[test > than 10]' failed on the 'max' tagKey: 'Container.Map[test > than 10]' Error:Field validation for 'Map[test > than 10]' failed on the 'required' tag
slice使用gt=0代表field.len()>0,所以[]string{}符合required规则,但是不符合gt=0规则
dive代表进入里面一层的验证 例如 a={“b”:”c”}中 dive之前的required代表a是必填,大于0,
dive之后出现keys与endkeys之间代表验证map的keys的tag值:max=10,也就是string长度不大于10
后面的自然而然的是验证value了,required 必填并且stirng最大长度是100
关系比较
"eqfield": isEqField,"eqcsfield": isEqCrossStructField,"necsfield": isNeCrossStructField,"gtcsfield": isGtCrossStructField,"gtecsfield": isGteCrossStructField,"ltcsfield": isLtCrossStructField,"ltecsfield": isLteCrossStructField,"nefield": isNeField,"gtefield": isGteField,"gtfield": isGtField,"ltefield": isLteField,"ltfield": isLtField
自定义tag
package mainimport ("fmt""gopkg.in/go-playground/validator.v9""strings")type MyStruct struct {String string `validate:"pre-we"`}func main() {validate := validator.New()validate.RegisterValidation("pre-we", ValidateMyVal)s := MyStruct{String: "wechat"}if err := validate.Struct(s);err != nil {fmt.Printf("Err(s):\n%+v\n", err)}s.String = "not wechat"if err := validate.Struct(s);err != nil {fmt.Println(err)}}func ValidateMyVal(fl validator.FieldLevel) bool {return strings.HasPrefix(fl.Field().String(),"we")}
打印结果
Key: 'MyStruct.String' Error:Field validation for 'String' failed on the 'pre-we' tag
自定义结构体
以下例子在验证FirstName与LastName时候不能为空
package mainimport ("fmt""github.com/go-playground/validator/v10")// User contains user informationtype User struct {FirstName string `json:"fname"`LastName string `json:"lname"`Age uint8 `validate:"gte=0,lte=130"`Email string `validate:"required,email"`FavouriteColor string `validate:"hexcolor|rgb|rgba"`Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...}// Address houses a users address informationtype Address struct {Street string `validate:"required"`City string `validate:"required"`Planet string `validate:"required"`Phone string `validate:"required"`}// use a single instance of Validate, it caches struct infovar validate *validator.Validatefunc main() {validate = validator.New()// register validation for 'User'// NOTE: only have to register a non-pointer type for 'User', validator// interanlly dereferences during it's type checks.validate.RegisterStructValidation(UserStructLevelValidation, User{})// build 'User' info, normally posted data etc...address := &Address{Street: "Eavesdown Docks",Planet: "Persphone",Phone: "none",City: "Unknown",}user := &User{FirstName: "",LastName: "",Age: 45,Email: "Badger.Smith@gmail.com",FavouriteColor: "#000",Addresses: []*Address{address},}// returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError )err := validate.Struct(user)if err != nil {// this check is only needed when your code could produce// an invalid value for validation such as interface with nil// value most including myself do not usually have code like this.if _, ok := err.(*validator.InvalidValidationError); ok {fmt.Println(err)return}for _, err := range err.(validator.ValidationErrors) {fmt.Println(err.Namespace())fmt.Println(err.Field())fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered orfmt.Println(err.StructField()) // by passing alt name to ReportError like belowfmt.Println(err.Tag())fmt.Println(err.ActualTag())fmt.Println(err.Kind())fmt.Println(err.Type())fmt.Println(err.Value())fmt.Println(err.Param())fmt.Println()}// from here you can create your own error messages in whatever language you wishreturn}// save user to database}// UserStructLevelValidation contains custom struct level validations that don't always// make sense at the field validation level. For Example this function validates that either// FirstName or LastName exist; could have done that with a custom field validation but then// would have had to add it to both fields duplicating the logic + overhead, this way it's// only validated once.//// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way// hooks right into validator and you can combine with validation tags and still have a// common error output format.func UserStructLevelValidation(sl validator.StructLevel) {user := sl.Current().Interface().(User)if len(user.FirstName) == 0 && len(user.LastName) == 0 {sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "")sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "")}// plus can do more, even with different tag than "fnameorlname"}
国际化处理
package mainimport ("fmt""github.com/go-playground/locales/en"ut "github.com/go-playground/universal-translator""github.com/go-playground/validator/v10"en_translations "github.com/go-playground/validator/v10/translations/en")// User contains user informationtype User struct {FirstName string `validate:"required"`LastName string `validate:"required"`Age uint8 `validate:"gte=0,lte=130"`Email string `validate:"required,email"`FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla'Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...}// Address houses a users address informationtype Address struct {Street string `validate:"required"`City string `validate:"required"`Planet string `validate:"required"`Phone string `validate:"required"`}// use a single instance , it caches struct infovar (uni *ut.UniversalTranslatorvalidate *validator.Validate)func main() {// NOTE: ommitting allot of error checking for brevityen := en.New()uni = ut.New(en, en)// this is usually know or extracted from http 'Accept-Language' header// also see uni.FindTranslator(...)trans, _ := uni.GetTranslator("en")validate = validator.New()en_translations.RegisterDefaultTranslations(validate, trans)translateAll(trans)translateIndividual(trans)translateOverride(trans) // yep you can specify your own in whatever locale you want!}func translateAll(trans ut.Translator) {type User struct {Username string `validate:"required"`Tagline string `validate:"required,lt=10"`Tagline2 string `validate:"required,gt=1"`}user := User{Username: "Joeybloggs",Tagline: "This tagline is way too long.",Tagline2: "1",}err := validate.Struct(user)if err != nil {// translate all error at onceerrs := err.(validator.ValidationErrors)// returns a map with key = namespace & value = translated error// NOTICE: 2 errors are returned and you'll see something surprising// translations are i18n aware!!!!// eg. '10 characters' vs '1 character'fmt.Println(errs.Translate(trans))}}func translateIndividual(trans ut.Translator) {type User struct {Username string `validate:"required"`}var user Usererr := validate.Struct(user)if err != nil {errs := err.(validator.ValidationErrors)for _, e := range errs {// can translate each error one at a time.fmt.Println(e.Translate(trans))}}}func translateOverride(trans ut.Translator) {validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details}, func(ut ut.Translator, fe validator.FieldError) string {t, _ := ut.T("required", fe.Field())return t})type User struct {Username string `validate:"required"`}var user Usererr := validate.Struct(user)if err != nil {errs := err.(validator.ValidationErrors)for _, e := range errs {// can translate each error one at a time.fmt.Println(e.Translate(trans))}}}
Gin 中v8升级
package mainimport ("reflect""sync""github.com/gin-gonic/gin/binding""github.com/go-playground/validator/v10")type defaultValidator struct {once sync.Oncevalidate *validator.Validate}var _ binding.StructValidator = &defaultValidator{}func (v *defaultValidator) ValidateStruct(obj interface{}) error {if kindOfData(obj) == reflect.Struct {v.lazyinit()if err := v.validate.Struct(obj); err != nil {return error(err)}}return nil}func (v *defaultValidator) Engine() interface{} {v.lazyinit()return v.validate}func (v *defaultValidator) lazyinit() {v.once.Do(func() {v.validate = validator.New()v.validate.SetTagName("binding")// add any custom validations etc. here})}func kindOfData(data interface{}) reflect.Kind {value := reflect.ValueOf(data)valueType := value.Kind()if valueType == reflect.Ptr {valueType = value.Elem().Kind()}return valueType}
在main 函数中调用
package mainimport "github.com/gin-gonic/gin/binding"func main() {binding.Validator = new(defaultValidator)// regular gin logic}
参考
https://www.cnblogs.com/zhzhlong/p/10033234.html
https://frankhitman.github.io/zh-CN/gin-validator/
