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...),有興趣的讀者可以閱讀一下~
關注我們
歡迎對本系列文章感興趣的讀者訂閱我們的公眾號,關注博主下次不迷路~