Golang使用標簽表達式校驗結構體字段的有效性

一、背景

在服務的API接口層面本慕,我們常常需要驗證參數(shù)的有效性。
Golang中侧漓,大部分參數(shù)校驗場景實際上是先將數(shù)據(jù)Bind到結構體锅尘,然后校驗其字段值。

一般地布蔗,校驗結構體字段值有如下兩種實現(xiàn)方式藤违。

  1. Case-By-Case 針對每個需校驗的結構體字段分別寫校驗代碼
    • 優(yōu)點:自由靈活,適應所有場景
    • 缺點:重復且瑣碎的碼農(nóng)工作纵揍,易使人厭煩
  2. 規(guī)則匹配顿乒,在結構體標簽中設置預先支持的驗證規(guī)則,如email泽谨、max:100等形式
    • 優(yōu)點:使用簡單璧榄,不需要寫瑣碎的代碼
    • 缺點:強依賴有限的規(guī)則,缺乏靈活性吧雹,無法滿足復雜場景骨杂,如多字段關聯(lián)驗證等

思考:有沒有一種方式,即簡單易用(少寫代碼)雄卷,又能滿足各種復雜的校驗場景腊脱?

答案是:有!結構體標簽表達式 go-tagexpr 的出現(xiàn)龙亲,為我們提供了兼得魚和熊掌的第三種選擇陕凹。

二、認識 go-tagexpr

go-tagexpr 允許Gopher們在 struct tag 寫表達式代碼鳄炉,并通過高性能的解釋器計算其結果杜耙。

安裝

go get -u github.com/bytedance/go-tagexpr

下面使用一個小示例,演示含有枚舉拂盯、比較佑女、字段關聯(lián)的較復雜場景。

示例代碼

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
}

代碼詮釋:

  • 新建一個標簽名稱為 te 的解釋器

    vm := tagexpr.New("te")
    
  • 定義一個結構體谈竿,添加標簽表達式团驱,并實例化一個 m 對象。其中 $ 表示當前字段值空凸,(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,
    }
    
  • 將對象實例 m 放入解釋器中運行嚎花,返回表達式對象 r

    r := vm.MustRun(m)
    
  • 計算 Season 字段匿名表達式($=='spring'||$=='summer'||$=='autumn'||$=='winter')的值。因字段值 summer 在窮舉列表中呀洲,故表達式結果為“true”

    r.Eval("Season")
    
  • 計算 Weather 字段匿名表達式 $!='snowing' || (Season)$=='winter' 的值紊选。因字段值為 snowing 且 Season 為 summer,故表達式結果為“false”

    r.Eval("Weather")
    
  • 計算 Temperature 字段的 range 表達式 $>=-10 && $<38 的值道逗。因字段值為 40兵罢,超出給出的范圍,所以結果為“false”

    r.Eval("Temperature@range")
    
  • 計算 Temperature 字段的 alarm 表達式 sprintf('Uncomfortable temperature: %v',$) 的值滓窍。這是一個調(diào)用內(nèi)部函數(shù)的表達式卖词,它打印并返回字符串,結果為“Uncomfortable temperature: 40”

    r.Eval("Temperature@alarm")
    

獲取更多關于 go-expr 結構體標簽表達式的語法知識 -> 查看這里

二吏夯、使用Validator校驗

Validator 是有 go-expr 包提供的一個采用結構體標簽表達式的參數(shù)校驗組件此蜈。

主要特性

  • 它要求在每個待校驗字段上添加結果為布爾值的匿名表達式
  • 當表達式結果為false時,表示驗證不通過锦亦,此時組件將返回與該字段相關的錯誤信息
  • 它支持使用名稱為msg且結果為字符串的表達式作為錯誤信息
  • 允許用戶按需求自由修改錯誤信息的模板
  • 支持各種常見的運算符
  • 支持訪問數(shù)組舶替,切片,字典成員
  • 支持訪問當前結構體中的任何字段
  • 支持訪問嵌套字段杠园,非導出字段等
  • 支持注冊自定義的驗證函數(shù)表達式
  • 內(nèi)置len顾瞪,sprintf,regexp抛蚁,email陈醒,phone等函數(shù)表達式

安裝

go get -u github.com/bytedance/go-tagexpr

我們基于前面示例稍作修改,來演示如何使用validator校驗結構體字段的有效性瞧甩。

示例代碼

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
}

代碼詮釋:

  • 新建一個標簽名稱為 vd 的校驗器

    vd := validator.New("vd")
    
  • 定義一個結構體钉跷,在標簽上添加校驗表達式,并使用 m 實例進行測試肚逸。

    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",
    }
    
  • 校驗實例 m 的各字段值是否有效爷辙,如果無效彬坏,則返回error信息

    err := vd.Validate(m)
    

注冊自己的校驗函數(shù)

可能你已注意到 email($) 這個表達式,它是默認注冊的一個函數(shù)表達式膝晾,用于驗證郵箱的有效性栓始。其實我們也可以定義自己通用的函數(shù)表達式,以便較少標簽中的代碼量血当,增加代碼復用性幻赚。

下面以 email 函數(shù)的實現(xià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ù)落恼,表示是否強制覆蓋已經(jīng)注冊了的同名函數(shù)。

結論:validator的使用方法非常簡單离熏、靈活且具有良好的擴展性佳谦,能夠輕松滿足各種復雜的驗證場景。

獲取更多關于 validator 校驗器的語法知識 -> 查看這里

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撤奸,一起剝皮案震驚了整個濱河市吠昭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胧瓜,老刑警劉巖矢棚,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異府喳,居然都是意外死亡蒲肋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門钝满,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兜粘,“玉大人,你說我怎么就攤上這事弯蚜】字幔” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵碎捺,是天一觀的道長路鹰。 經(jīng)常有香客問我,道長收厨,這世上最難降的妖魔是什么晋柱? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮诵叁,結果婚禮上雁竞,老公的妹妹穿的比我還像新娘。我一直安慰自己拧额,他們只是感情好碑诉,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布彪腔。 她就那樣靜靜地躺著,像睡著了一般联贩。 火紅的嫁衣襯著肌膚如雪漫仆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天泪幌,我揣著相機與錄音,去河邊找鬼署照。 笑死祸泪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的建芙。 我是一名探鬼主播没隘,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼禁荸!你這毒婦竟也來了右蒲?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤赶熟,失蹤者是張志新(化名)和其女友劉穎瑰妄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體映砖,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡间坐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了邑退。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竹宋。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖地技,靈堂內(nèi)的尸體忽然破棺而出蜈七,到底是詐尸還是另有隱情,我是刑警寧澤莫矗,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布飒硅,位于F島的核電站,受9級特大地震影響趣苏,放射性物質發(fā)生泄漏狡相。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一食磕、第九天 我趴在偏房一處隱蔽的房頂上張望尽棕。 院中可真熱鬧,春花似錦彬伦、人聲如沸滔悉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽回官。三九已至曹宴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歉提,已是汗流浹背笛坦。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留苔巨,地道東北人版扩。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像侄泽,于是被迫代替她去往敵國和親礁芦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容