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...),有趣味的读者能够浏览一下~

关注咱们

欢送对本系列文章感兴趣的读者订阅咱们的公众号,关注博主下次不迷路~