【深入理解Go】从0到1实现一个validator
validator是我们平时业务中用的非常广泛的框架组件,很多web框架、微服务框架都有集成。通常用来做一些请求参数的校验以避免写出重复的检验逻辑。接下来的文章中,我们就去看看如何去实现一个validator。
初体验
实践是第一生产力,我先提供一个场景,现在我们有一个接口,作用是填写用户信息,需要我们保存入库。我们该怎么做呢?
首先,我们先定义一个结构体,规定用户信息的几个参数:
type ValidateStruct struct {
Namestring `json:"name"`
Address string `json:"address"`
Emailstring `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 := truefor 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)
接下来我们来跑一下:
//定义我们需要的结构体
type ValidateStructV3 struct {
// 字符串的 gt=0 表示长度必须 > 0,gt = greater than
Namestring `json:"name" validate:"gt=0"`
Address string `json:"address" validate:"gt=0"`
Emailstring `json:"email" validate:"email;
gt=3"`
Ageint64`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
Namestring `json:"name" validate:"gt=0"`
Address string `json:"address" validate:"gt=0"`
EmailEmailV4
Ageint64`json:"age" validate:"eq=0"`
}type EmailV4 struct {
// 字符串的 gt=0 表示长度必须 > 0,gt = greater than
Emailstring `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)
【【深入理解Go】从0到1实现一个validator】这时候它的执行流程大概长这个样子:
文章图片
扩展
到这里其实基本原理我们都已经讲完了,但是真正的实现肯定没这么简单,这边笔者给你们推荐一个专门的validator库(https://github.com/go-playgro...),有兴趣的读者可以阅读一下~
关注我们
欢迎对本系列文章感兴趣的读者订阅我们的公众号,关注博主下次不迷路~
文章图片
推荐阅读
- 宽容谁
- 我要做大厨
- 增长黑客的海盗法则
- 画画吗()
- 2019-02-13——今天谈梦想()
- 远去的风筝
- 三十年后的广场舞大爷
- 叙述作文
- 深入理解Go之generate
- 20190302|20190302 复盘翻盘