欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang 經典校驗庫 validator 用法解析

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

開篇

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

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

validator

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

validator 是一個結構體參數驗證器。

它提供了【基于 tag 對結構體以及單獨屬性的校驗能力】。經典的 gin 框架就是用了 validator 作為默認的校驗器。它的能力能夠幫助開發(fā)者最大程度地減少【基礎校驗】的代碼,你只需要一個 tag 就能完成校驗。完整的文檔參照 這里

目前 validator 最新版本已經升級到了 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 結構體,我們希望 Name 這個字符串長度在 [6, 10] 這個區(qū)間內,并且希望 Age 這個數字在 [1, 100] 區(qū)間內。就可以用上面這個 tag。

校驗的時候只需要三步:

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

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

<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ū)別不大,其實一旦結構體屬性變多,校驗規(guī)則變復雜,這個校驗函數的代價立刻會上升,另外你還要顯示的處理報錯信息,以達到上面這樣清晰的效果(這個手寫的示例代碼只返回了一個 bool,不好判斷是哪個沒過)。

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

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

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

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

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

使用方法

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

這一節(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
}

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

關鍵在第二步,大體上分為兩類:

  • 基于結構體調用 err := validate.Struct(user) 來校驗;
  • 基于變量調用 errs := validate.Var(myEmail, "required,email")

結構體校驗這個相信看完這個實例,大家已經很熟悉了。

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

原因很簡單,不要以為 validator 只能干一些及其簡單的,比大小,比長度,判空邏輯。這些非?;A的校驗用一個 if 語句也搞定。

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

我們先把前面示例的結構體拿出來,看看支持哪些 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",這里不再說,關鍵是里面的配置。

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

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

我們一個個來看這個 User 結構體出現的 tag:

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

這時 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 適用于最外層的數組,出現 dive 后,往下走,len=1 作為一個 tag 適用于內層的 []string,此后又出現 dive,繼續(xù)往下走,對于最內層的每個 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

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

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

回到我們一開始的例子:

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

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

內置校驗器

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

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

1. Fields

對于結構體各個屬性的校驗,這里可以針對一個 field 與另一個 field 相互比較。

2. Network

網絡相關的格式校驗,可以用來校驗 IP 格式,TCP, UDP, URL 等

3. Strings

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

4. Formats

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

5. Comparisons

比較大小,用的很多

6. Other

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

7. 別名

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

錯誤處理

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

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

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

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

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

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

底層的結構我們看一下:

// 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 實現的 func Error() string 方法,也是將各個 fieldError(對 FieldError 接口的默認實現)連接起來,最后 TrimSpace 清掉空格展示。

在我們拿到了 ValidationErrors 后,可以遍歷各個 FieldError,拿到業(yè)務需要的信息,用來做日志打印/打點上報/錯誤碼對照等,這里是個 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
}

小結

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

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

相關文章

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

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

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

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

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

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

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

    Go實現各類限流的方法

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

    Go關鍵字defer的使用和底層實現

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

    Golang實現將中文轉化為拼音

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

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

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

    go cron定時任務的基本使用講解

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

    Golang 使用Map實現去重與set的功能操作

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

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

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

最新評論