GO學習筆記 - 數據校驗

GO學習筆記 - 數據校驗

本文主題:基於asaskevich/govalidator實現Golang數據校驗

小慢哥的原創文章,歡迎轉載


目錄

▪ 一. asaskevich/govalidator介紹
▪ 二. 字符串匹配
▪ 三. struct元素匹配
▪ 四. struct元素可選驗證
▪ 五. struct嵌套校驗
▪ 六. 無法實現嵌套的可選校驗
▪ 七. 個人最佳實踐
▪ 八. 其他功能
▪ 附錄1. 字符串合法性校驗
▪ 附錄2. struct元素校驗項
▪ 附錄3. 數據特徵匹配
▪ 附錄4. 類型轉換
▪ 附錄5. 裁剪、處理、填充、遍歷等


一. 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

完整的可用校驗方法列表詳見本文附錄1、3


三. struct元素匹配

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

govalidator.ValidateStruct()

簡單例子

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):測試效果和上述完全相同

另外,還有一個全局變量參數,通過govalidator.SetNilPtrAllowedByRequired()設置,但由於筆者尚未測試過,因此直接貼出官方解釋

// 來自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的任何功能時,先看看源碼,這是一個大而全的源碼寶庫,非常值得學習和借鑑。


附錄1. 字符串合法性校驗

下面都是業務級別的合法校驗,比如是否爲IPv4格式,是否爲URL

func IsBase64(str string) bool
func IsCIDR(str string) bool    // 是否爲合法的CIDR格式,包含了IPv4與IPv6
func IsCreditCard(str string) bool
func IsDNSName(str string) bool
func IsDataURI(str string) bool
func IsEmail(str string) bool
func IsExistingEmail(email string) bool
func IsFilePath(str string) (bool, int)
func IsHash(str string, algorithm string) bool
func IsHexcolor(str string) bool
func IsHost(str string) bool
func IsIP(str string) bool    // 是否爲合法的IP地址,包含了IPv4與IPv6
func IsIPv4(str string) bool
func IsIPv6(str string) bool
func IsISBN(str string, version int) bool
func IsISBN10(str string) bool
func IsISBN13(str string) bool
func IsISO3166Alpha2(str string) bool
func IsISO3166Alpha3(str string) bool
func IsISO4217(str string) bool
func IsISO693Alpha2(str string) bool
func IsISO693Alpha3b(str string) bool
func IsJSON(str string) bool    // 通過json.Unmarshal()是否返回error進行判斷
func IsLatitude(str string) bool
func IsLongitude(str string) bool
func IsMAC(str string) bool    // 支持aa:bb:cc:dd:ee:ff,以及aabb.ccdd.eeff格式
func IsMongoID(str string) bool
func IsPort(str string) bool
func IsRFC3339(str string) bool
func IsRFC3339WithoutZone(str string) bool
func IsRGBcolor(str string) bool
func IsRequestURI(rawurl string) bool
func IsRequestURL(rawurl string) bool
func IsRsaPub(str string, params ...string) bool
func IsRsaPublicKey(str string, keylen int) bool
func IsSSN(str string) bool
func IsSemver(str string) bool
func IsTime(str string, format string) bool
func IsURL(str string) bool
func IsUUID(str string) bool    // 包含UUIDv3、UUIDv4、UUIDv5
func IsUUIDv3(str string) bool
func IsUUIDv4(str string) bool
func IsUUIDv5(str string) bool

附錄2. struct元素校驗項

有2種,第一種是不帶參數的,第二種是帶參數的

▷ 第一種:不帶參數(第一列表示在valid tag裏怎麼寫,第二列表示相當於govalidator的哪個導出函數)

"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"rfc3339WithoutZone": IsRFC3339WithoutZone,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,

▷ 第二種:帶參數(第一列表示在valid tag裏怎麼寫,第二列表示相當於govalidator的哪個導出函數)

"range(min|max)": Range,
"length(min|max)": ByteLength,
"runelength(min|max)": RuneLength,
"stringlength(min|max)": StringLength,
"matches(pattern)": StringMatches,
"in(string1|string2|...|stringN)": IsIn,
"rsapub(keylength)" : IsRsaPub,

附錄3. 數據特徵匹配

下面是非業務的數據校驗,比如在一個字符串中是否包含固定字符、是否包含空白符、是否正整數

