GoLang Validator 数据验证库入门

validator 开源仓库地址:https://github.com/go-playground/validator

本文基于 validator v10.14.1 版本实现数据验证,自定义验证,自定义错误信息,错误信息翻译的实现。

才疏学浅,如有错误,欢迎指正。

1. 介绍与安装

1.1 介绍

Validator 是一个 Go 语言的开源库,用于对结构体字段进行验证。它支持许多常见的验证规则,例如必填、最小长度、最大长度、正则表达式等。使用 Validator 可以帮助开发者快速简洁地验证结构体字段,从而提高代码质量和安全性。

Validator 的使用非常简单,只需在结构体字段上添加验证规则即可。例如,以下代码验证 Name 字段必须是一个非空字符串:

type User struct {
    Name string `validate:"required"`
}

要验证结构体,只需调用 validator.ValidateStruct() 方法。例如,以下代码验证 User 结构体:

user := User{
    Name: "John Doe",
}

err := validator.ValidateStruct(&user)
if err != nil {
    fmt.Println(err)
}

如果 User 结构体是有效的,则 ValidateStruct() 方法不会返回任何错误。如果 User 结构体无效,则 ValidateStruct() 方法会返回一个包含错误信息的 ValidationErrors 结构体。

Validator 是一个非常强大的验证库,支持许多常见的验证规则。使用 Validator 可以帮助开发者快速简洁地验证结构体字段,从而提高代码质量和安全性。

1.2 安装

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

2. 快速开始

2.1 基本使用

2.1.1 变量验证

package main
import "github.com/go-playground/validator/v10"
var validate *validator.Validate
func main() {
	validate = validator.New()
	// 验证变量
	email := "joeybloggs.gmail.com"
	randomString := "1234567890"
	err := validate.Var(email, "required,email")
	if err != nil {
		println("email:" + err.Error())
	}
	err = validate.Var(randomString, "required,email")
	if err != nil {
		println("random" + err.Error())
	}
}

首先创建validator实例化对象,然后调用validate.Var()方法进行验证,第一个参数是需要验证的变量,第二个参数是验证规则,多个规则用逗号分隔。 如果不出意外,你会看到如下输出:

randomKey: '' Error:Field validation for '' failed on the 'email' tag

上述错误信息的意思是:randomKey字段验证失败,因为randomKey字段的值不是一个有效的邮箱地址。

2.1.2 结构体验证

package main

