Golang使用validator進行數據校驗及自定義翻譯器
包下載:go get github.com/go-playground/validator/v10
一、概述
在接口開發經常會遇到一個問題是后端需要寫大量的繁瑣代碼進行數據校驗,所以就想着有沒有像前端校驗一樣寫規則進行匹配校驗,然后就發現了validator包,一個比較強大的校驗工具包下面是一些學習總結,詳細內容可以查看validator
二、操作符說明
標記 | 標記說明 |
---|---|
, | 多操作符分割 |
| | 或操作 |
- | 跳過字段驗證 |
三、常用標記說明
標記 | 標記說明 | 例 |
---|---|---|
required | 必填 | Field或Struct validate:"required" |
omitempty | 空時忽略 | Field或Struct validate:"omitempty" |
len | 長度 | Field validate:"len=0" |
eq | 等於 | Field validate:"eq=0" |
gt | 大於 | Field validate:"gt=0" |
gte | 大於等於 | Field validate:"gte=0" |
lt | 小於 | Field validate:"lt=0" |
lte | 小於等於 | Field validate:"lte=0" |
eqfield | 同一結構體字段相等 | Field validate:"eqfield=Field2" |
nefield | 同一結構體字段不相等 | Field validate:"nefield=Field2" |
gtfield | 大於同一結構體字段 | Field validate:"gtfield=Field2" |
gtefield | 大於等於同一結構體字段 | Field validate:"gtefield=Field2" |
ltfield | 小於同一結構體字段 | Field validate:"ltfield=Field2" |
ltefield | 小於等於同一結構體字段 | Field validate:"ltefield=Field2" |
eqcsfield | 跨不同結構體字段相等 | Struct1.Field validate:"eqcsfield=Struct2.Field2" |
necsfield | 跨不同結構體字段不相等 | Struct1.Field validate:"necsfield=Struct2.Field2" |
gtcsfield | 大於跨不同結構體字段 | Struct1.Field validate:"gtcsfield=Struct2.Field2" |
gtecsfield | 大於等於跨不同結構體字段 | Struct1.Field validate:"gtecsfield=Struct2.Field2" |
ltcsfield | 小於跨不同結構體字段 | Struct1.Field validate:"ltcsfield=Struct2.Field2" |
ltecsfield | 小於等於跨不同結構體字段 | Struct1.Field validate:"ltecsfield=Struct2.Field2" |
min | 最大值 | Field validate:"min=1" |
max | 最小值 | Field validate:"max=2" |
structonly | 僅驗證結構體,不驗證任何結構體字段 | Struct validate:"structonly" |
nostructlevel | 不運行任何結構級別的驗證 | Struct validate:"nostructlevel" |
dive | 向下延伸驗證,多層向下需要多個dive標記 | [][]string validate:"gt=0,dive,len=1,dive,required" |
dive Keys & EndKeys | 與dive同時使用,用於對map對象的鍵的和值的驗證,keys為鍵,endkeys為值 | map[string]string validate:"gt=0,dive,keys,eq=1\|eq=2,endkeys,required" |
required_with | 其他字段其中一個不為空且當前字段不為空 | Field validate:"required_with=Field1 Field2" |
required_with_all | 其他所有字段不為空且當前字段不為空 | Field validate:"required_with_all=Field1 Field2" |
required_without | 其他字段其中一個為空且當前字段不為空 | Field `validate:"required_without=Field1 Field2" |
required_without_all | 其他所有字段為空且當前字段不為空 | Field validate:"required_without_all=Field1 Field2" |
isdefault | 是默認值 | Field validate:"isdefault=0" |
oneof | 其中之一 | Field validate:"oneof=5 7 9" |
containsfield | 字段包含另一個字段 | Field validate:"containsfield=Field2" |
excludesfield | 字段不包含另一個字段 | Field validate:"excludesfield=Field2" |
unique | 是否唯一,通常用於切片或結構體 | Field validate:"unique" |
alphanum | 字符串值是否只包含 ASCII 字母數字字符 | Field validate:"alphanum" |
alphaunicode | 字符串值是否只包含 unicode 字符 | Field validate:"alphaunicode" |
alphanumunicode | 字符串值是否只包含 unicode 字母數字字符 | Field validate:"alphanumunicode" |
numeric | 字符串值是否包含基本的數值 | Field validate:"numeric" |
hexadecimal | 字符串值是否包含有效的十六進制 | Field validate:"hexadecimal" |
hexcolor | 字符串值是否包含有效的十六進制顏色 | Field validate:"hexcolor" |
lowercase | 符串值是否只包含小寫字符 | Field validate:"lowercase" |
uppercase | 符串值是否只包含大寫字符 | Field validate:"uppercase" |
字符串值包含一個有效的電子郵件 | Field validate:"email" |
|
json | 字符串值是否為有效的 JSON | Field validate:"json" |
file | 符串值是否包含有效的文件路徑,以及該文件是否存在於計算機上 | Field validate:"file" |
url | 符串值是否包含有效的 url | Field validate:"url" |
uri | 符串值是否包含有效的 uri | Field validate:"uri" |
base64 | 字符串值是否包含有效的 base64值 | Field validate:"base64" |
contains | 字符串值包含子字符串值 | Field validate:"contains=@" |
containsany | 字符串值包含子字符串值中的任何字符 | Field validate:"containsany=abc" |
containsrune | 字符串值包含提供的特殊符號值 | Field validate:"containsrune=☢" |
excludes | 字符串值不包含子字符串值 | Field validate:"excludes=@" |
excludesall | 字符串值不包含任何子字符串值 | Field validate:"excludesall=abc" |
excludesrune | 字符串值不包含提供的特殊符號值 | Field validate:"containsrune=☢" |
startswith | 字符串以提供的字符串值開始 | Field validate:"startswith=abc" |
endswith | 字符串以提供的字符串值結束 | Field validate:"endswith=abc" |
ip | 字符串值是否包含有效的 IP 地址 | Field validate:"ip" |
ipv4 | 字符串值是否包含有效的 ipv4地址 | Field validate:"ipv4" |
datetime | 字符串值是否包含有效的 日期 | Field validate:"datetime" |
四、標記使用注意
1、當搜索條件與特殊標記沖突時,如:逗號(,),或操作(|),中橫線(-)等則需要使用 UTF-8十六進制表示形式
例:
type Test struct {
Field1 string `validate:"excludesall=|"` // 錯誤
Field2 string `validate:"excludesall=0x7C"` // 正確.
}
2、可通過validationErrors := errs.(validator.ValidationErrors)獲取錯誤對象自定義返回響應錯誤
3、自定義校驗結果翻譯
// 初始化翻譯器
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框架中使用驗證翻譯器
1. 定義錯誤翻譯器
//validator.go
// 定義一個全局翻譯器
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 ErrResp(err error) *gin.H {
errs, ok := err.(validator.ValidationErrors)
fmt.Println(reflect.TypeOf(err))
if !ok {
return &gin.H{
"errCode": -1,
"errMsg": err.Error(), // 翻譯校驗錯誤提示
}
}
return &gin.H{
"errCode": -1,
"errMsg": removeTopStruct(errs.Translate(trans)), // 翻譯校驗錯誤提示
}
}
2.使用
//router.go
//初始化翻譯器,這部分可放在main.go或router.go中
if err := util.InitTrans("zh"); err != nil {
log.Fatalf("init trans failed, err:%v\n", err)
return
}
//handler.go
//翻譯錯誤
func (h handler) Login(c *gin.Context) {
req := models.LoginReq{}
err := c.ShouldBindWith(&req, binding.JSON)
if err != nil {
c.JSON(http.StatusBadRequest, util.ErrResp(err)) //這里調用翻譯
return
}
}