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