import "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"`
	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...
}

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

var validate *validator.Validate

func main() {
	validate = validator.New()
	user := User{}
	err := validate.Struct(user)
	if err != nil {
		println(err.Error())
	}
}

上文定义了结构体AddressUser,其中User结构体包含了一个Address结构体的切片。validate.Struct()方法可以验证结构体,如果结构体中包含了其他结构体,那么需要使用dive关键字进行递归验证。

如果不出意外,你会看到如下输出:

Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag
Key: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag
Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag
Key: 'User.FavouriteColor' Error:Field validation for 'FavouriteColor' failed on the 'iscolor' tag
Key: 'User.Addresses' Error:Field validation for 'Addresses' failed on the 'required' tag

上述错误信息的意思是:User结构体中的FirstName字段验证失败,因为FirstName字段的值是一个空字符串。其他的错误信息也是类似的。

2.1.3 内置验证规则

Fields:

TagDescription
eqcsfieldField Equals Another Field (relative)
eqfieldField Equals Another Field
fieldcontainsCheck the indicated characters are present in the Field
fieldexcludesCheck the indicated characters are not present in the field
gtcsfieldField Greater Than Another Relative Field
gtecsfieldField Greater Than or Equal To Another Relative Field
gtefieldField Greater Than or Equal To Another Field
gtfieldField Greater Than Another Field
ltcsfieldLess Than Another Relative Field
ltecsfieldLess Than or Equal To Another Relative Field
ltefieldLess Than or Equal To Another Field
ltfieldLess Than Another Field
necsfieldField Does Not Equal Another Field (relative)
nefieldField Does Not Equal Another Field

Network:

TagDescription
cidrClassless Inter-Domain Routing CIDR
cidrv4Classless Inter-Domain Routing CIDRv4
cidrv6Classless Inter-Domain Routing CIDRv6
datauriData URL
fqdnFull Qualified Domain Name (FQDN)
hostnameHostname RFC 952
hostname_portHostPort
hostname_rfc1123Hostname RFC 1123
ipInternet Protocol Address IP
ip4_addrInternet Protocol Address IPv4
ip6_addrInternet Protocol Address IPv6
ip_addrInternet Protocol Address IP
ipv4Internet Protocol Address IPv4
ipv6Internet Protocol Address IPv6
macMedia Access Control Address MAC
tcp4_addrTransmission Control Protocol Address TCPv4
tcp6_addrTransmission Control Protocol Address TCPv6
tcp_addrTransmission Control Protocol Address TCP
udp4_addrUser Datagram Protocol Address UDPv4
udp6_addrUser Datagram Protocol Address UDPv6
udp_addrUser Datagram Protocol Address UDP
unix_addrUnix domain socket end point Address
uriURI String
urlURL String
http_urlHTTP URL String
url_encodedURL Encoded
urn_rfc2141Urn RFC 2141 String

Strings:

TagDescription
alphaAlpha Only
alphanumAlphanumeric
alphanumunicodeAlphanumeric Unicode
alphaunicodeAlpha Unicode
asciiASCII
booleanBoolean
containsContains
containsanyContains Any
containsruneContains Rune
endsnotwithEnds Not With
endswithEnds With
excludesExcludes
excludesallExcludes All
excludesruneExcludes Rune
lowercaseLowercase
multibyteMulti-Byte Characters
numberNumber
numericNumeric
printasciiPrintable ASCII
startsnotwithStarts Not With
startswithStarts With
uppercaseUppercase

Format:

TagDescription
base64Base64 String
base64urlBase64URL String
base64rawurlBase64RawURL String
bicBusiness Identifier Code (ISO 9362)
bcp47_language_tagLanguage tag (BCP 47)
btc_addrBitcoin Address
btc_addr_bech32Bitcoin Bech32 Address (segwit)
credit_cardCredit Card Number
mongodbMongoDB ObjectID
cronCron
datetimeDatetime
e164e164 formatted phone number
emailE-mail String
eth_addrEthereum Address
hexadecimalHexadecimal String
hexcolorHexcolor String
hslHSL String
hslaHSLA String
htmlHTML Tags
html_encodedHTML Encoded
isbnInternational Standard Book Number
isbn10International Standard Book Number 10
isbn13International Standard Book Number 13
iso3166_1_alpha2Two-letter country code (ISO 3166-1 alpha-2)
iso3166_1_alpha3Three-letter country code (ISO 3166-1 alpha-3)
iso3166_1_alpha_numericNumeric country code (ISO 3166-1 numeric)
iso3166_2Country subdivision code (ISO 3166-2)
iso4217Currency code (ISO 4217)
jsonJSON
jwtJSON Web Token (JWT)
latitudeLatitude
longitudeLongitude
luhn_checksumLuhn Algorithm Checksum (for strings and (u)int)
postcode_iso3166_alpha2Postcode
postcode_iso3166_alpha2_fieldPostcode
rgbRGB String
rgbaRGBA String
ssnSocial Security Number SSN
timezoneTimezone
uuidUniversally Unique Identifier UUID
uuid3Universally Unique Identifier UUID v3
uuid3_rfc4122Universally Unique Identifier UUID v3 RFC4122
uuid4Universally Unique Identifier UUID v4
uuid4_rfc4122Universally Unique Identifier UUID v4 RFC4122
uuid5Universally Unique Identifier UUID v5
uuid5_rfc4122Universally Unique Identifier UUID v5 RFC4122
uuid_rfc4122Universally Unique Identifier UUID RFC4122
md4MD4 hash
md5MD5 hash
sha256SHA256 hash
sha384SHA384 hash
sha512SHA512 hash
ripemd128RIPEMD-128 hash
ripemd128RIPEMD-160 hash
tiger128TIGER128 hash
tiger160TIGER160 hash
tiger192TIGER192 hash
semverSemantic Versioning 2.0.0
ulidUniversally Unique Lexicographically Sortable Identifier ULID
cveCommon Vulnerabilities and Exposures Identifier (CVE id)

Comparisons:

TagDescription
eqEquals
eq_ignore_caseEquals ignoring case
gtGreater than
gteGreater than or equal
ltLess Than
lteLess Than or Equal
neNot Equal
ne_ignore_caseNot Equal ignoring case

Other:

TagDescription
dirExisting Directory
dirpathDirectory Path
fileExisting File
filepathFile Path
imageImage
isdefaultIs Default
lenLength
maxMaximum
minMinimum
oneofOne Of
requiredRequired
required_ifRequired If
required_unlessRequired Unless
required_withRequired With
required_with_allRequired With All
required_withoutRequired Without
required_without_allRequired Without All
excluded_ifExcluded If
excluded_unlessExcluded Unless
excluded_withExcluded With
excluded_with_allExcluded With All
excluded_withoutExcluded Without
excluded_without_allExcluded Without All
uniqueUnique
Aliases:
TagDescription
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

2.2 自定义验证器

package main

import (
	"github.com/go-playground/validator/v10"
	"regexp"
)

var validate *validator.Validate

func IsPhone(fl validator.FieldLevel) bool {
	// 如果是空值则跳过验证
	if fl.Field().String() == "" {
		return true
	}
	// 正则验证手机号
	matched, _ := regexp.MatchString(`^1[3456789]\d{9}$`, fl.Field().String())
	return matched
}

func main() {
	validate = validator.New()
	err := validate.RegisterValidation("isphone", IsPhone)
	if err != nil {
		panic(err)
	}
	randomStr := "1234567890"
	err = validate.Var(randomStr, "isphone")
	if err != nil {
		println(err.Error())
	}
}

上文中的自定义验证器IsPhone,其实就是一个函数,函数的签名如下:

func IsPhone(fl validator.FieldLevel) bool

当然,你也可以使用结构体的方法来实现自定义验证器,只需要将函数签名改为:

func (s *Struct) IsPhone(fl validator.FieldLevel) bool

创建validator实例以后将自定义验证器注册进去即可:

err := validate.RegisterValidation("isphone", IsPhone)

RegisterValidation方法接收两个参数,第一个参数是自定义验证器的名称,第二个参数是自定义验证器的实现,

注册成功后,就可以在验证器中使用了:

如果操作无误的话,你应该会看到如下输出:

Key: '' Error:Field validation for '' failed on the 'isphone' tag

2.3 翻译验证器错误信息

package main

import (
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zhTranslation "github.com/go-playground/validator/v10/translations/zh"
	"reflect"
	"regexp"
)

var validate *validator.Validate
var trans ut.Translator

func IsPhone(fl validator.FieldLevel) bool {
	// 如果是空值则跳过验证
	if fl.Field().String() == "" {
		return true
	}
	// 正则验证手机号
	matched, _ := regexp.MatchString(`^1[3456789]\d{9}$`, fl.Field().String())
	return matched
}
func initTranslate() {
	//初始化错误翻译器
	trans, _ = ut.New(zh.New()).GetTranslator("zh")
	validate = validator.New()
	//使用fld.Tag.Get("comment")注册一个获取tag的自定义方法以实现翻译Field
	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
		return fld.Tag.Get("tag")
	})
	err := validate.RegisterValidation("isphone", IsPhone)
	if err != nil {
		panic(err)
	}
	_ = validate.RegisterTranslation("isphone", trans, func(ut ut.Translator) error {
		return ut.Add("isphone", "{0}该参数不是一个手机号", true)
	}, func(ut ut.Translator, fe validator.FieldError) string {
		t, _ := ut.T("isphone", fe.Field())
		return t
	})
	err = zhTranslation.RegisterDefaultTranslations(validate, trans)
	if err != nil {
		println("注册错误翻译器失败:" + err.Error())
	}
	println("注册错误翻译器成功")
}

type User struct {
	Phone string `json:"phone" validate:"required,isphone" tag:"手机号"`
}

func main() {
	initTranslate()
	err := validate.Struct(User{
		Phone: "0123456789",
	})
	if err != nil {
		err := err.(validator.ValidationErrors)
		for _, e := range err.Translate(trans) {
			println(e)
		}
	}
}

上文中的initTranslate方法,主要是初始化错误翻译器,这里使用的是zh语言包,你也可以使用其他语言包,具体可以参考这里。 我们还是使用上面自定义验证器isphone,只不过这次我们将错误信息翻译成中文,你可以看到输出如下:

注册错误翻译器成功
手机号该参数不是一个手机号

注意翻译器翻译时不包括 Field 字段,因此我们需要在翻译器中添加 {0} 占位符,这样就可以将 Field 字段的值传入翻译器中了。

RegisterTagNameFunc函数的作用是注册一个获取tag的自定义方法以实现翻译Field,这里我们使用了tag作为json的别名,因此我们需要在tag中添加tag字段,这样就可以将tag字段的值传入翻译器中了。

至此,我们已经完成了验证器的基本使用,gin中使用验证器与上述一致。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计
本网站由 提供 CDN加速/云储存服务