echo 源碼分析之校驗(yàn)器:Validator
echo 默認(rèn)沒有自己的validator,只提供了接口,需要自己實(shí)現(xiàn)
Echo struct {Validator Validator}
validator需要實(shí)現(xiàn)Validate接口
Validator interface {Validate(i interface{}) error}
所以我們可以包裝一下go-playground/validator來實(shí)現(xiàn)echo的validator
由于go-playground/validator并沒有實(shí)現(xiàn)Validate方法,所以不能直接賦值
e.Validator := validator.New()
如何實(shí)現(xiàn)呢,可以自定義一個(gè)接口,中間調(diào)用validate.Struct(i)方法
package mainimport ("sync""github.com/go-playground/validator/v10")type CustomValidator struct {once sync.Oncevalidate *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 mainimport ("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_emailEmail 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-standardtestdatatranslationsbaked_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
主要是下面幾個(gè)部分:
baked_in.go :定義默認(rèn)【標(biāo)簽校驗(yàn)器】和【別名校驗(yàn)器】,程序初始的時(shí)候直接賦值了默認(rèn)的校驗(yàn)器,相當(dāng)于你買了個(gè)機(jī)器人送幾根電池的行為。當(dāng)然這邊你的校驗(yàn)器可以手動(dòng)添加自定義,后面會(huì)說到
cache.go:定義結(jié)構(gòu)體校驗(yàn)器緩存、字段校驗(yàn)器緩存和獲取的方法,一個(gè)validator對象如果一直存活,他會(huì)把之前處理過的結(jié)構(gòu)體或者字段校驗(yàn)器進(jìn)行緩存.
regexes.go:【標(biāo)簽校驗(yàn)器】里面有一些使用到正則進(jìn)行校驗(yàn)的,這邊存儲(chǔ)的就是靜態(tài)的正則表達(dá)式
util.go:工具類,一般是用在【標(biāo)簽校驗(yàn)器】里面進(jìn)行處理
validator.go:校驗(yàn)類主體,提供四個(gè)主要的校驗(yàn)方法
validator一共提供了四種校驗(yàn)器:
validationFuncs map[string]Func //規(guī)則類型的校驗(yàn) 【tag標(biāo)簽】-> 校驗(yàn)規(guī)則
structLevelFuncs map[reflect.Type]StructLevelFunc //規(guī)則結(jié)構(gòu)體的校驗(yàn) 【結(jié)構(gòu)體類型】-> 校驗(yàn)規(guī)則
customTypeFuncs map[reflect.Type]CustomTypeFunc //類型校驗(yàn)器 【數(shù)據(jù)類型】-> 校驗(yàn)規(guī)則
aliasValidators map[string]string //別名校驗(yàn)器 【別名匹配規(guī)則組合】-> 校驗(yàn)規(guī)則
其中比較重要的就是
validator.govalidator_instance.go
這兩個(gè)文件,在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 instancefor k, val := range bakedInAliases {v.RegisterAlias(k, val)}// must copy validators for separate validations to be used in each instancefor k, val := range bakedInValidators {switch k {// these require that even if the value is nil that the validation should run, omitempty still overrides this behaviourcase 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}
由于validate是每一個(gè)請求都需要的高頻操作,所以非常關(guān)注性能,盡量使用緩存。
校驗(yàn)器結(jié)構(gòu)體
Ⅰ.cTag(tag規(guī)則)
cTag是一個(gè)鏈表,存儲(chǔ)一連串的相關(guān)聯(lián)tag的校驗(yàn)器,比如說這邊是作為存儲(chǔ)一個(gè)Field的相關(guān)所有標(biāo)簽,看一下cTag的結(jié)構(gòu):
type cTag struct {tag string //標(biāo)簽aliasTag string //actualAliasTag string //param string //如果是比較類型的標(biāo)簽,這里存放的是比較的值,比如說 min=10,這里存放的是【10】這個(gè)值hasAlias bool //是否有別名校驗(yàn)器標(biāo)簽typeof tagType //對應(yīng)的tagTypehasTag bool //是否存在tag標(biāo)簽fn Func //當(dāng)前cTag對應(yīng)的【tag標(biāo)簽校驗(yàn)器】next *cTag //下一個(gè)cTag標(biāo)簽}
Ⅱ.cFeild(字段規(guī)則)
cField代表一個(gè)結(jié)構(gòu)體字段對應(yīng)的規(guī)則,他會(huì)包含這個(gè)結(jié)構(gòu)體字段對應(yīng)的所有tag規(guī)則,也就是一組cTag鏈表存儲(chǔ)的規(guī)則:
type cField struct {Idx int //字段下標(biāo)Name string //字段名AltName string //cTags *cTag //Field對應(yīng)的cTag規(guī)則,是一個(gè)鏈表(一串的規(guī)則).}
Ⅲ.cStruct(結(jié)構(gòu)體規(guī)則)
同理,cStruct對應(yīng)的是這個(gè)結(jié)構(gòu)體所有字段(包含tag規(guī)則),以及這個(gè)結(jié)構(gòu)體自己對應(yīng)的【StructLevelFunc】的校驗(yàn)方法:
type cStruct struct {Name string //結(jié)構(gòu)體名稱fields map[int]*cField //結(jié)構(gòu)體對應(yīng)的字段mapfn StructLevelFunc //結(jié)構(gòu)體校驗(yàn)器 (結(jié)構(gòu)體類型->結(jié)構(gòu)體校驗(yàn)器)}
可以看下調(diào)用Struct來進(jìn)行驗(yàn)證的過程
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是校驗(yàn)類的主體,所有的注冊和緩存數(shù)據(jù)、錯(cuò)誤信息數(shù)據(jù)都是存儲(chǔ)在validate中的,看一下具體的數(shù)據(jù)結(jié)構(gòu):
// Validate contains the validator settings passed in using the Config structtype Validate struct {tagName string //校驗(yàn)起作用的tag名fieldNameTag string //validationFuncs map[string]Func //規(guī)則類型的校驗(yàn) 【tag標(biāo)簽】-> 校驗(yàn)規(guī)則structLevelFuncs map[reflect.Type]StructLevelFunc //規(guī)則結(jié)構(gòu)體的校驗(yàn) 【結(jié)構(gòu)體類型】-> 校驗(yàn)規(guī)則customTypeFuncs map[reflect.Type]CustomTypeFunc //類型校驗(yàn)器 【數(shù)據(jù)類型】-> 校驗(yàn)規(guī)則aliasValidators map[string]string //別名校驗(yàn)器 【別名匹配規(guī)則組合】-> 校驗(yàn)規(guī)則hasCustomFuncs bool //是否存在類型校驗(yàn)器hasAliasValidators bool //是否有別名校驗(yàn)器hasStructLevelFuncs bool //是否有結(jié)構(gòu)體校驗(yàn)器tagCache *tagCache //tag對應(yīng)的【校驗(yàn)規(guī)則方法】的緩存structCache *structCache //結(jié)構(gòu)體對應(yīng)的【校驗(yàn)規(guī)則方法】的緩存errsPool *sync.Pool //校驗(yàn)錯(cuò)誤獎(jiǎng)池}
定義在validator_instance.go
type validate struct {v *Validatetop reflect.Valuens []byteactualNs []byteerrs ValidationErrorsincludeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwiseffn FilterFuncslflParent reflect.Value // StructLevel & FieldLevelslCurrent reflect.Value // StructLevel & FieldLevelflField reflect.Value // StructLevel & FieldLevelcf *cField // StructLevel & FieldLevelct *cTag // StructLevel & FieldLevelmisc []byte // misc reusablestr1 string // misc reusablestr2 string // misc reusablefldIsPointer bool // StructLevel & FieldLevelisPartial boolhasExcludes bool}
定義在validator.go
在validator.go中還有兩個(gè)方法
func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) {}
func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) {}
1,獲取結(jié)構(gòu)體校驗(yàn)器緩存,如果沒有獲取到,則對該結(jié)構(gòu)體進(jìn)行解析,然后返回對應(yīng)的校驗(yàn)器,否則往下
2,判斷是否存在校驗(yàn)器,否則忽略該字段校驗(yàn)(這邊有一個(gè)判斷是first的判斷,如果是第一層,也就是傳進(jìn)來的機(jī)構(gòu)體一定會(huì)對它的Field進(jìn)行校驗(yàn))
2,循環(huán)所有的Field,判斷Field是否包含在不校驗(yàn)的集合中,如果不包含則進(jìn)行校驗(yàn),包含則不校驗(yàn)(這邊通過傳入一個(gè)【includeExclude】的map結(jié)構(gòu)可以指定對哪些字段不進(jìn)行校驗(yàn).這個(gè)在【StructExcept】方法中會(huì)用到)
3,判斷是否存在對應(yīng)的結(jié)構(gòu)體類型校驗(yàn)方法,如果存在則調(diào)用該方法進(jìn)行校驗(yàn)
整個(gè)驗(yàn)證的過程就是利用反射和struct tag中定義的一些語法擴(kuò)展,對參數(shù)的值進(jìn)行校驗(yàn)。
在很多工具類里面對于可能多次出現(xiàn)的東西都會(huì)進(jìn)行相應(yīng)的緩存處理,這邊也不例外,對于一個(gè)Validator。它可能會(huì)進(jìn)行多次校驗(yàn),那么可能會(huì)有重復(fù)的結(jié)構(gòu)體或者字段數(shù)據(jù),可以進(jìn)行緩存不需要下次再提取,所以這邊提供了兩個(gè)對應(yīng)的緩存。
1.structCache(結(jié)構(gòu)體緩存)
這個(gè)緩存存儲(chǔ)的就是 【結(jié)構(gòu)體類型】->【cStruct】之間的對應(yīng)關(guān)系,考慮并發(fā)的問題,這邊是進(jìn)行加鎖存儲(chǔ):
type structCache struct {lock sync.Mutexm atomic.Value // map[reflect.Type]*cStruct}
對應(yīng)的緩存方法包含 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] = valuesc.m.Store(nm)}
2.tagCache(標(biāo)簽規(guī)則緩存)
這個(gè)緩存存儲(chǔ)的就是 【tag】 ->【cTag】之間的對應(yīng)關(guān)系,考慮并發(fā)的問題,這邊是進(jìn)行加鎖存儲(chǔ):
type tagCache struct {lock sync.Mutexm atomic.Value // map[string]*cTag}
對應(yīng)的緩存方法包含 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] = valuetc.m.Store(nm)}

推薦閱讀
