echo 源碼分析(validator)

echo 默認沒有自己的 validator 只提供了接口,需要自己實現

validator 需要實現 Validate 接口

1Validator interface {
2    Validate(i interface{}) error
3}
4

所以我們可以包裝一下 go-playground/validator 來實現 echo 的 validator

由於 go-playground/validator 並沒有實現 Validate 方法,所以不能直接賦值

e.Validator := validator.New()

如何實現呢,可以自定義一個接口,中間調用 validate.Struct(i) 方法

 1package main
 2import (
 3  "sync"
 4  "github.com/go-playground/validator/v10"
 5)
 6type CustomValidator struct {
 7  once     sync.Once
 8  validate *validator.Validate
 9}
10func (c *CustomValidator) Validate(i interface{}) error {
11  c.lazyInit()
12  return c.validate.Struct(i)
13}
14func (c *CustomValidator) lazyInit() {
15  c.once.Do(func() {
16    c.validate = validator.New()
17  })
18}
19func NewCustomValidator() *CustomValidator {
20  return &CustomValidator{}
21}
22

接着就可以賦值給 echo 的 validator 了

 1package main
 2import (
 3  "fmt"
 4  "net/http"
 5  "github.com/labstack/echo/v4"
 6)
 7type User struct {
 8  Name  string `json:"name" param:"name" query:"name" form:"name" xml:"name" validate:"required"` //  //curl -XGET http://localhost:1323/users/Joe\?email\=joe_email
 9  Email string `json:"email" form:"email" query:"email"`
10}
11func main() {
12  e := echo.New()
13  e.Validator = NewCustomValidator()
14  e.GET("/users/:name", func(c echo.Context) error {
15    u := new(User)
16    u.Name = c.Param("name")
17    if err := c.Bind(u); err != nil {
18      return c.JSON(http.StatusBadRequest, nil)
19    }
20    if err := c.Validate(u); err != nil {
21      return c.JSON(http.StatusBadRequest, nil)
22    }
23    return c.JSON(http.StatusOK, u)
24  })
25  fmt.Println(e.Start(":1336"))
26}
27

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

 1% ls
 2LICENSE
 3Makefile
 4README.md
 5_examples
 6non-standard
 7testdata
 8translations
 9baked_in.go
10benchmarks_test.go
11cache.go
12country_codes.go
13doc.go
14errors.go
15field_level.go
16go.mod
17go.sum
18logo.png
19regexes.go
20struct_level.go
21translations.go
22util.go
23validator.go
24validator_instance.go
25validator_test.go
26 % ls non-standard/validators/
27notblank.go
28notblank_test.go
29

主要是下面幾個部分:

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                                     // 別名校驗器                   【別名匹配規則組合】->   校驗規則

其中比較重要的就是

1validator.go
2validator_instance.go
3

這兩個文件,在 validator_instance.go 中的方法是公有的

 1func New() *Validate {
 2  tc := new(tagCache)
 3  tc.m.Store(make(map[string]*cTag))
 4  sc := new(structCache)
 5  sc.m.Store(make(map[reflect.Type]*cStruct))
 6  v := &Validate{
 7    tagName:     defaultTagName,
 8    aliases:     make(map[string]string, len(bakedInAliases)),
 9    validations: make(map[string]internalValidationFuncWrapper, len(bakedInValidators)),
10    tagCache:    tc,
11    structCache: sc,
12  }
13  // must copy alias validators for separate validations to be used in each validator instance
14  for k, val := range bakedInAliases {
15    v.RegisterAlias(k, val)
16  }
17  // must copy validators for separate validations to be used in each instance
18  for k, val := range bakedInValidators {
19    switch k {
20    // these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour
21    case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag:
22      _ = v.registerValidation(k, wrapFunc(val), true, true)
23    default:
24      // no need to error check here, baked in will always be valid
25      _ = v.registerValidation(k, wrapFunc(val), true, false)
26    }
27  }
28  v.pool = &sync.Pool{
29    New: func() interface{} {
30      return &validate{
31        v:        v,
32        ns:       make([]byte, 0, 64),
33        actualNs: make([]byte, 0, 64),
34        misc:     make([]byte, 32),
35      }
36    },
37  }
38  return v
39}
40

1,先創建了兩個校驗器緩存(緩存思想)

2,設置 validator 的標籤、緩存信息等

3,註冊默認校驗器

4,註冊默認 tag 校驗器

5,返回 validator

由於 validate 是每一個請求都需要的高頻操作,所以非常關注性能,儘量使用緩存。

校驗器結構體

Ⅰ.cTag(tag 規則)

       cTag 是一個鏈表,存儲一連串的相關聯 tag 的校驗器,比如說這邊是作爲存儲一個 Field 的相關所有標籤,看一下 cTag 的結構:

 1type cTag struct {
 2  tag            string  //標籤
 3  aliasTag       string  //
 4  actualAliasTag string  //
 5  param          string  //如果是比較類型的標籤,這裏存放的是比較的值,比如說 min=10,這裏存放的是【10】這個值
 6  hasAlias       bool    //是否有別名校驗器標籤
 7  typeof         tagType //對應的tagType
 8  hasTag         bool    //是否存在tag標籤
 9  fn             Func    //當前cTag對應的【tag標籤校驗器】
10  next           *cTag   //下一個cTag標籤
11}
12

