前言
在go项目中,经常有校验数据合法性的需求,比如邮箱、年龄、车牌号、网址、字符串长度、金额、枚举范围等。一个好的校验包能帮我们少写很多ifelse,提高系统的可维护性。
validate包内置了丰富的校验语法,包括字符串、数字、邮箱等,当然如果我们有定制化的校验提示文案或者校验规则,validate也支持定制化校验。
安装
# 安装validate包
$ go get github.com/go-playground/validator/v10
# 项目中引入包
import "github.com/go-playground/validator/v10"
简单使用
校验语法:在字段tag上,也就是字段类型后面的`xxx`里内容,加上validate:“yyy”,其中yyy就是validate的校验语法;
校验方法:使用err := validate.Struct(u)进行校验,err是校验结果
结果输出:我们可以对err进行反射判断处理,可以获取字段名等信息,定制化输出校验结果
下面是一个简单示例:
import (
"fmt"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zhTrans "github.com/go-playground/validator/v10/translations/zh"
)
type User struct {
Name string
Age int `validate:"required,gte=18,lte=55"`
Email string `validate:"required,email"`
}
func ValidTestSimple() {
u := &User{
Name: "tt",
Age: 12,
Email: "abcde@gmail",
}
validSimple := validator.New()
err := validSimple.Struct(u)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
fmt.Printf(e.String())
fmt.Println()
}
} else {
fmt.Println("validate success!")
}
}
func main() {
ValidTestSimple()
}
让我们看看输出
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tag
Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag
看这个这个输出,validate已经生效,校验了age字段必须是18-55的数字范围,12不符合;email字段abcde@gmail不是一个合法的邮箱,是不是很简单,继续往下看。
错误处理
Validator对于错误的验证输入只返回InvalidValidationError,nil或ValidationErrors作为类型错误;因此,在您的代码中,您所需要做的就是检查返回的错误是否不是nil,如果不是,则检查错误是否为InvalidValidationError(如有必要,大多数情况下不是),将其类型转换为ValidationErrors类型,如下所示:
err := validate.Struct(mystruct)
validationErrors := err.(validator.ValidationErrors)
翻译器
一般来说,对于数据验证结果文案,在产品上有特殊要求,我们可以按照上面的错误处理,遍历validationErrors,判断字段类型,指定文案返回。
通常,如果对文案没有指定要求,我们可以使用下面的翻译器配合验证对象,输出中文容易理解的验证结果,而不是返回内置的英文验证结果。
翻译器包:github.com/go-playground/universal-translator@v0.18.1
配合validate使用示例:
package main
import (
"fmt"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zhTrans "github.com/go-playground/validator/v10/translations/zh"
)
type User struct {
Name string
Age int `validate:"required,gte=18,lte=55"`
Email string `validate:"required,email"`
}
func ValidTestSimple() {
u := &User{
Name: "tt",
Age: 12,
Email: "abcde@gmail",
}
// 新建验证对象
validSimple := validator.New()
// 新建中文翻译器,类型为
// type UniversalTranslator struct {
// translators map[string]Translator
// fallback Translator
// }
trans := ut.New(zh.New())
// 取其中的Translator
zhTran, _ := trans.GetTranslator("zh")
// 注册翻译器到验证对象上
zhTrans.RegisterDefaultTranslations(validSimple, zhTran)
// 开始验证
err := validSimple.Struct(u)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
// 输出翻译后的验证结果
fmt.Printf(e.Translate(zhTran))
fmt.Println()
// 输出未翻译的验证结果
fmt.Println(e.Error())
}
} else {
fmt.Println("validate success!")
}
}
func main() {
ValidTestSimple()
}
未添加翻译器时:
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tag
Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag
添加翻译器时校验结果:
Age必须大于或等于18
Email必须是一个有效的邮箱
Validator库介绍
校验语法常用标记
标记 | 作用 | 示例 |
---|---|---|
len | 长度 | validate:“len=12” ,只能用等于,不能用其他比较符号 |
gt | 大于 | validate:“gt=12”,只能用于数字类型 |
gte | 大于等于 | validate:“gte=0”,只能用于数字类型 |
lt | 小于 | validate:“lt=0”,只能用于数字类型 |
lte | 小于等于 | validate:“lte=12”,只能用于数字类型 |
min | 最小值 | validate:“min=12”,只能用于数字类型 |
max | 最大值 | validate:“max=12”,只能用于数字类型 |
oneof | 其中之一 | validate:“oneof=cc vv aa” |
unique | 是否唯一,通常用于数组和切片 | validate:“unique” |
numeric | 字符串是否只包含基础的数值 | validate:“numeric” |
json | 字符串是否为有效的json | validate:“json” |
contains | 字符串是否包含子字符串的值 | validate:“contains=aa” |
url | 字符串是否为有效的url | validate:“url” |
ip | 字符串是否为有效的ip | validate:“ip” |
自定义校验
需求【校验车身颜色】
需求描述
车身颜色枚举为【黑色、白色、绿色、红色、粉色】,如果输入的车身颜色不在这个范围中,输出校验结果:车身颜色取值必须是黑色、白色、绿色、红色、粉色;
思路
- 初始化validate包
- 自定义车身颜色枚举
- 自定义车身颜色校验方法和输出格式
- 捕捉validate校验的错误,判断是否为枚举
- 填充输出返回
代码
1.初始化validate
package customvalid
import (
"bytes"
"errors"
"fmt"
locales_zh "github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
translations_zh "github.com/go-playground/validator/v10/translations/zh"
"validate/customvalid/enum"
)
const (
ValidEnum = "valid_enum"
ValidEnumComment = "%s取值范围只能是%s"
ValidEnumCommentNotFound = "校验失败,%s没有对应的枚举"
)
var validateObj *validator.Validate
var globalZhTranslator ut.Translator
func InitValid() {
registerZhTransactions()
registerCustomValid()
}
// 注册中文翻译器
func registerZhTransactions() {
universalTranslator := ut.New(locales_zh.New(), locales_zh.New())
zhTranslator, ok := universalTranslator.GetTranslator("zh")
if !ok {
panic("无可用翻译器")
}
validateObj = validator.New()
globalZhTranslator = zhTranslator
translations_zh.RegisterDefaultTranslations(validateObj, zhTranslator)
}
// 注册校验器
func registerCustomValid() {
validateObj = validator.New()
validateObj.RegisterValidation(ValidEnum, ValidEnumFc)
}
func ValidateStruct(s interface{}) error {
buffer := bytes.NewBufferString("")
err := validateObj.Struct(s)
if err != nil {
for _, errField := range err.(validator.ValidationErrors) {
switch errField.Tag() {
case ValidEnum:
enumType := enum.BuildEnum(errField.Param())
if enumType == nil {
buffer.WriteString(fmt.Sprintf(ValidEnumCommentNotFound, errField.StructField()))
} else {
buffer.WriteString(fmt.Sprintf(ValidEnumComment, errField.StructField(), enumType.AllEnumComments()))
}
}
}
}
if buffer.String() != "" {
return errors.New(buffer.String())
}
return nil
}
// 校验在固定枚举中
func ValidEnumFc(fl validator.FieldLevel) bool {
fieldName := fl.Param()
enumType := enum.BuildEnum(fieldName)
return enumType != nil && enumType.ValidEnumValue((int32)(fl.Field().Int()))
}
demo仓库:https://gitee.com/fengyurong/validate,敬请star!!