echo 源码分析之校验器:Validator

Go语言精选

共 10019字,需浏览 21分钟

 ·

2021-05-10 17:57

echo 默认没有自己的validator,只提供了接口,需要自己实现

Echo struct {    Validator        Validator}

validator需要实现Validate接口

Validator interface {    Validate(i interface{}) error}

所以我们可以包装一下go-playground/validator来实现echo的validator

由于go-playground/validator并没有实现Validate方法,所以不能直接赋值

e.Validator := validator.New()

如何实现呢,可以自定义一个接口,中间调用validate.Struct(i)方法

package main
import ( "sync"
"github.com/go-playground/validator/v10")
type CustomValidator struct { once sync.Once validate *validator.Validate}
func (c *CustomValidator) Validate(i interface{}) error { c.lazyInit() return c.validate.Struct(i)}
func (c *CustomValidator) lazyInit() { c.once.Do(func() { c.validate = validator.New() })}
func NewCustomValidator() *CustomValidator { return &CustomValidator{}}

接着就可以赋值给echo的validator了

package main
import ( "fmt" "net/http"
"github.com/labstack/echo/v4")
type User struct { Name string `json:"name" param:"name" query:"name" form:"name" xml:"name" validate:"required"` // //curl -XGET http://localhost:1323/users/Joe\?email\=joe_email Email string `json:"email" form:"email" query:"email"`}
func main() { e := echo.New() e.Validator = NewCustomValidator() e.GET("/users/:name", func(c echo.Context) error { u := new(User) u.Name = c.Param("name") if err := c.Bind(u); err != nil { return c.JSON(http.StatusBadRequest, nil) }
if err := c.Validate(u); err != nil { return c.JSON(http.StatusBadRequest, nil) } return c.JSON(http.StatusOK, u) }) fmt.Println(e.Start(":1336"))}

我们看下go-playground/validator包含哪些文件

% lsLICENSEMakefileREADME.md_examplesnon-standardtestdatatranslations

baked_in.gobenchmarks_test.gocache.gocountry_codes.godoc.goerrors.gofield_level.gogo.modgo.sumlogo.pngregexes.gostruct_level.gotranslations.goutil.govalidator.govalidator_instance.govalidator_test.go

% ls non-standard/validators/notblank.gonotblank_test.go

主要是下面几个部分:

baked_in.go :定义默认【标签校验器】和【别名校验器】,程序初始的时候直接赋值了默认的校验器,相当于你买了个机器人送几根电池的行为。当然这边你的校验器可以手动添加自定义,后面会说到

cache.go:定义结构体校验器缓存、字段校验器缓存和获取的方法,一个validator对象如果一直存活,他会把之前处理过的结构体或者字段校验器进行缓存.

regexes.go:【标签校验器】里面有一些使用到正则进行校验的,这边存储的就是静态的正则表达式

util.go:工具类,一般是用在【标签校验器】里面进行处理

validator.go:校验类主体,提供四个主要的校验方法


validator一共提供了四种校验器:

validationFuncs     map[string]Func  //规则类型的校验 【tag标签】->        校验规则

structLevelFuncs    map[reflect.Type]StructLevelFunc  //规则结构体的校验       【结构体类型】->        校验规则

customTypeFuncs     map[reflect.Type]CustomTypeFunc  //类型校验器                   【数据类型】->         校验规则

aliasValidators     map[string]string                                     //别名校验器                   【别名匹配规则组合】->   校验规则


其中比较重要的就是

validator.govalidator_instance.go

这两个文件,在validator_instance.go中的方法是公有的

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, 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) }
// 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 requiredIfTag, requiredUnlessTag, 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) } }
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}

1,先创建了两个校验器缓存(缓存思想)

2,设置validator的标签、缓存信息等

3,注册默认校验器

4,注册默认tag校验器

5,返回validator

由于validate是每一个请求都需要的高频操作,所以非常关注性能,尽量使用缓存。

校验器结构体

Ⅰ.cTag(tag规则)

       cTag是一个链表,存储一连串的相关联tag的校验器,比如说这边是作为存储一个Field的相关所有标签,看一下cTag的结构:

type cTag struct {  tag            string  //标签  aliasTag       string  //  actualAliasTag string  //  param          string  //如果是比较类型的标签,这里存放的是比较的值,比如说 min=10,这里存放的是【10】这个值  hasAlias       bool    //是否有别名校验器标签  typeof         tagType //对应的tagType  hasTag         bool    //是否存在tag标签  fn             Func    //当前cTag对应的【tag标签校验器】  next           *cTag   //下一个cTag标签}

Ⅱ.cFeild(字段规则)

      cField代表一个结构体字段对应的规则,他会包含这个结构体字段对应的所有tag规则,也就是一组cTag链表存储的规则:

type cField struct {  Idx     int    //字段下标  Name    string //字段名  AltName string //  cTags   *cTag  //Field对应的cTag规则,是一个链表(一串的规则).}

Ⅲ.cStruct(结构体规则)

     同理,cStruct对应的是这个结构体所有字段(包含tag规则),以及这个结构体自己对应的【StructLevelFunc】的校验方法:

type cStruct struct {  Name   string          //结构体名称  fields map[int]*cField //结构体对应的字段map  fn     StructLevelFunc //结构体校验器 (结构体类型->结构体校验器)}

可以看下调用Struct来进行验证的过程

1,获取结构体的value

2,通过value,进行结构体校验

3,获取err池错误信息返回

func (v *Validate) Struct(s interface{}) error {  return v.StructCtx(context.Background(), s)}


func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) {  vd := v.pool.Get().(*validate)  vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil)}

其中 validate是校验类的主体,所有的注册和缓存数据、错误信息数据都是存储在validate中的,看一下具体的数据结构:

// Validate contains the validator settings passed in using the Config structtype Validate struct {  tagName             string                           //校验起作用的tag名  fieldNameTag        string                           //  validationFuncs     map[string]Func                  //规则类型的校验      【tag标签】->      校验规则  structLevelFuncs    map[reflect.Type]StructLevelFunc //规则结构体的校验        【结构体类型】->      校验规则  customTypeFuncs     map[reflect.Type]CustomTypeFunc  //类型校验器          【数据类型】->      校验规则  aliasValidators     map[string]string                //别名校验器           【别名匹配规则组合】->   校验规则  hasCustomFuncs      bool                             //是否存在类型校验器  hasAliasValidators  bool                             //是否有别名校验器  hasStructLevelFuncs bool                             //是否有结构体校验器  tagCache            *tagCache                        //tag对应的【校验规则方法】的缓存  structCache         *structCache                     //结构体对应的【校验规则方法】的缓存  errsPool            *sync.Pool                       //校验错误奖池}

定义在validator_instance.go

type validate struct {  v              *Validate  top            reflect.Value  ns             []byte  actualNs       []byte  errs           ValidationErrors  includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise  ffn            FilterFunc  slflParent     reflect.Value // StructLevel & FieldLevel  slCurrent      reflect.Value // StructLevel & FieldLevel  flField        reflect.Value // StructLevel & FieldLevel  cf             *cField       // StructLevel & FieldLevel  ct             *cTag         // StructLevel & FieldLevel  misc           []byte        // misc reusable  str1           string        // misc reusable  str2           string        // misc reusable  fldIsPointer   bool          // StructLevel & FieldLevel  isPartial      bool  hasExcludes    bool}

定义在validator.go

在validator.go中还有两个方法

1,获取结构体的数据

2,判断是否为结构体类型或者接口类型,不是的话直接进行报错处理

3,传入结构体数据进行处理

func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) {}

func (v *validatetraverseField(ctx context.Contextparent reflect.Valuecurrent reflect.Valuens []bytestructNs []bytecf *cFieldct *cTag) { }

1,获取结构体校验器缓存,如果没有获取到,则对该结构体进行解析,然后返回对应的校验器,否则往下

2,判断是否存在校验器,否则忽略该字段校验(这边有一个判断是first的判断,如果是第一层,也就是传进来的机构体一定会对它的Field进行校验)

2,循环所有的Field,判断Field是否包含在不校验的集合中,如果不包含则进行校验,包含则不校验(这边通过传入一个【includeExclude】的map结构可以指定对哪些字段不进行校验.这个在【StructExcept】方法中会用到)

3,判断是否存在对应的结构体类型校验方法,如果存在则调用该方法进行校验


整个验证的过程就是利用反射和struct tag中定义的一些语法扩展,对参数的值进行校验。

在很多工具类里面对于可能多次出现的东西都会进行相应的缓存处理,这边也不例外,对于一个Validator。它可能会进行多次校验,那么可能会有重复的结构体或者字段数据,可以进行缓存不需要下次再提取,所以这边提供了两个对应的缓存。

1.structCache(结构体缓存)

      这个缓存存储的就是 【结构体类型】->【cStruct】之间的对应关系,考虑并发的问题,这边是进行加锁存储:

type structCache struct {  lock sync.Mutex  m    atomic.Value // map[reflect.Type]*cStruct}

对应的缓存方法包含 Get、Set:

func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {  c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]  return  }
func (sc *structCache) Set(key reflect.Type, value *cStruct) {   m := sc.m.Load().(map[reflect.Type]*cStruct)   nm := make(map[reflect.Type]*cStruct, len(m)+1)  for k, v := range m {    nm[k] = v  }  nm[key] = value  sc.m.Store(nm)}

2.tagCache(标签规则缓存)

      这个缓存存储的就是 【tag】 ->【cTag】之间的对应关系,考虑并发的问题,这边是进行加锁存储:

type tagCache struct {  lock sync.Mutex  m    atomic.Value // map[string]*cTag}

对应的缓存方法包含 Get、Set:

func (tc *tagCache) Get(key string) (c *cTag, found bool) {  c, found = tc.m.Load().(map[string]*cTag)[key]  return}
func (tc *tagCache) Set(key string, value *cTag) {   m := tc.m.Load().(map[string]*cTag)   nm := make(map[string]*cTag, len(m)+1)  for k, v := range m {    nm[k] = v  }  nm[key] = value  tc.m.Store(nm)}



推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。


浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报