Ⅱ.cFeild(字段規則)

      cField 代表一個結構體字段對應的規則,他會包含這個結構體字段對應的所有 tag 規則,也就是一組 cTag 鏈表存儲的規則:

1type cField struct {
2  Idx     int    //字段下標
3  Name    string //字段名
4  AltName string //
5  cTags   *cTag  //Field對應的cTag規則,是一個鏈表(一串的規則).
6}
7

Ⅲ.cStruct(結構體規則)

     同理,cStruct 對應的是這個結構體所有字段(包含 tag 規則),以及這個結構體自己對應的【StructLevelFunc】的校驗方法:

1type cStruct struct {
2  Name   string          //結構體名稱
3  fields map[int]*cField //結構體對應的字段map
4  fn     StructLevelFunc //結構體校驗器 (結構體類型->結構體校驗器)
5}
6

可以看下調用 Struct 來進行驗證的過程

1, 獲取結構體的 value

2, 通過 value,進行結構體校驗

3, 獲取 err 池錯誤信息返回

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

其中 validate 是校驗類的主體,所有的註冊和緩存數據、錯誤信息數據都是存儲在 validate 中的,看一下具體的數據結構:

 1// Validate contains the validator settings passed in using the Config struct
 2type Validate struct {
 3  tagName             string                           //校驗起作用的tag名
 4  fieldNameTag        string                           //
 5  validationFuncs     map[string]Func                  //規則類型的校驗      【tag標籤】->      校驗規則
 6  structLevelFuncs    map[reflect.Type]StructLevelFunc //規則結構體的校驗        【結構體類型】->      校驗規則
 7  customTypeFuncs     map[reflect.Type]CustomTypeFunc  //類型校驗器          【數據類型】->      校驗規則
 8  aliasValidators     map[string]string                //別名校驗器           【別名匹配規則組合】->   校驗規則
 9  hasCustomFuncs      bool                             //是否存在類型校驗器
10  hasAliasValidators  bool                             //是否有別名校驗器
11  hasStructLevelFuncs bool                             //是否有結構體校驗器
12  tagCache            *tagCache                        //tag對應的【校驗規則方法】的緩存
13  structCache         *structCache                     //結構體對應的【校驗規則方法】的緩存
14  errsPool            *sync.Pool                       //校驗錯誤獎池
15}
16

定義在 validator_instance.go

 1type validate struct {
 2  v              *Validate
 3  top            reflect.Value
 4  ns             []byte
 5  actualNs       []byte
 6  errs           ValidationErrors
 7  includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise
 8  ffn            FilterFunc
 9  slflParent     reflect.Value // StructLevel & FieldLevel
10  slCurrent      reflect.Value // StructLevel & FieldLevel
11  flField        reflect.Value // StructLevel & FieldLevel
12  cf             *cField       // StructLevel & FieldLevel
13  ct             *cTag         // StructLevel & FieldLevel
14  misc           []byte        // misc reusable
15  str1           string        // misc reusable
16  str2           string        // misc reusable
17  fldIsPointer   bool          // StructLevel & FieldLevel
18  isPartial      bool
19  hasExcludes    bool
20}
21

定義在 validator.go

在 validator.go 中還有兩個方法

1, 獲取結構體的數據

2, 判斷是否爲結構體類型或者接口類型,不是的話直接進行報錯處理

3, 傳入結構體數據進行處理

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

1, 獲取結構體校驗器緩存,如果沒有獲取到,則對該結構體進行解析,然後返回對應的校驗器,否則往下

2, 判斷是否存在校驗器,否則忽略該字段校驗(這邊有一個判斷是 first 的判斷,如果是第一層,也就是傳進來的機構體一定會對它的 Field 進行校驗)

2, 循環所有的 Field,判斷 Field 是否包含在不校驗的集合中,如果不包含則進行校驗,包含則不校驗(這邊通過傳入一個【includeExclude】的 map 結構可以指定對哪些字段不進行校驗. 這個在【StructExcept】方法中會用到)

3, 判斷是否存在對應的結構體類型校驗方法,如果存在則調用該方法進行校驗

整個驗證的過程就是利用反射和 struct tag 中定義的一些語法擴展,對參數的值進行校驗。

在很多工具類裏面對於可能多次出現的東西都會進行相應的緩存處理,這邊也不例外,對於一個 Validator。它可能會進行多次校驗,那麼可能會有重複的結構體或者字段數據,可以進行緩存不需要下次再提取,所以這邊提供了兩個對應的緩存。

1.structCache(結構體緩存)

      這個緩存存儲的就是 【結構體類型】->【cStruct】之間的對應關係,考慮併發的問題,這邊是進行加鎖存儲:

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

對應的緩存方法包含 Get、Set:

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

2.tagCache(標籤規則緩存)

      這個緩存存儲的就是 【tag】 ->【cTag】之間的對應關係,考慮併發的問題,這邊是進行加鎖存儲:

1type tagCache struct {
2  lock sync.Mutex
3  m    atomic.Value // map[string]*cTag
4}
5

對應的緩存方法包含 Get、Set:

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

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_FUiYKEkmQRyhlxArr2bbw