-
博文分类专栏
- Jquery基础教程
-
- 文章:(15)篇
- 阅读:46657
- shell命令
-
- 文章:(42)篇
- 阅读:154566
- Git教程
-
- 文章:(36)篇
- 阅读:235286
- leetCode刷题
-
- 文章:(76)篇
- 阅读:132514
-
关于go-playground/validator源码分析2019-03-19 14:32 阅读(5488) 评论(0)
一、简介
针对go语言,使用比较多的验证包是go-playground/validator,之前也在不少项目中用过,但是一直没深入看一下源码,只是停了表面,知道其是利用反射获取tag,然后在验证。针对程序员来说,知其然,还有知其所以然,于是就来过一波其源码。废话不多少,先来个简单的验证,如下:
type Student struct { Age int64 `validate:"required,min=5"` Name string `validate:"required"` } func main() { validate := validator.New() // 创建验证器 s := Student{ Age: 3, Name: "a", } err := validate.Struct(s) // 执行验证 if err != nil { fmt.Println("check err", err) return } fmt.Println("pass") }
通过上面的案例,我们可以发现,validator使用还是比较简洁的。在验证的过程中,涉及的对象主要有如下:
验证流程如下:
1、调用 Validate对象中Struct方法
2、调用 validate对象中validateStruct方法
// parent and current will be the same the first run of validateStruct func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { // 1、获取 structCache 对象,该对象中保存着结构体的所有字段,每个字段里面包含tags对象链,tags对象中包含验证方法 cs, ok := v.v.structCache.Get(typ) if !ok { cs = v.v.extractStructCache(current, typ.Name()) } // 省略部分代码 // ct is nil on top level struct, and structs as fields that have no tag info // so if nil or if not nil and the structonly tag isn't present if ct == nil || ct.typeof != typeStructOnly { var f *cField // 遍历每个字段 for i := 0; i < len(cs.fields); i++ { f = cs.fields[i] // 省略部分代码 // ctx 上下文 // parent // current.Field(f.idx) 当前字段 // 针对每个字段的tags进行验证方 v.traverseField(ctx, parent, current.Field(f.idx), ns, structNs, f, f.cTags) } } // 省略部分代码 } // traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { var typ reflect.Type var kind reflect.Kind current, kind, v.fldIsPointer = v.extractTypeInternal(current, false) switch kind { // 省略部分代码 case reflect.Struct: typ = current.Type() if typ != timeType { if ct != nil { if ct.typeof == typeStructOnly { goto CONTINUE } else if ct.typeof == typeIsDefault { // set Field Level fields v.slflParent = parent v.flField = current v.cf = cf v.ct = ct if !ct.fn(ctx, v) { // 调用tag对应的函数验证 v.str1 = string(append(ns, cf.altName...)) if v.v.hasTagNameFunc { v.str2 = string(append(structNs, cf.name...)) } else { v.str2 = v.str1 } v.errs = append(v.errs, &fieldError{ v: v.v, tag: ct.aliasTag, actualTag: ct.tag, ns: v.str1, structNs: v.str2, fieldLen: uint8(len(cf.altName)), structfieldLen: uint8(len(cf.name)), value: current.Interface(), param: ct.param, kind: kind, typ: typ, }, ) return } } ct = ct.next } if ct != nil && ct.typeof == typeNoStructLevel { return } CONTINUE: // if len == 0 then validating using 'Var' or 'VarWithValue' // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... // VarWithField - this allows for validating against each field within the struct against a specific value // pretty handy in certain situations if len(cf.name) > 0 { ns = append(append(ns, cf.altName...), '.') structNs = append(append(structNs, cf.name...), '.') } v.validateStruct(ctx, current, current, typ, ns, structNs, ct) return } } // 省略部分代码 }
下面,我打算从三个方面,来分析go-playground/validator源码验证流程。
二、验证管理器的创建
验证管理器Validate创建函数New如下:
func New() *Validate { tc := new(tagCache) tc.m.Store(make(map[string]*cTag)) sc := new(structCache) sc.m.Store(make(map[reflect.Type]*cStruct)) v := &Validate{ tagName: defaultTagName, // tag名字,只会对tag名字为 defaultTagName 的才会进行验证 aliases: make(map[string]string, len(bakedInAliases)), // 别名 validations: make(map[string]internalValidationFuncWrapper, len(bakedInValidators)),// 验证函数 tagCache: tc, structCache: sc, } // 注册别名 // must copy alias validators for separate validations to be used in each validator instance for k, val := range bakedInAliases { v.RegisterAlias(k, val) } // 注册默认的验证器,后续会会分发到Ctag上面 // must copy validators for separate validations to be used in each instance for k, val := range bakedInValidators { switch k { // these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour case requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag: _ = v.registerValidation(k, wrapFunc(val), true, true) default: // no need to error check here, baked in will always be valid _ = v.registerValidation(k, wrapFunc(val), true, false) } } // 注册 validate 构造器 v.pool = &sync.Pool{ New: func() interface{} { return &validate{ v: v, ns: make([]byte, 0, 64), actualNs: make([]byte, 0, 64), misc: make([]byte, 32), } }, } return v }
在New函数中,我们要关注两点
1、验证函数的注册,如下:
bakedInValidators = map[string]Func{ "required": hasValue, "required_with": requiredWith, "required_with_all": requiredWithAll, "required_without": requiredWithout, "required_without_all": requiredWithoutAll, "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, "fieldcontains": fieldContains, "fieldexcludes": fieldExcludes, "alpha": isAlpha, "alphanum": isAlphanum, "alphaunicode": isAlphaUnicode, "alphanumunicode": isAlphanumUnicode, "numeric": isNumeric, "number": isNumber, "hexadecimal": isHexadecimal, "hexcolor": isHEXColor, "rgb": isRGB, "rgba": isRGBA, "hsl": isHSL, "hsla": isHSLA, "e164": isE164, "email": isEmail, "url": isURL, "uri": isURI, "urn_rfc2141": isUrnRFC2141, // RFC 2141 "file": isFile, "base64": isBase64, "base64url": isBase64URL, "contains": contains, "containsany": containsAny, "containsrune": containsRune, "excludes": excludes, "excludesall": excludesAll, "excludesrune": excludesRune, "startswith": startsWith, "endswith": endsWith, "startsnotwith": startsNotWith, "endsnotwith": endsNotWith, "isbn": isISBN, "isbn10": isISBN10, "isbn13": isISBN13, "eth_addr": isEthereumAddress, "btc_addr": isBitcoinAddress, "btc_addr_bech32": isBitcoinBech32Address, "uuid": isUUID, "uuid3": isUUID3, "uuid4": isUUID4, "uuid5": isUUID5, "uuid_rfc4122": isUUIDRFC4122, "uuid3_rfc4122": isUUID3RFC4122, "uuid4_rfc4122": isUUID4RFC4122, "uuid5_rfc4122": isUUID5RFC4122, "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, "dir": isDir, "json": isJSON, "hostname_port": isHostnamePort, "lowercase": isLowercase, "uppercase": isUppercase, "datetime": isDatetime, }
2、利用 sync.pool来管理 validate对象,如下
三、获取结构体元数据
获取结构体元数据,也就是创建cStruct的过程,结构体如下:
// 结构体 type cStruct struct { name string fields []*cField fn StructLevelFuncCtx } // 结构体里面的字段 type cField struct { idx int name string altName string namesEqual bool cTags *cTag } // 字段对应的tags type cTag struct { tag string aliasTag string actualAliasTag string param string keys *cTag // only populated when using tag's 'keys' and 'endkeys' for map key validation next *cTag // 链表存储 fn FuncCtx typeof tagType hasTag bool hasAlias bool hasParam bool // true if parameter used eg. eq= where the equal sign has been set isBlockEnd bool // indicates the current tag represents the last validation in the block runValidationWhenNil bool }
如何获取tags呢?
利用reflect.TypeOs()获取结构体的类型typ,然后调用typ.Field()遍历,获取每个字段信息,结构体如下:
type StructField struct { Name string // 字段名称 PkgPath string // 字段首字母开头,则为包路径,字段首字母大写时,则为空 Type Type // field type Tag StructTag // field tag string Offset uintptr // offset within struct, in bytes Index []int // index sequence for Type.FieldByIndex Anonymous bool // is an embedded field //StructTag }
然后,调用 structField 的中StructTag,针对validate ,使用的tagName为validate,cache.go中131行如下:
tag = fld.Tag.Get(v.tagName) //v.tagName为validate if tag == skipValidationTag { // skipValidationTag为"-",代表忽略,不需要验证 continue }
获取到tag(格式如required,min=5)后,我们需要生成validate中cTag结构,这个时候就需要分隔
tags := strings.Split(tag, tagSeparator) // tagSeparator 是","
分隔后tag,数组元素一般为 required 或 min=5这种格式,然后再次分隔,获取验证函数。
四、执行验证
验证过程比较简单,遍历结构体的Filed,遍历每个Filed上面绑定的tag,然后将当前字段的值,传递tag绑定的验证函数即可,如下:
备注:
validate使用的版本为v10.3.0