概述 🔗
在接口开发经常会遇到一个问题是后端需要写大量的繁琐代码进行数据校验,所以就想着有没有像前端校验一样写规则进行匹配校验,然后就发现了validator包,一个比较强大的校验工具包下面是一些学习总结,详细内容可以查看validator
包下载:go get github.com/go-playground/validator/v10
操作符说明 🔗
标记 |
标记说明 |
, |
多操作符分割 |
| |
或操作符 |
- |
跳过验证字段 |
常用标记说明 🔗
标记 |
标记说明 |
例 |
required |
必填 |
Field或Structvalidate:“required”,如果int,0作为特殊可通过*int判断 |
omitempty |
空时忽略 |
Field或Structvalidate:“omitempty” |
len |
长度 |
Fieldvalidate:“len=0” |
eq |
等于 |
Fieldvalidate:“eq=0” |
gt |
大于 |
Fieldvalidate:“gt=0” |
gte |
大于等于 |
Fieldvalidate:“gte=0” |
lt |
小于 |
Fieldvalidate:“lt=0” |
lte |
小于等于 |
Fieldvalidate:“lte=0” |
eqfield |
同一结构体字段相等 |
Fieldvalidate:“eqfield=Field2” |
nefield |
同一结构体字段不相等 |
Fieldvalidate:“nefield=Field2” |
gtfield |
大于同一结构体字段 |
Fieldvalidate:“gtfield=Field2” |
gtefield |
大于等于同一结构体字段 |
Fieldvalidate:“gtefield=Field2” |
ltfield |
小于同一结构体字段 |
Fieldvalidate:“ltfield=Field2” |
ltefield |
小于等于同一结构体字段 |
Fieldvalidate:“ltefield=Field2” |
eqcsfield |
跨不同结构体字段相等 |
Struct1.Fieldvalidate:“eqcsfield=Struct2.Field2” |
necsfield |
跨不同结构体字段不相等 |
Struct1.Fieldvalidate:“necsfield=Struct2.Field2” |
gtcsfield |
大于跨不同结构体字段 |
Struct1.Fieldvalidate:“gtcsfield=Struct2.Field2” |
gtecsfield |
大于等于跨不同结构体字段 |
Struct1.Fieldvalidate:“gtecsfield=Struct2.Field2” |
ltcsfield |
小于跨不同结构体字段 |
Struct1.Fieldvalidate:“ltcsfield=Struct2.Field2” |
ltecsfield |
小于等于跨不同结构体字段 |
Struct1.Fieldvalidate:“ltecsfield=Struct2.Field2” |
min |
最大值 |
Fieldvalidate:“min=1” |
max |
最小值 |
Fieldvalidate:“max=2” |
structonly |
仅验证结构体,不验证任何结构体字段 |
Structvalidate:“structonly” |
nostructlevel |
不运行任何结构级别的验证 |
Structvalidate:“nostructlevel” |
dive |
向下延伸验证,多层向下需要多个dive标记 |
[][]stringvalidate:“gt=0,dive,len=1,dive,required” |
diveKeys&EndKeys |
与dive同时使用,用于对map对象的键的和值的验证,keys为键,endkeys为值 |
map[string]stringvalidate:“gt=0,dive,keys,eq=1|eq=2,endkeys,required” |
required_with |
其他字段其中一个不为空且当前字段不为空 |
Fieldvalidate:“required_with=Field1Field2” |
required_with_all |
其他所有字段不为空且当前字段不为空 |
Fieldvalidate:“required_with_all=Field1Field2” |
required_without |
其他字段其中一个为空且当前字段不为空 |
Field`validate:“required_without=Field1Field2” |
required_without_all |
其他所有字段为空且当前字段不为空 |
Fieldvalidate:“required_without_all=Field1Field2” |
isdefault |
是默认值 |
Fieldvalidate:“isdefault=0” |
oneof |
其中之一 |
Fieldvalidate:“oneof=579” |
containsfield |
字段包含另一个字段 |
Fieldvalidate:“containsfield=Field2” |
excludesfield |
字段不包含另一个字段 |
Fieldvalidate:“excludesfield=Field2” |
unique |
是否唯一,通常用于切片或结构体 |
Fieldvalidate:“unique” |
alphanum |
字符串值是否只包含ASCII字母数字字符 |
Fieldvalidate:“alphanum” |
alphaunicode |
字符串值是否只包含unicode字符 |
Fieldvalidate:“alphaunicode” |
alphanumunicode |
字符串值是否只包含unicode字母数字字符 |
Fieldvalidate:“alphanumunicode” |
numeric |
字符串值是否包含基本的数值 |
Fieldvalidate:“numeric” |
hexadecimal |
字符串值是否包含有效的十六进制 |
Fieldvalidate:“hexadecimal” |
hexcolor |
字符串值是否包含有效的十六进制颜色 |
Fieldvalidate:“hexcolor” |
lowercase |
符串值是否只包含小写字符 |
Fieldvalidate:“lowercase” |
uppercase |
符串值是否只包含大写字符 |
Fieldvalidate:“uppercase” |
email |
字符串值包含一个有效的电子邮件 |
Fieldvalidate:“email” |
json |
字符串值是否为有效的JSON |
Fieldvalidate:“json” |
file |
符串值是否包含有效的文件路径,以及该文件是否存在于计算机上 |
Fieldvalidate:“file” |
url |
符串值是否包含有效的url |
Fieldvalidate:“url” |
uri |
符串值是否包含有效的uri |
Fieldvalidate:“uri” |
base64 |
字符串值是否包含有效的base64值 |
Fieldvalidate:“base64” |
contains |
字符串值包含子字符串值 |
Fieldvalidate:“contains=@” |
containsany |
字符串值包含子字符串值中的任何字符 |
Fieldvalidate:“containsany=abc” |
containsrune |
字符串值包含提供的特殊符号值 |
Fieldvalidate:“containsrune=☢” |
excludes |
字符串值不包含子字符串值 |
Fieldvalidate:“excludes=@” |
excludesall |
字符串值不包含任何子字符串值 |
Fieldvalidate:“excludesall=abc” |
excludesrune |
字符串值不包含提供的特殊符号值 |
Fieldvalidate:“containsrune=☢” |
startswith |
字符串以提供的字符串值开始 |
Fieldvalidate:“startswith=abc” |
endswith |
字符串以提供的字符串值结束 |
Fieldvalidate:“endswith=abc” |
ip |
字符串值是否包含有效的IP地址 |
Fieldvalidate:“ip” |
ipv4 |
字符串值是否包含有效的ipv4地址 |
Fieldvalidate:“ipv4” |
datetime |
字符串值是否包含有效的日期 |
Fieldvalidate:“datetime” |
使用示例 🔗
使用注意 🔗
- 当搜索条件与特殊标记冲突时,如:逗号(,),或操作(|),中横线(-)等则需要使用 UTF-8十六进制表示形式
type Test struct {
Field1 string `validate:"excludesall=|"` // 错误
Field2 string `validate:"excludesall=0x7C"` // 正确.
}
- 可通过validationErrors := errs.(validator.ValidationErrors)获取错误对象自定义返回响应错误
- 自定义校验结果翻译
// 初始化翻译器
func validateInit() {
zh_ch := zh.New()
uni := ut.New(zh_ch) // 万能翻译器,保存所有的语言环境和翻译数据
Trans, _ = uni.GetTranslator("zh") // 翻译器
Validate = validator.New()
_ = zh_translations.RegisterDefaultTranslations(Validate, Trans)
// 添加额外翻译
_ = Validate.RegisterTranslation("required_without", Trans, func(ut ut.Translator) error {
return ut.Add("required_without", "{0} 为必填字段!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required_without", fe.Field())
return t
})
}
使用示例 🔗
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// 实例化验证对象
var validate = validator.New()
func main() {
// 结构体验证
type Inner struct {
String string `validate:"contains=111"`
}
inner := &Inner{String: "11@"}
errs := validate.Struct(inner)
if errs != nil {
fmt.Println(errs.Error())
}
// 变量验证
m := map[string]string{"": "", "val3": "val3"}
errs = validate.Var(m, "required,dive,keys,required,endkeys,required")
if errs != nil {
fmt.Println(errs.Error())
}
}
gin框架中使用验证器 🔗
定义错误翻译器 🔗
package xcore
import (
"fmt"
"github.com/gin-gonic/gin/binding"
"reflect"
"strings"
//gin表单验证
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
"github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTranslations "github.com/go-playground/validator/v10/translations/en"
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)
// 定义一个全局翻译器
var trans ut.Translator
// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
//修改gin框架中的Validator属性,实现自定制
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册一个获取json tag的自定义方法
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
zhT := zh.New() //中文翻译器
enT := en.New() //英文翻译器
// 第一个参数是备用(fallback)的语言环境
// 后面的参数是应该支持的语言环境(支持多个)
// uni := ut.New(zhT, zhT) 也是可以的
uni := ut.New(enT, zhT, enT)
// locale 通常取决于 http 请求头的 'Accept-Language'
var ok bool
// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
}
// 添加额外翻译
_ = v.RegisterTranslation("required_with", trans, func(ut ut.Translator) error {
return ut.Add("required_with", "{0} 为必填字段!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required_with", fe.Field())
return t
})
_ = v.RegisterTranslation("required_without", trans, func(ut ut.Translator) error {
return ut.Add("required_without", "{0} 为必填字段!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required_without", fe.Field())
return t
})
_ = v.RegisterTranslation("required_without_all", trans, func(ut ut.Translator) error {
return ut.Add("required_without_all", "{0} 为必填字段!", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required_without_all", fe.Field())
return t
})
// 注册翻译器
switch locale {
case "en":
err = enTranslations.RegisterDefaultTranslations(v, trans)
case "zh":
err = zhTranslations.RegisterDefaultTranslations(v, trans)
default:
err = enTranslations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}
func addValueToMap(fields map[string]string) map[string]interface{} {
res := make(map[string]interface{})
for field, err := range fields {
fieldArr := strings.SplitN(field, ".", 2)
if len(fieldArr) > 1 {
NewFields := map[string]string{fieldArr[1]: err}
returnMap := addValueToMap(NewFields)
if res[fieldArr[0]] != nil {
for k, v := range returnMap {
res[fieldArr[0]].(map[string]interface{})[k] = v
}
} else {
res[fieldArr[0]] = returnMap
}
continue
} else {
res[field] = err
continue
}
}
return res
}
// 去掉结构体名称前缀
func removeTopStruct(fields map[string]string) map[string]interface{} {
lowerMap := map[string]string{}
for field, err := range fields {
fieldArr := strings.SplitN(field, ".", 2)
lowerMap[fieldArr[1]] = err
}
res := addValueToMap(lowerMap)
return res
}
//handler中调用的错误翻译方法
func ValidatorError(err error) map[string]interface{} {
errs, ok := err.(validator.ValidationErrors)
if ok {
return removeTopStruct(errs.Translate(trans))
}
return nil
}
使用 🔗
func (c *IndexController) Validator(ctx *gin.Context) {
req := requests.AdminReq{}
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": xcore.ValidatorError(err)})
return
}
ctx.JSON(http.StatusNotFound, "ok")
}