亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Golang 經(jīng)典校驗庫 validator 用法解析

 更新時間:2022年08月26日 09:45:09   作者:ag9920  
這篇文章主要為大家介紹了Golang 經(jīng)典校驗庫 validator 用法解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

開篇

今天繼續(xù)我們的 Golang 經(jīng)典開源庫學(xué)習(xí)之旅,這篇文章的主角是 validator,Golang 中經(jīng)典的校驗庫,它可以讓開發(fā)者可以很便捷地通過 tag 來控制對結(jié)構(gòu)體字段的校驗,使用面非常廣泛。

本來打算一節(jié)收尾,越寫越發(fā)現(xiàn) validator 整體復(fù)雜度還是很高的,而且支持了很多場景??刹鸾獾乃悸泛芏啵谑谴蛩惴殖蓛善恼聛碇v。這篇我們會先來了解 validator 的用法,下一篇我們會關(guān)注實現(xiàn)的思路和源碼解析。

validator

Package validator implements value validations for structs and individual fields based on tags.

validator 是一個結(jié)構(gòu)體參數(shù)驗證器。

它提供了【基于 tag 對結(jié)構(gòu)體以及單獨屬性的校驗?zāi)芰Α俊=?jīng)典的 gin 框架就是用了 validator 作為默認(rèn)的校驗器。它的能力能夠幫助開發(fā)者最大程度地減少【基礎(chǔ)校驗】的代碼,你只需要一個 tag 就能完成校驗。完整的文檔參照 這里

目前 validator 最新版本已經(jīng)升級到了 v10,我們可以用

go get github.com/go-playground/validator/v10

添加依賴后,import 進來即可

import "github.com/go-playground/validator/v10"

我們先來看一個簡單的例子,了解 validator 能怎樣幫助開發(fā)者完成校驗。

package main
import (
	"fmt"
	"github.com/go-playground/validator/v10"
)
type User struct {
	Name string `validate:"min=6,max=10"`
	Age  int    `validate:"min=1,max=100"`
}
func main() {
	validate := validator.New()
	u1 := User{Name: "lidajun", Age: 18}
	err := validate.Struct(u1)
	fmt.Println(err)
	u2 := User{Name: "dj", Age: 101}
	err = validate.Struct(u2)
	fmt.Println(err)
}

這里我們有一個 User 結(jié)構(gòu)體,我們希望 Name 這個字符串長度在 [6, 10] 這個區(qū)間內(nèi),并且希望 Age 這個數(shù)字在 [1, 100] 區(qū)間內(nèi)。就可以用上面這個 tag。

校驗的時候只需要三步:

  • 調(diào)用 validator.New() 初始化一個校驗器;
  • 將【待校驗的結(jié)構(gòu)體】傳入我們的校驗器的 Struct 方法中;
  • 校驗返回的 error 是否為 nil 即可。

上面的例子中,lidajun 長度符合預(yù)期,18 這個 Age 也在區(qū)間內(nèi),預(yù)期 err 為 nil。而第二個用例 Name 和 Age 都在區(qū)間外。我們運行一下看看結(jié)果:

<nil>
Key: 'User.Name' Error:Field validation for 'Name' failed on the 'min' tag
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'max' tag

這里我們也可以看到,validator 返回的報錯信息包含了 Field 名稱 以及 tag 名稱,這樣我們也容易判斷哪個校驗沒過。

如果沒有 tag,我們自己手寫的話,還需要這樣處理:

func validate(u User) bool {
	if u.Age < 1 || u.Age > 100 {
		return false
	}
	if len(u.Name) < 6 || len(u.Name) > 10 {
		return false
	}
	return true
}

乍一看好像區(qū)別不大,其實一旦結(jié)構(gòu)體屬性變多,校驗規(guī)則變復(fù)雜,這個校驗函數(shù)的代價立刻會上升,另外你還要顯示的處理報錯信息,以達到上面這樣清晰的效果(這個手寫的示例代碼只返回了一個 bool,不好判斷是哪個沒過)。

越是大結(jié)構(gòu)體,越是規(guī)則復(fù)雜,validator 的收益就越高。我們還可以把 validator 放到中間件里面,對所有請求加上校驗,用的越多,效果越明顯。

