天天看點

GO學習筆記 - 資料校驗GO學習筆記 - 資料校驗

GO學習筆記 - 資料校驗

一. asaskevich/govalidator介紹

godoc裡可以搜到若幹相似的第三方資料校驗子產品,但筆者推薦使用asaskevich/govalidator,原因:

  • star最多、持續更新釋出
  • 功能完善、使用便利
  • 豐富的字元串校驗、資料比對、裁剪拼接處理等
  • 支援struct元素合法性校驗,并且支援嵌套檢查
  • 源碼值得學習,就是一個百寶箱
// 下載下傳
go get github.com/asaskevich/govalidator
           

注意:檢視使用方法到github,檢視支援的函數清單到godoc

https://github.com/asaskevich/govalidator
https://godoc.org/github.com/asaskevich/govalidator
           

二. 字元串比對

govalidator支援非常多種字元串比對,先貼上一個簡單例子

package main

import (
    "fmt"
    "github.com/asaskevich/govalidator"
)

func main() {
    // 判斷字元串值是否為合法的IPv4位址
    ip4 := "192.168.1.1"
    fmt.Println(govalidator.IsIPv4(ip4)) // true

    // 判斷字元串值是否為合法的MAC
    mac := "aa:bb:cc:dd:ee:ffffff"
    fmt.Println(govalidator.IsMAC(mac)) // false

    // 判斷數字是否在指定範圍内
    dig := 101    // string類型也可以用
    fmt.Println(govalidator.InRange(dig, 0, 100)) // false
}
           

輸出

true
false
false
           

三. struct元素比對

govalidator專門提供了一個函數,用于校驗struct的元素

簡單例子

package main

import (
    "fmt"
    "github.com/asaskevich/govalidator"
)

type foo struct {
    A string `valid:"ipv4"`
    B string `valid:"mac"`
    C string `valid:"range(0|100)"`    // 也可以使用int類型
}

func main() {
    f := foo{
        A: "192.168.1.1",
        B: "aa:bb:cc:dd:ee:ffffff",
        C: "101",
    }

    result, err := govalidator.ValidateStruct(f)
    if err != nil {
        fmt.Println("error: " + err.Error())
    }
    fmt.Println(result)
}
           

輸出

error: B: aa:bb:cc:dd:ee:ffffff does not validate as mac;C: 101 does not validate as range(0|100)
false
           

注意:

▪ struct元素隻支援部分常用的校驗,詳見本文附錄2

▪ struct元素必須是導出型,也就是必須大寫字母開頭,govalidator才會去理會

▪ struct元素比對較為智能,比如range(min|max)不僅支援string也支援int類型

四. struct元素可選驗證

govalidator有一個bool類型的全局變量,可通過函數govalidator.SetFieldsRequiredByDefault()進行設定:

  • 當設定為true時,如果沒有定義valid tag,則會提示錯誤
  • 當設定為false時,如果沒有定義valid tag,不會提示錯誤。預設值就是false

另外,valid tag裡,可以通過顯式設定方式更細顆粒度地控制:當遇到zero value時是需要驗證還是提示錯誤。此設定可以覆寫SetFieldsRequiredByDefault()。是以,valid tag有如下幾種寫法

`valid:""` // 等同于空tag,即``
`valid:"-"`
`valid:","`
`valid:",optional`
`valid:",required`
           

接下來,分别測試:假設一個struct元素的值為空字元""(即zero value)

govalidator.SetFieldsRequiredByDefault(true)

`valid:""`    // 報錯:All fields are required to at least have one validation defined
`valid:"-"`    // true
`valid:","`    // 報錯:Missing required field
`valid:",optional`    // true
`valid:",required`    // 報錯:non zero value required
`valid:"ipv4"`    // 報錯:Missing required field
`valid:"ipv4,optional"`    // true
`valid:"ipv4,required"`    // 報錯:non zero value required
           
  • govalidator.SetFieldsRequiredByDefault(false)
`valid:""`    // true
`valid:"-"`    // true
`valid:","`    // true
`valid:",optional`    // true
`valid:",required`    // non zero value required
`valid:"ipv4"`    // true
`valid:"ipv4,optional"`    // true
`valid:"ipv4,required"`    // 報錯:non zero value required
           

