validator 是咱们平时业务中用的十分宽泛的框架组件,很多 web 框架、微服务框架都有集成。通常用来做一些申请参数的校验以防止写出反复的测验逻辑。接下来的文章中,咱们就去看看如何去实现一个 validator。
初体验
实际是第一生产力,我先提供一个场景,当初咱们有一个接口,作用是填写用户信息,须要咱们保留入库。咱们该怎么做呢?
首先,咱们先定义一个构造体,规定用户信息的几个参数:
type ValidateStruct struct {
Name string `json:"name"`
Address string `json:"address"`
Email string `json:"email"`
}
用户传进来数据,咱们须要校验能力入库,例如 Name 是必填的,Email 是非法的这些等等,那咱们要怎么去实现它?能够是这样:
func validateEmail(email string) error {
//do something
return nil
}
func validateV1(req ValidateStruct) error{if len(req.Name) > 0 {if len(req.Address) > 0 {if len(req.Email) > 0 {if err := validateEmail(req.Email); err != nil {return err}
}else {return errors.New("Email is required")
}
} else {return errors.New("Address is required")
}
} else {return errors.New("Name is required")
}
return nil
}
也能够是这样:
func validateV2(req ValidateStruct) error{if len(req.Name) < 0 {return errors.New("Name is required")
}
if len(req.Address) < 0 {return errors.New("Name is required")
}
if len(req.Email) < 0 || validateEmail(req.Email) != nil {return errors.New("Name is required")
}
return nil
}
能够用倒是能够用了,试想一下,如果当初咱们要减少 100 个接口,每个接口有不同的申请参数,那这样的逻辑咱们岂不是要写 100 遍?那是不可能的!咱们再想想方法。
进阶
咱们会发现参数名尽管不同,然而校验逻辑是能够雷同的,例如参数大于 0,小于 0,不等于这种,共性能够找到,那咱们是不是就能够抽出通用的逻辑来了呢?先来看咱们的通用逻辑,这个办法能够帮咱们实现 int,string 参数的校验,因为只是做演示应用,所以只是简略的进行实现,以此来表白这种形式的可行性。
func validateEmail(input string) bool {
if pass, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, input,
); pass {return true}
return false
}
// 通用的校验逻辑,采纳反射实现
func validate(v interface{}) (bool, string) {vt := reflect.TypeOf(v)
vv := reflect.ValueOf(v)
errmsg := "success"
validateResult := true
for i := 0; i < vt.NumField(); i++ {
if errmsg != "success" {return validateResult, errmsg}
fieldValue := vv.Field(i)
tagContend := vt.Field(i).Tag.Get("validate")
k := fieldValue.Kind()
switch k {
case reflect.Int64:
val := fieldValue.Int()
tagValStr := strings.Split(tagContend, "=")
if tagValStr[0] != "eq" {errmsg = "validate int failed, tag is:" + tagValStr[0]
validateResult = false
}
tagVal, _ := strconv.ParseInt(tagValStr[1], 10, 64)
if val != tagVal {
errmsg = "validate int failed, tag is:"+ strconv.FormatInt(tagVal, 10,)
validateResult = false
}
case reflect.String:
valStr := fieldValue.String()
tagValStr := strings.Split(tagContend, ";")
for _, val := range tagValStr {
if val == "email" {nestedResult := validateEmail(valStr)
if nestedResult == false {
errmsg = "validate mail failed, field val is:"+ val
validateResult = false
}
}
tagValChildStr := strings.Split(val, "=")
if tagValChildStr[0] == "gt" {length, _ := strconv.ParseInt(tagValChildStr[1], 10, 64)
if len(valStr) < int(length) {
errmsg = "validate int failed, tag is:"+ strconv.FormatInt(length, 10,)
validateResult = false
}
}
}
case reflect.Struct:
// 如果有内嵌的 struct,那么深度优先遍历
// 就是一个递归过程
valInter := fieldValue.Interface()
nestedResult, msg := validate(valInter)
if nestedResult == false {
validateResult = false
errmsg = msg
}
}
}
return validateResult, errmsg
}
接下来咱们来跑一下:
// 定义咱们须要的构造体
type ValidateStructV3 struct {
// 字符串的 gt=0 示意长度必须 > 0,gt = greater than
Name string `json:"name" validate:"gt=0"`
Address string `json:"address" validate:"gt=0"`
Email string `json:"email" validate:"email;gt=3"`
Age int64 `json:"age" validate:"eq=0"`
}
func ValidateV3(req ValidateStructV3) string {ret, err := validate(req)
if !ret {println(ret, err)
return err
}
return ""
}
// 实现这个构造体
req := demos.ValidateStructV3{
Name: "nosay",
Address: "beijing",
Email: "nosay@qq.com",
Age: 3,
}
resp := demos.ValidateV3(req)
// 输入:validate int failed, tag is: 0
这样就不须要在每个申请进入业务逻辑之前都写反复的 validate() 函数了,咱们同样能够集成在框架里。
原理介绍
正如咱们上文 validator 的实现一样,他的原理就是下图这个构造,如果是可判断类型就通过 tag 去做相应的动作,如果是 struct 就递归,持续去遍历。
struct 是咱们的申请体 (也就是父节点),子节点对应咱们的每一个元素,它的类型是 int64,string,struct 或者其它的类型,咱们通过类型去执行对应的行为(即 int 类型的 eq=0,string 类型的 gt= 0 等)。
举个例子,咱们依照下边这种形式去跑咱们的 validator:
type ValidateStructV3 struct {
// 字符串的 gt=0 示意长度必须 > 0,gt = greater than
Name string `json:"name" validate:"gt=0"`
Address string `json:"address" validate:"gt=0"`
Email EmailV4
Age int64 `json:"age" validate:"eq=0"`
}
type EmailV4 struct {
// 字符串的 gt=0 示意长度必须 > 0,gt = greater than
Email string `json:"email" validate:"email;gt=3"`
}
req := demos.ValidateStructV3{
Name: "nosay",
Address: "beijing",
Email: demos.EmailV4{Email: "nosayqq.com",},
Age: 0,
}
resp := demos.ValidateV3(req)
这时候它的执行流程大略长这个样子:
扩大
到这里其实基本原理咱们都曾经讲完了,然而真正的实现必定没这么简略,这边笔者给你们举荐一个专门的 validator 库(https://github.com/go-playgro…),有趣味的读者能够浏览一下~
关注咱们
欢送对本系列文章感兴趣的读者订阅咱们的公众号,关注博主下次不迷路~