其實筆者個人使用經(jīng)驗來看,validator 帶來的另外兩個好處在于:

  • 因為需要經(jīng)常使用校驗?zāi)芰?,養(yǎng)成了習(xí)慣,每定義一個結(jié)構(gòu),都事先想好每個屬性應(yīng)該有哪些約束,促使開發(fā)者思考自己的模型。這一點非常重要,很多時候我們就是太隨意定義一些結(jié)構(gòu),沒有對應(yīng)的校驗,結(jié)果導(dǎo)致各種臟數(shù)據(jù),把校驗邏輯一路下沉;
  • 有了 tag 來描述約束規(guī)則,讓結(jié)構(gòu)體本身更容易理解,可讀性,可維護性提高。一看結(jié)構(gòu)體,掃幾眼 tag 就知道業(yè)務(wù)對它的預(yù)期。

這兩個點雖然比較【意識流】,但在開發(fā)習(xí)慣上還是很重要的。

好了,到目前只是淺嘗輒止,下面我們結(jié)合示例看看 validator 到底提供了哪些能力。

使用方法

我們上一節(jié)舉的例子就是最簡單的場景,在一個 struct 中定義好 validate:"xxx" tag,然后調(diào)用校驗器的 err := validate.Struct(user) 方法來校驗。

這一節(jié)我們結(jié)合實例來看看最常用的場景下,我們會怎樣用 validator:

package main
import (
	"fmt"
	"github.com/go-playground/validator/v10"
)
// User contains user information
type User struct {
	FirstName      string     `validate:"required"`
	LastName       string     `validate:"required"`
	Age            uint8      `validate:"gte=0,lte=130"`
	Email          string     `validate:"required,email"`
	FavouriteColor string     `validate:"iscolor"`                // alias for 'hexcolor|rgb|rgba|hsl|hsla'
	Addresses      []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
}
// Address houses a users address information
type Address struct {
	Street string `validate:"required"`
	City   string `validate:"required"`
	Planet string `validate:"required"`
	Phone  string `validate:"required"`
}
// use a single instance of Validate, it caches struct info
var validate *validator.Validate
func main() {
	validate = validator.New()
	validateStruct()
	validateVariable()
}
func validateStruct() {
	address := &Address{
		Street: "Eavesdown Docks",
		Planet: "Persphone",
		Phone:  "none",
	}
	user := &User{
		FirstName:      "Badger",
		LastName:       "Smith",
		Age:            135,
		Email:          "Badger.Smith@gmail.com",
		FavouriteColor: "#000-",
		Addresses:      []*Address{address},
	}
	// returns nil or ValidationErrors ( []FieldError )
	err := validate.Struct(user)
	if err != nil {
		// this check is only needed when your code could produce
		// an invalid value for validation such as interface with nil
		// value most including myself do not usually have code like this.
		if _, ok := err.(*validator.InvalidValidationError); ok {
			fmt.Println(err)
			return
		}
		for _, err := range err.(validator.ValidationErrors) {
			fmt.Println(err.Namespace())
			fmt.Println(err.Field())
			fmt.Println(err.StructNamespace())
			fmt.Println(err.StructField())
			fmt.Println(err.Tag())
			fmt.Println(err.ActualTag())
			fmt.Println(err.Kind())
			fmt.Println(err.Type())
			fmt.Println(err.Value())
			fmt.Println(err.Param())
			fmt.Println()
		}
		// from here you can create your own error messages in whatever language you wish
		return
	}
	// save user to database
}
func validateVariable() {
	myEmail := "joeybloggs.gmail.com"
	errs := validate.Var(myEmail, "required,email")
	if errs != nil {
		fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag
		return
	}
	// email ok, move on
}

仔細(xì)觀察你會發(fā)現(xiàn),第一步永遠(yuǎn)是創(chuàng)建一個校驗器,一個 validator.New() 解決問題,后續(xù)一定要復(fù)用,內(nèi)部有緩存機制,效率比較高。

關(guān)鍵在第二步,大體上分為兩類:

  • 基于結(jié)構(gòu)體調(diào)用 err := validate.Struct(user) 來校驗;
  • 基于變量調(diào)用 errs := validate.Var(myEmail, "required,email")