繼續測試,當struct元素的值為不合法的ipv4位址字元串(非空字元串),如"192.168.1.1.1"

govalidator.SetFieldsRequiredByDefault(true)

`valid:""`    // 報錯:All fields are required to at least have one validation defined
`valid:"-"`    // true
`valid:","`    // true
`valid:",optional`    // true
`valid:",required`    // true
`valid:"ipv4"`    // 報錯:192.168.1.1.1 does not validate as ipv4
`valid:"ipv4,optional"`    // 報錯:192.168.1.1.1 does not validate as ipv4
`valid:"ipv4,required"`    // 報錯:192.168.1.1.1 does not validate as ipv4
           
  • govalidator.SetFieldsRequiredByDefault(false):測試效果和上述完全相同
// 來自github
SetNilPtrAllowedByRequired causes validation to pass when struct fields marked by required are set to nil. This is disabled by default for consistency, but some packages that need to be able to determine between nil and zero value state can use this. If disabled, both nil and zero values cause validation errors.

// 來自godoc
SetNilPtrAllowedByRequired causes validation to pass for nil ptrs when a field is set to required. The validation will still reject ptr fields in their zero value state. Example with this enabled:

type exampleStruct struct {
    Name *string `valid:"required"

With `Name` set to "", this will be considered invalid input and will cause a validation error. With `Name` set to nil, this will be considered valid by validation. By default this is disabled.

           

五. struct嵌套校驗

嵌套元素名必須是導出型,也就是大寫字母開頭,舉例

package main

import (
    "fmt"
    "github.com/asaskevich/govalidator"
)

type Foo struct {
    A string `valid:"ipv4"`
    B string `valid:"mac"`
    C int `valid:"range(0|100)"`
}

type bar struct {
    X string `valid:"ipv4"`
    Foo `valid:",required"`
}

func main() {
    govalidator.SetFieldsRequiredByDefault(true)

    b := bar{
        X: "192.168.1.1",
    }

    b.Foo.A = "192.168.1.1.1"
    b.Foo.B = "aa:bb:cc:dd:ee:ff"
    b.Foo.C = 100

    result, err := govalidator.ValidateStruct(b)
    if err != nil {
        fmt.Println("error: " + err.Error())
    }
    fmt.Println(result)
}
           

輸出

error: Foo.A: 192.168.1.1.1 does not validate as ipv4;A: 192.168.1.1.1 does not validate as ipv4
false
           

注意:可以給Foo設定一個元素名,但也必須是大寫字母開頭,比如

MyFoo Foo `valid:",required"`    // 正确,可以讀取到
myFoo Foo `valid:",required"`    // 錯誤,無法讀取到
           

六. 無法實作嵌套的可選校驗

無法實作以嵌套為顆粒度的可選校驗,比如下面這樣是沒有效果的

type bar struct {
    X string `valid:"ipv4"`
    Foo `valid:",optional"`    // 不可行
}
           

因為上面代碼實際會被轉換為這樣

type bar struct {
    X string `valid:"ipv4"`
    Foo.A string `valid:"ipv4"`
    Foo.B string `valid:"mac"`
    Foo.C int `valid:"range(0|100)"`
}
           

這就導緻沒有辦法實作Foo全校驗或者全不校驗

七. 個人最佳實踐

建議全部顯式配置校驗,因為使用隐式一旦配置有誤,難以及時發現

  • govalidator.SetFieldsRequiredByDefault(true)
  • valid tag寫法:帶上required,例如:
想做驗證使用`valid:ipv4,required`
不想做驗證使用`valid:",required"`
           

八. 其他功能

govalidator的校驗功能還支援自定義tag與自定義校驗函數,由于筆者尚未深度實踐過,是以請參考官方github文檔。

govalidator除了支援校驗,還支援較為豐富的字元串裁剪、處理、正則等功能,以及若幹類型轉換功能,詳見本文附錄4、5(本文相比godoc和官網文檔進行了更為細緻的分類)。但筆者不推薦直接使用這些裁剪、處理、正則功能,因為實際上就是做了一層封裝和一些細節處理,并不複雜,但可以學習。

筆者認為在使用govalidator的任何功能時,先看看源碼,這是一個大而全的源碼寶庫,非常值得學習和借鑒。

參考文章

繼續閱讀