func ByteLength(str string, params ...string) bool
func Contains(str, substring string) bool
func HasLowerCase(str string) bool
func HasUpperCase(str string) bool
func HasWhitespace(str string) bool
func HasWhitespaceOnly(str string) bool
func InRange(value interface{}, left interface{}, right interface{}) bool
func InRangeFloat32(value, left, right float32) bool
func InRangeFloat64(value, left, right float64) bool
func InRangeInt(value, left, right interface{}) bool
func IsASCII(str string) bool
func IsAlpha(str string) bool
func IsAlphanumeric(str string) bool
func IsByteLength(str string, min, max int) bool
func IsDialString(str string) bool
func IsDivisibleBy(str, num string) bool
func IsFloat(str string) bool
func IsFullWidth(str string) bool
func IsHalfWidth(str string) bool
func IsHexadecimal(str string) bool
func IsIn(str string, params ...string) bool
func IsInt(str string) bool
func IsLowerCase(str string) bool
func IsMultibyte(str string) bool
func IsNatural(value float64) bool
func IsNegative(value float64) bool
func IsNonNegative(value float64) bool // >=0
func IsNonPositive(value float64) bool // <=0
func IsNull(str string) bool // 空字符串
func IsNumeric(str string) bool // 字符串裏僅包含數字
func IsPositive(value float64) bool // 正數
func IsPrintableASCII(str string) bool
func IsUTFDigit(str string) bool
func IsUTFLetter(str string) bool
func IsUTFLetterNumeric(str string) bool
func IsUTFNumeric(str string) bool
func IsUpperCase(str string) bool
func IsVariableWidth(str string) bool
func IsWhole(value float64) bool // 整數
func Matches(str, pattern string) bool // 正則匹配
func Range(str string, params ...string) bool // 字符串長度,params的string會轉換成float64然後調用InRange(),主要是用於struct tag的range(min|max)
func RuneLength(str string, params ...string) bool // alias for StringLength
func Sign(value float64) float64 // 如果大於0則返回1,等於0返回0,小於0返回-1
func StringLength(str string, params ...string) bool // 字符串長度在指定範圍內(視爲utf8)
func StringMatches(s string, params ...string) bool // 正則匹配,等同於Matches(),主要是用於struct tag的range(min|max)

附錄4. 類型轉換

func ToBoolean(str string) (bool, error)
func ToFloat(str string) (float64, error)
func ToInt(value interface{}) (res int64, err error)
func ToString(obj interface{}) string
func ToJSON(obj interface{}) (string, error)
func NormalizeEmail(str string) (string, error)    // 輸出規範化的電子郵件格式

附錄5. 裁剪、處理、填充、遍歷等

func Abs(value float64) float64 // 獲得絕對值
func BlackList(str, chars string) string // 從字符串中移除指定字符
func CamelCaseToUnderscore(str string) string // 將駝峯拼寫法轉換爲下劃線分割寫法,如MyFunc => my_func
func Count(array []interface{}, iterator ConditionIterator) int // 通過自定義ConditionIterator(實現了迭代器)來實現判斷count
func Each(array []interface{}, iterator Iterator) // 通過自定義Iterator(實現了迭代器)來實現操作,不做任何返回,自行處理,比如打印一些東西
func Filter(array []interface{}, iterator ConditionIterator) []interface{} // 通過自定義ConditionIterator(實現了迭代器)來對[]interface{}的元素進行遍歷處理
func Find(array []interface{}, iterator ConditionIterator) interface{} // 通過自定義ConditionIterator(實現了迭代器)來對[]interface{}的元素進行遍歷查找,返回第一個找到的,若都沒找到則返回nil
func GetLine(s string, index int) (string, error) // 從含多行的字符串中返回指定行內容(0爲第一行),res, err := valid.GetLine("aa\nbb\ncc\n", 1) 返回bb
func GetLines(s string) []string // 將字符串的換行符去掉,返回由每一行組成的slice,原理就是strings.Split(s, "\n")
func LeftTrim(str, chars string) string // 若字符串最左邊匹配了chars,則刪除,如果chars爲"",則刪除前導符(空格、tab、換行符)
func Map(array []interface{}, iterator ResultIterator) []interface{}
func PadBoth(str string, padStr string, padLen int) string // 字符串首尾填充字符
func PadLeft(str string, padStr string, padLen int) string // 字符串開頭填充字符
func PadRight(str string, padStr string, padLen int) string // 字符串末尾填充字符
func RemoveTags(s string) string // RemoveTags remove all tags from HTML string
func ReplacePattern(str, pattern, replace string) string // 將正則匹配到的字符用指定字符替換
func Reverse(s string) string // 字符反轉
func RightTrim(str, chars string) string // 若字符串最右邊匹配了chars,則刪除,如果chars爲"",則刪除前導符(空格、tab、換行符)
func SafeFileName(str string) string // 返回安全的文件名,裁剪掉空格等符號,轉小寫字母等
func Trim(str, chars string) string // 就是LeftTrim+RightTrim
func Truncate(str string, length int, ending string) string
func UnderscoreToCamelCase(s string) string // 將下劃線分割寫法轉換爲駝峯拼寫法
func WhiteList(str, chars string) string // 從字符串中移除非指定字符
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章