結(jié)構(gòu)體校驗這個相信看完這個實例,大家已經(jīng)很熟悉了。

變量校驗這里很有意思,用起來確實簡單,大家看 validateVariable 這個示例就 ok,但是,但是,我只有一個變量,我為啥還要用這個 validator ???

原因很簡單,不要以為 validator 只能干一些及其簡單的,比大小,比長度,判空邏輯。這些非常基礎(chǔ)的校驗用一個 if 語句也搞定。

validator 支持的校驗規(guī)則遠(yuǎn)比這些豐富的多。

我們先把前面示例的結(jié)構(gòu)體拿出來,看看支持哪些 tag:

// User contains user information
type User struct {
	FirstName      string     `validate:"required"`
	LastName       string     `validate:"required"`
	Age            uint8      `validate:"gte=0,lte=130"`
	Email          string     `validate:"required,email"`
	FavouriteColor string     `validate:"iscolor"`                // alias for 'hexcolor|rgb|rgba|hsl|hsla'
	Addresses      []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
}
// Address houses a users address information
type Address struct {
	Street string `validate:"required"`
	City   string `validate:"required"`
	Planet string `validate:"required"`
	Phone  string `validate:"required"`
}

格式都是 validate:"xxx",這里不再說,關(guān)鍵是里面的配置。

validator 中如果你針對同一個 Field,有多個校驗項,可以用下面兩種運算符:

  • , 逗號表示【與】,即每一個都需要滿足;
  • | 表示【或】,多個條件滿足一個即可。

我們一個個來看這個 User 結(jié)構(gòu)體出現(xiàn)的 tag:

  • required 要求必須有值,不為空;
  • gte=0,lte=130 其中 gte 代表大于等于,lte 代表小于等于,這個語義是 [0,130] 區(qū)間;
  • required, emal 不僅僅要有值,還得符合 Email 格式;
  • iscolor 后面注釋也提了,這是個別名,本質(zhì)等價于 hexcolor|rgb|rgba|hsl|hsla,屬于 validator 自帶的別名能力,符合這幾個規(guī)則任一的,我們都認(rèn)為屬于表示顏色。
  • required,dive,required 這個 dive 大有來頭,注意這個 Addresses 是個 Address 數(shù)組,我們加 tag 一般只是針對單獨的數(shù)據(jù)類型,這種【容器型】的怎么辦?

這時 dive 的能力就派上用場了。

dive 的語義在于告訴 validator 不要停留在我這一級,而是繼續(xù)往下校驗,無論是 slice, array 還是 map,校驗要用的 tag 就是在 dive 之后的這個。

這樣說可能不直觀,我們來看一個例子:

[][]string with validation tag "gt=0,dive,len=1,dive,required"
// gt=0 will be applied to []
// len=1 will be applied to []string
// required will be applied to string

第一個 gt=0 適用于最外層的數(shù)組,出現(xiàn) dive 后,往下走,len=1 作為一個 tag 適用于內(nèi)層的 []string,此后又出現(xiàn) dive,繼續(xù)往下走,對于最內(nèi)層的每個 string,要求每個都是 required。

[][]string with validation tag "gt=0,dive,dive,required"
// gt=0 will be applied to []
// []string will be spared validation
// required will be applied to string

第二個例子,看看能不能理解?

其實,只要記住,每次出現(xiàn) dive,都往里面走就 ok。

回到我們一開始的例子:

Addresses []*Address validate:"required,dive,required"

表示的意思是,我們要求 Addresses 這個數(shù)組是 required,此外對于每個元素,也得是 required。

內(nèi)置校驗器

validator 對于下面六種場景都提供了豐富的校驗器,放到 tag 里就能用。這里我們簡單看一下:

(注:想看完整的建議參考文檔 以及倉庫 README

1. Fields

對于結(jié)構(gòu)體各個屬性的校驗,這里可以針對一個 field 與另一個 field 相互比較。

2. Network

網(wǎng)絡(luò)相關(guān)的格式校驗,可以用來校驗 IP 格式,TCP, UDP, URL 等

3. Strings

字符串相關(guān)的校驗,用的非常多,比如校驗是否是數(shù)字,大小寫,前后綴等,非常方便。

4. Formats

符合特定格式,如我們上面提到的 email,信用卡號,顏色,html,base64,json,經(jīng)緯度,md5 等

5. Comparisons

比較大小,用的很多

6. Other

雜項,各種通用能力,用的也非常多,我們上面用的 required 就在這一節(jié)。包括校驗是否為默認(rèn)值,最大,最小等。

7. 別名

除了上面的六個大類,還包含兩個內(nèi)部封裝的別名校驗器,我們已經(jīng)用過 iscolor,還有國家碼:

錯誤處理

Golang 的 error 是個 interface,默認(rèn)其實只提供了 Error() 這一個方法,返回一個字符串,能力比較雞肋。同樣的,validator 返回的錯誤信息也是個字符串:

Key: 'User.Name' Error:Field validation for 'Name' failed on the 'min' tag

這樣當(dāng)然不錯,但問題在于,線上環(huán)境下,很多時候我們并不是【人工地】來閱讀錯誤信息,這里的 error 最終是要轉(zhuǎn)化成錯誤信息展現(xiàn)給用戶,或者打點上報的。

我們需要有能力解析出來,是哪個結(jié)構(gòu)體的哪個屬性有問題,哪個 tag 攔截了。怎么辦?

其實 validator 返回的類型底層是 validator.ValidationErrors,我們可以在判空之后,用它來進行類型斷言,將 error 類型轉(zhuǎn)化過來再判斷:

err := validate.Struct(mystruct)
validationErrors := err.(validator.ValidationErrors)

底層的結(jié)構(gòu)我們看一下:

// ValidationErrors is an array of FieldError's
// for use in custom error messages post validation.
type ValidationErrors []FieldError
// Error is intended for use in development + debugging and not intended to be a production error message.
// It allows ValidationErrors to subscribe to the Error interface.
// All information to create an error message specific to your application is contained within
// the FieldError found within the ValidationErrors array
func (ve ValidationErrors) Error() string {
	buff := bytes.NewBufferString("")
	var fe *fieldError
	for i := 0; i &lt; len(ve); i++ {
		fe = ve[i].(*fieldError)
		buff.WriteString(fe.Error())
		buff.WriteString("\n")
	}
	return strings.TrimSpace(buff.String())
}

這里可以看到,所謂 ValidationErrors 其實一組 FieldError,所謂 FieldError 就是每一個屬性的報錯,我們的 ValidationErrors 實現(xiàn)的 func Error() string 方法,也是將各個 fieldError(對 FieldError 接口的默認(rèn)實現(xiàn))連接起來,最后 TrimSpace 清掉空格展示。

在我們拿到了 ValidationErrors 后,可以遍歷各個 FieldError,拿到業(yè)務(wù)需要的信息,用來做日志打印/打點上報/錯誤碼對照等,這里是個 interface,大家各取所需即可:

// FieldError contains all functions to get error details
type FieldError interface {
	// Tag returns the validation tag that failed. if the
	// validation was an alias, this will return the
	// alias name and not the underlying tag that failed.
	//
	// eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla"
	// will return "iscolor"
	Tag() string
	// ActualTag returns the validation tag that failed, even if an
	// alias the actual tag within the alias will be returned.
	// If an 'or' validation fails the entire or will be returned.
	//
	// eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla"
	// will return "hexcolor|rgb|rgba|hsl|hsla"
	ActualTag() string
	// Namespace returns the namespace for the field error, with the tag
	// name taking precedence over the field's actual name.
	//
	// eg. JSON name "User.fname"
	//
	// See StructNamespace() for a version that returns actual names.
	//
	// NOTE: this field can be blank when validating a single primitive field
	// using validate.Field(...) as there is no way to extract it's name
	Namespace() string
	// StructNamespace returns the namespace for the field error, with the field's
	// actual name.
	//
	// eq. "User.FirstName" see Namespace for comparison
	//
	// NOTE: this field can be blank when validating a single primitive field
	// using validate.Field(...) as there is no way to extract its name
	StructNamespace() string
	// Field returns the fields name with the tag name taking precedence over the
	// field's actual name.
	//
	// eq. JSON name "fname"
	// see StructField for comparison
	Field() string
	// StructField returns the field's actual name from the struct, when able to determine.
	//
	// eq.  "FirstName"
	// see Field for comparison
	StructField() string
	// Value returns the actual field's value in case needed for creating the error
	// message
	Value() interface{}
	// Param returns the param value, in string form for comparison; this will also
	// help with generating an error message
	Param() string
	// Kind returns the Field's reflect Kind
	//
	// eg. time.Time's kind is a struct
	Kind() reflect.Kind
	// Type returns the Field's reflect Type
	//
	// eg. time.Time's type is time.Time
	Type() reflect.Type
	// Translate returns the FieldError's translated error
	// from the provided 'ut.Translator' and registered 'TranslationFunc'
	//
	// NOTE: if no registered translator can be found it returns the same as
	// calling fe.Error()
	Translate(ut ut.Translator) string
	// Error returns the FieldError's message
	Error() string
}

小結(jié)

今天我們了解了 validator 的用法,其實整體還是非常簡潔的,我們只需要全局維護一個 validator 實例,內(nèi)部會幫我們做好緩存。此后只需要把結(jié)構(gòu)體傳入,就可以完成校驗,并提供可以解析的錯誤。

validator 的實現(xiàn)也非常精巧,只不過內(nèi)容太多,我們今天暫時覆蓋不到,更多關(guān)于Go 校驗庫validator 的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang中值類型/指針類型的變量區(qū)別總結(jié)

    golang中值類型/指針類型的變量區(qū)別總結(jié)

    golang的值類型和指針類型receiver一直是大家比較混淆的地方,下面這篇文章主要給大家總結(jié)介紹了關(guān)于golang中值類型/指針類型的變量區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。
    2017-12-12
  • Golang?WorkerPool線程池并發(fā)模式示例詳解

    Golang?WorkerPool線程池并發(fā)模式示例詳解

    這篇文章主要為大家介紹了Golang?WorkerPool線程池并發(fā)模式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 一文帶你深入探索Golang操作mongodb的方法

    一文帶你深入探索Golang操作mongodb的方法

    這篇文章主要為大家詳細(xì)介紹了Golang操作mongodb的相關(guān)知識,包括:初始化項目工程、容器方式安裝mongo和調(diào)試運行和編譯運行,感興趣的小伙伴可以了解一下
    2023-02-02
  • Go實現(xiàn)各類限流的方法

    Go實現(xiàn)各類限流的方法

    這篇文章主要介紹了Go實現(xiàn)各類限流的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • Go關(guān)鍵字defer的使用和底層實現(xiàn)

    Go關(guān)鍵字defer的使用和底層實現(xiàn)

    defer是Go語言的關(guān)鍵字,一般用于資源的釋放和異常的捕捉,defer語句后將其后面跟隨的語句進行延遲處理,就是說在函數(shù)執(zhí)行完畢后再執(zhí)行調(diào)用,也就是return的ret指令之前,本文給大家介紹了Go關(guān)鍵字defer的使用和底層實現(xiàn),需要的朋友可以參考下
    2023-11-11
  • Golang實現(xiàn)將中文轉(zhuǎn)化為拼音

    Golang實現(xiàn)將中文轉(zhuǎn)化為拼音

    這篇文章主要為大家詳細(xì)介紹了如何通過Golang實現(xiàn)將中文轉(zhuǎn)化為拼音功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-02-02
  • Go語言清除文件中空行的方法

    Go語言清除文件中空行的方法

    這篇文章主要介紹了Go語言清除文件中空行的方法,實例分析了Go語言針對文件的操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • go cron定時任務(wù)的基本使用講解

    go cron定時任務(wù)的基本使用講解

    這篇文章主要為大家介紹了gocron定時任務(wù)的基本使用講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • Golang 使用Map實現(xiàn)去重與set的功能操作

    Golang 使用Map實現(xiàn)去重與set的功能操作

    這篇文章主要介紹了Golang 使用 Map 實現(xiàn)去重與 set 的功能操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Golang實現(xiàn)文件夾的創(chuàng)建與刪除的方法詳解

    Golang實現(xiàn)文件夾的創(chuàng)建與刪除的方法詳解

    這篇文章主要介紹了如何利用Go語言實現(xiàn)對文件夾的常用操作:創(chuàng)建于刪除。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-05-05

最新評論