Golang使用標(biāo)簽表達(dá)式校驗(yàn)結(jié)構(gòu)體字段的有效性

一、背景

在服務(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)方式。

  1. 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)工作,易使人厭煩
  2. 規(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í) -> 查看這里

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容