首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >echo 源码分析(validator)

echo 源码分析(validator)

作者头像
golangLeetcode
发布2022-08-02 19:13:03
发布2022-08-02 19:13:03
97800
代码可运行
举报
运行总次数:0
代码可运行

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

代码语言:javascript
代码运行次数:0
运行
复制
Echo struct {
    Validator        Validator
}

validator需要实现Validate接口

代码语言:javascript
代码运行次数:0
运行
复制
Validator interface {
    Validate(i interface{}) error
}

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

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

e.Validator := validator.New()

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

代码语言:javascript
代码运行次数:0
运行
复制
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了

代码语言:javascript
代码运行次数:0
运行
复制
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包含哪些文件

代码语言:javascript
代码运行次数:0
运行
复制
% ls
LICENSE
Makefile
README.md
_examples
non-standard
testdata
translations


baked_in.go
benchmarks_test.go
cache.go
country_codes.go
doc.go
errors.go
field_level.go
go.mod
go.sum
logo.png
regexes.go
struct_level.go
translations.go
util.go
validator.go
validator_instance.go
validator_test.go


 % ls non-standard/validators/
notblank.go
notblank_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 //别名校验器 【别名匹配规则组合】-> 校验规则

其中比较重要的就是

代码语言:javascript
代码运行次数:0
运行
复制
validator.go
validator_instance.go

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

代码语言:javascript
代码运行次数:0
运行
复制
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的结构:

代码语言:javascript
代码运行次数:0
运行
复制
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链表存储的规则:

代码语言:javascript
代码运行次数:0
运行
复制
type cField struct {
  Idx     int    //字段下标
  Name    string //字段名
  AltName string //
  cTags   *cTag  //Field对应的cTag规则,是一个链表(一串的规则).
}

Ⅲ.cStruct(结构体规则)

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

代码语言:javascript
代码运行次数:0
运行
复制
type cStruct struct {
  Name   string          //结构体名称
  fields map[int]*cField //结构体对应的字段map
  fn     StructLevelFunc //结构体校验器 (结构体类型->结构体校验器)
}

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

1,获取结构体的value

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

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

代码语言:javascript
代码运行次数:0
运行
复制
func (v *Validate) Struct(s interface{}) error {
  return v.StructCtx(context.Background(), s)
}
代码语言:javascript
代码运行次数:0
运行
复制
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中的,看一下具体的数据结构:

代码语言:javascript
代码运行次数:0
运行
复制
// Validate contains the validator settings passed in using the Config struct
type 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

代码语言:javascript
代码运行次数:0
运行
复制
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,传入结构体数据进行处理

代码语言:javascript
代码运行次数:0
运行
复制
func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) {
}
代码语言:javascript
代码运行次数:0
运行
复制
func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { 
}

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

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

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

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

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

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

1.structCache(结构体缓存)

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

代码语言:javascript
代码运行次数:0
运行
复制
type structCache struct {
  lock sync.Mutex
  m    atomic.Value // map[reflect.Type]*cStruct
}

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

代码语言:javascript
代码运行次数:0
运行
复制
func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
  c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
  return
  }
代码语言:javascript
代码运行次数:0
运行
复制
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】之间的对应关系,考虑并发的问题,这边是进行加锁存储:

代码语言:javascript
代码运行次数:0
运行
复制
type tagCache struct {
  lock sync.Mutex
  m    atomic.Value // map[string]*cTag
}

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

代码语言:javascript
代码运行次数:0
运行
复制
func (tc *tagCache) Get(key string) (c *cTag, found bool) {
  c, found = tc.m.Load().(map[string]*cTag)[key]
  return
}
代码语言:javascript
代码运行次数:0
运行
复制
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)
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档