golang常用庫:欄位引數驗證庫-validator

九卷發表於2020-10-15

背景

在平常開發中,特別是在web應用開發中,為了驗證輸入欄位的合法性,都會做一些驗證操作。比如對使用者提交的表單欄位進行驗證,或者對請求的API介面欄位進行驗證,驗證欄位的合法性,保證輸入欄位值的安全,防止使用者的惡意請求。

一般的做法是用正規表示式,一個欄位一個欄位的進行驗證。一個一個欄位驗證的話,寫起來比較繁瑣。那有沒更好的方法,進行欄位的合法性驗證?有, 這就是下面要介紹的 validator 這個驗證元件。

程式碼地址:
https://github.com/go-playground/validator

文件地址:
https://github.com/go-playground/validator/blob/master/README.md

安裝

go get:

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

在檔案中引用validator包:

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

validator使用

文件:https://github.com/go-playground/validator/blob/master/README.md#examples

例子1:驗證單個欄位變數值

validation1.go

package main

import (
	"fmt"

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

func main() {
	validate := validator.New()

	var boolTest bool
	err := validate.Var(boolTest, "required")
	if err != nil {
		fmt.Println(err)
	}
	var stringTest string = ""
	err = validate.Var(stringTest, "required")
	if err != nil {
		fmt.Println(err)
	}

	var emailTest string = "test@126.com"
	err = validate.Var(emailTest, "email")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("success") // 輸出: success。 說明驗證成功
	}

	emailTest2 := "test.126.com"
	errs := validate.Var(emailTest2, "required,email")
	if errs != nil {
		fmt.Println(errs) // 輸出: Key: "" Error:Field validation for "" failed on the "email" tag。驗證失敗
	}

	fmt.Println("\r\nEnd!!")
    
    
}

執行輸出:

go run simple1.go
Key: '' Error:Field validation for '' failed on the 'required' tag
Key: '' Error:Field validation for '' failed on the 'required' tag
success
Key: '' Error:Field validation for '' failed on the 'email' tag

End!!

例子2:驗證結構體struct

from:struct validate

validation_struct.go,這個程式還列出了效驗出錯欄位的一些資訊,

package main

