一、背景
在服務(wù)的API接口層面,我們常常需要驗(yàn)證參數(shù)的有效性。
Golang中,大部分參數(shù)校驗(yàn)場(chǎng)景實(shí)際上是先將數(shù)據(jù)Bind到結(jié)構(gòu)體,然后校驗(yàn)其字段值。
一般地,校驗(yàn)結(jié)構(gòu)體字段值有如下兩種實(shí)現(xiàn)方式。
- Case-By-Case 針對(duì)每個(gè)需校驗(yàn)的結(jié)構(gòu)體字段分別寫(xiě)校驗(yàn)代碼
- 優(yōu)點(diǎn):自由靈活,適應(yīng)所有場(chǎng)景
- 缺點(diǎn):重復(fù)且瑣碎的碼農(nóng)工作,易使人厭煩
- 規(guī)則匹配,在結(jié)構(gòu)體標(biāo)簽中設(shè)置預(yù)先支持的驗(yàn)證規(guī)則,如
email、max:100等形式- 優(yōu)點(diǎn):使用簡(jiǎn)單,不需要寫(xiě)瑣碎的代碼
- 缺點(diǎn):強(qiáng)依賴(lài)有限的規(guī)則,缺乏靈活性,無(wú)法滿(mǎn)足復(fù)雜場(chǎng)景,如多字段關(guān)聯(lián)驗(yàn)證等
思考:有沒(méi)有一種方式,即簡(jiǎn)單易用(少寫(xiě)代碼),又能滿(mǎn)足各種復(fù)雜的校驗(yàn)場(chǎng)景?
答案是:有!結(jié)構(gòu)體標(biāo)簽表達(dá)式 go-tagexpr 的出現(xiàn),為我們提供了兼得魚(yú)和熊掌的第三種選擇。
二、認(rèn)識(shí) go-tagexpr
go-tagexpr 允許Gopher們?cè)?struct tag 寫(xiě)表達(dá)式代碼,并通過(guò)高性能的解釋器計(jì)算其結(jié)果。
安裝
go get -u github.com/bytedance/go-tagexpr
下面使用一個(gè)小示例,演示含有枚舉、比較、字段關(guān)聯(lián)的較復(fù)雜場(chǎng)景。
示例代碼
import (
"fmt"
tagexpr "github.com/bytedance/go-tagexpr"
)
func ExampleTagexpr() {
vm := tagexpr.New("te")
type Meteorology struct {
Season string `te:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
Weather string `te:"$!='snowing' || (Season)$=='winter'"`
Temperature int `te:"{range:$>=-10 && $<38}{alarm:sprintf('Uncomfortable temperature: %v',$)}"`
}
m := &Meteorology{
Season: "summer",
Weather: "snowing",
Temperature: 40,
}
r := vm.MustRun(m)
fmt.Println(r.Eval("Season"))
fmt.Println(r.Eval("Weather"))
fmt.Println(r.Eval("Temperature@range"))
fmt.Println(r.Eval("Temperature@alarm"))
// Output:
// true
// false
// false
// Uncomfortable temperature: 40
}
代碼詮釋?zhuān)?/strong>
-
新建一個(gè)標(biāo)簽名稱(chēng)為 te 的解釋器
vm := tagexpr.New("te") -
定義一個(gè)結(jié)構(gòu)體,添加標(biāo)簽表達(dá)式,并實(shí)例化一個(gè) m 對(duì)象。其中
$表示當(dāng)前字段值,(Season)$表示 Season 字段的值type Meteorology struct { Season string `te:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"` Weather string `te:"$!='snowing' || (Season)$=='winter'"` Temperature int `te:"{range:$>=-10 && $<38}{alarm:sprintf('Uncomfortable temperature: %v',$)}"` } m := &Meteorology{ Season: "summer", Weather: "snowing", Temperature: 40, } -
將對(duì)象實(shí)例 m 放入解釋器中運(yùn)行,返回表達(dá)式對(duì)象 r
r := vm.MustRun(m) -
計(jì)算 Season 字段匿名表達(dá)式(
$=='spring'||$=='summer'||$=='autumn'||$=='winter')的值。因字段值 summer 在窮舉列表中,故表達(dá)式結(jié)果為“true”r.Eval("Season") -
計(jì)算 Weather 字段匿名表達(dá)式
$!='snowing' || (Season)$=='winter'的值。因字段值為 snowing 且 Season 為 summer,故表達(dá)式結(jié)果為“false”r.Eval("Weather") -
計(jì)算 Temperature 字段的
range表達(dá)式$>=-10 && $<38的值。因字段值為 40,超出給出的范圍,所以結(jié)果為“false”r.Eval("Temperature@range") -
計(jì)算 Temperature 字段的
alarm表達(dá)式sprintf('Uncomfortable temperature: %v',$)的值。這是一個(gè)調(diào)用內(nèi)部函數(shù)的表達(dá)式,它打印并返回字符串,結(jié)果為“Uncomfortable temperature: 40”r.Eval("Temperature@alarm")
獲取更多關(guān)于 go-expr 結(jié)構(gòu)體標(biāo)簽表達(dá)式的語(yǔ)法知識(shí) -> 查看這里
二、使用Validator校驗(yàn)
Validator 是有 go-expr 包提供的一個(gè)采用結(jié)構(gòu)體標(biāo)簽表達(dá)式的參數(shù)校驗(yàn)組件。
主要特性
- 它要求在每個(gè)待校驗(yàn)字段上添加結(jié)果為布爾值的匿名表達(dá)式
- 當(dāng)表達(dá)式結(jié)果為false時(shí),表示驗(yàn)證不通過(guò),此時(shí)組件將返回與該字段相關(guān)的錯(cuò)誤信息
- 它支持使用名稱(chēng)為
msg且結(jié)果為字符串的表達(dá)式作為錯(cuò)誤信息 - 允許用戶(hù)按需求自由修改錯(cuò)誤信息的模板
- 支持各種常見(jiàn)的運(yùn)算符
- 支持訪(fǎng)問(wèn)數(shù)組,切片,字典成員
- 支持訪(fǎng)問(wèn)當(dāng)前結(jié)構(gòu)體中的任何字段
- 支持訪(fǎng)問(wèn)嵌套字段,非導(dǎo)出字段等
- 支持注冊(cè)自定義的驗(yàn)證函數(shù)表達(dá)式
- 內(nèi)置len,sprintf,regexp,email,phone等函數(shù)表達(dá)式
安裝
go get -u github.com/bytedance/go-tagexpr
我們基于前面示例稍作修改,來(lái)演示如何使用validator校驗(yàn)結(jié)構(gòu)體字段的有效性。
示例代碼
import (
"fmt"
"github.com/bytedance/go-tagexpr/validator"
)
func ExampleValidator() {
vd := validator.New("vd")
type Meteorology struct {
Season string `vd:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
Weather string `vd:"$!='snowing' || (Season)$=='winter'"`
Temperature int `vd:"{@:$>=-10 && $<38}{msg:sprintf('Uncomfortable temperature: %v',$)}"`
Contact string `vd:"email($)"`
}
m := &Meteorology{
Season: "summer",
Weather: "rain",
Temperature: 40,
Contact: "henrylee2cn@gmail.com",
}
err := vd.Validate(m)
if err != nil {
fmt.Println(err)
}
// Output:
// Uncomfortable temperature: 40
}
代碼詮釋?zhuān)?/strong>
-
新建一個(gè)標(biāo)簽名稱(chēng)為 vd 的校驗(yàn)器
vd := validator.New("vd") -
定義一個(gè)結(jié)構(gòu)體,在標(biāo)簽上添加校驗(yàn)表達(dá)式,并使用 m 實(shí)例進(jìn)行測(cè)試。
type Meteorology struct { Season string `vd:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"` Weather string `vd:"$!='snowing' || (Season)$=='winter'"` Temperature int `vd:"{@:$>=-10 && $<38}{msg:sprintf('Uncomfortable temperature: %v',$)}"` Contact string `vd:"email($)"` } m := &Meteorology{ Season: "summer", Weather: "rain", Temperature: 40, Contact: "henrylee2cn@gmail.com", } -
校驗(yàn)實(shí)例 m 的各字段值是否有效,如果無(wú)效,則返回error信息
err := vd.Validate(m)
注冊(cè)自己的校驗(yàn)函數(shù)
可能你已注意到 email($) 這個(gè)表達(dá)式,它是默認(rèn)注冊(cè)的一個(gè)函數(shù)表達(dá)式,用于驗(yàn)證郵箱的有效性。其實(shí)我們也可以定義自己通用的函數(shù)表達(dá)式,以便較少標(biāo)簽中的代碼量,增加代碼復(fù)用性。
下面以 email 函數(shù)的實(shí)現(xiàn)為例,演示如何注冊(cè)自己的校驗(yàn)函數(shù):
var pattern = "^([A-Za-z0-9_\\-\\.\u4e00-\u9fa5])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]{2,8})$"
emailRegexp := regexp.MustCompile(pattern)
validator.RegValidateFunc("email", func(args ...interface{}) bool {
if len(args) != 1 {
return false
}
s, ok := args[0].(string)
if !ok {
return false
}
return emailRegexp.MatchString(s)
}, true)
其中,validator.RegValidateFunc 的定義如下:
func RegValidateFunc(funcName string, fn func(args ...interface{}) bool, force ...bool) error
RegValidateFunc的force可選參數(shù),表示是否強(qiáng)制覆蓋已經(jīng)注冊(cè)了的同名函數(shù)。
結(jié)論:validator的使用方法非常簡(jiǎn)單、靈活且具有良好的擴(kuò)展性,能夠輕松滿(mǎn)足各種復(fù)雜的驗(yàn)證場(chǎng)景。
獲取更多關(guān)于 validator 校驗(yàn)器的語(yǔ)法知識(shí) -> 查看這里