import (
	"fmt"

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

type User struct {
	FirstName string     `validate:"required"`
	LastName  string     `validate:"required"`
	Age       uint8      `validate:"gte=0,lte=130"`
	Email     string     `validate:"required,email"`
	Addresses []*Address `validate:"required,dive,required"`
}

type Address struct {
	Street string `validate:"required"`
	City   string `validate:"required"`
	Planet string `validate:"required"`
	Phone  string `validate:"required"`
}

func main() {
	address := &Address{
		Street: "Eavesdown Docks",
		Planet: "Persphone",
		Phone:  "none",
	}

	user := &User{
		FirstName: "Badger",
		LastName:  "Smith",
		Age:       135,
		Email:     "Badger.Smith@gmail.com",
		Addresses: []*Address{address},
	}

	validate := validator.New()
	err := validate.Struct(user)
	if err != nil {
		fmt.Println("=== error msg ====")
		fmt.Println(err)

		if _, ok := err.(*validator.InvalidValidationError); ok {
			fmt.Println(err)
			return
		}

		fmt.Println("\r\n=========== error field info ====================")
		for _, err := range err.(validator.ValidationErrors) {
           // 列出效驗出錯欄位的資訊
			fmt.Println("Namespace: ", err.Namespace())
			fmt.Println("Fild: ", err.Field())
			fmt.Println("StructNamespace: ", err.StructNamespace())
			fmt.Println("StructField: ", err.StructField())
			fmt.Println("Tag: ", err.Tag())
			fmt.Println("ActualTag: ", err.ActualTag())
			fmt.Println("Kind: ", err.Kind())
			fmt.Println("Type: ", err.Type())
			fmt.Println("Value: ", err.Value())
			fmt.Println("Param: ", err.Param())
			fmt.Println()
		}

		// from here you can create your own error messages in whatever language you wish
		return
	}
}

執行 輸出:

$ go run validation_struct.go
=== error msg ====
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
Key: 'User.Addresses[0].City' Error:Field validation for 'City' failed on the 'required' tag

=========== error field info ====================
Namespace: User.Age
Fild: Age
StructNamespace: User.Age
StructField: Age
Tag: lte
ActualTag: lte
Kind: uint8
Type: uint8
Value: 135
Param: 130

Namespace: User.Addresses[0].City
Fild: City
StructNamespace: User.Addresses[0].City
StructField: City
Tag: required
ActualTag: required
Kind: string
Type: string
Value:
Param:

還可以給欄位加一些其他tag資訊,方面form,json的解析,如下:

type User struct {
    FirstName string     `form:"firstname" json:"firstname" validate:"required"`
	LastName  string     `form:"lastname" json:"lastname" validate:"required"`
	Age       uint8      ` form:"age" json:"age"validate:"gte=0,lte=130"`
	Email     string     ` form:"email" json:"email" validate:"required,email"`
}

使用者自定義函式驗證

使用者自定義函式驗證欄位是否合法,效驗是否正確。

例子3: 通過欄位tag自定義函式

validate.RegisterValidation

customer_tag.go:

package main

import (
	"fmt"

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

type User struct {
	Name string `form:"name" json:"name" validate:"required,CustomerValidation"` //注意:required和CustomerValidation之間不能有空格,否則panic。CustomerValidation:自定義tag-函式標籤
	Age  uint8  ` form:"age" json:"age" validate:"gte=0,lte=80"`                 //注意:gte=0和lte=80之間不能有空格,否則panic
}

var validate *validator.Validate

func main() {
	validate = validator.New()
	validate.RegisterValidation("CustomerValidation", CustomerValidationFunc) //註冊自定義函式,前一個引數是struct裡tag自定義,後一個引數是自定義的函式

	user := &User{
		Name: "jimmy",
		Age:  86,
	}

	fmt.Println("first value: ", user)
	err := validate.Struct(user)
	if err != nil {
		fmt.Printf("Err(s):\n%+v\n", err)
	}

	user.Name = "tom"
	user.Age = 29
	fmt.Println("second value: ", user)
	err = validate.Struct(user)
	if err != nil {
		fmt.Printf("Err(s):\n%+v\n", err)
	}
}

// 自定義函式
func CustomerValidationFunc(f1 validator.FieldLevel) bool {
    // f1 包含了欄位相關資訊
    // f1.Field() 獲取當前欄位資訊
    // f1.Param() 獲取tag對應的引數
    // f1.FieldName() 獲取欄位名稱
    
	return f1.Field().String() == "jimmy"
}

執行輸出:

$ go run customer.go
first value: &{jimmy 86}
Err(s):
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
second value: &{tom 29}
Err(s):
Key: 'User.Name' Error:Field validation for 'Name' failed on the 'CustomerValidation' tag

**注意

上面程式碼user struct定義中 ,validate裡的required和CustomerValidation之間不能有空格,否則執行時報panic錯誤:panic: Undefined validation function ' CustomerValidation' on field 'Name'

例子4:自定義函式-直接註冊函式1

不通過欄位tag自定義函式,直接註冊函式。

RegisterStructValidation

https://github.com/go-playground/validator/blob/master/_examples/struct-level/main.go

customer1.go

package main

import (
	"fmt"

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

type User struct {
	FirstName      string `json:firstname`
	LastName       string `json:lastname`
	Age            uint8  `validate:"gte=0,lte=130"`
	Email          string `validate:"required,email"`
	FavouriteColor string `validate:"hexcolor|rgb|rgba"`
}

var validate *validator.Validate

func main() {
	validate = validator.New()

	validate.RegisterStructValidation(UserStructLevelValidation, User{})

	user := &User{
		FirstName:      "",
		LastName:       "",
		Age:            30,
		Email:          "TestFunc@126.com",
		FavouriteColor: "#000",
	}

	err := validate.Struct(user)
	if err != nil {
		fmt.Println(err)
	}
}

func UserStructLevelValidation(sl validator.StructLevel) {
	user := sl.Current().Interface().(User)

	if len(user.FirstName) == 0 && len(user.LastName) == 0 {
		sl.ReportError(user.FirstName, "FirstName", "firstname", "firstname", "")
		sl.ReportError(user.LastName, "LastName", "lastname", "lastname", "")
	}
}

執行輸出:

$ go run customer1.go
Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'firstname' tag
Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'lastname' tag

例子5:自定義函式-直接註冊函式2

RegisterCustomTypeFunc

https://github.com/go-playground/validator/blob/master/_examples/custom/main.go

validate.RegisterCustomTypeFunc:驗證型別的自定義函式

customer2.go:

package main

import (
	"database/sql"
	"database/sql/driver"
	"fmt"
	"reflect"

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

type DbBackedUser struct {
	Name sql.NullString `validate:"required"`
	Age  sql.NullInt64  `validate:"required"`
}

var validate *validator.Validate

func main() {
	validate = validator.New()

	validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})

	// build object for validation
	x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}}

	err := validate.Struct(x)
	if err != nil {
		fmt.Printf("Err(s):\n%+v\n", err)
	}
}

func ValidateValuer(field reflect.Value) interface{} {
	if valuer, ok := field.Interface().(driver.Valuer); ok {
		val, err := valuer.Value()
		if err == nil {
			return val
		}
		// handle the error how you want
	}
	return nil
}

執行輸出:

$ go run customer.go
Err(s):
Key: 'DbBackedUser.Name' Error:Field validation for 'Name' failed on the 'required' tag
Key: 'DbBackedUser.Age' Error:Field validation for 'Age' failed on the 'required' tag

注意,這個函式
RegisterCustomTypeFunc,它上面有2行註釋:

// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
//
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation

它是一個驗證資料型別自定義函式,NOTE:這個方法不是執行緒安全的

相關文章