参数校验---gin框架内置使用validator

一、标记之前特殊符号说明

逗号(,):把多个验证标记分开。注意逗号之间不能有空格

横线(-):该字段不验证,跳过

竖线(|):使用多个验证标记,但是只需要满足其中一个即可

required:    表示改字段必须设置,并且不能作为默认值

omitempty: 如果字段未设置,则忽略它

omitempty,xxx=xxx # 可选,如果存在,则继续向后校验规则xxx=xxx,如果不存在,则xxx=xxx不生效,但是如果omitempty之前存在校验规则,则前面的校验规则还是生效的,如 gte=-1,omitempty,len=3,则gte=-1规则始终生效,而len=3只有在值不为0时生效。

二、验证范围

1:验证范围:切片、数组和map、字符串,验证其长度;数值,验证大小范围

lte : 小于等于参数值,validate:"lte=3" (小于等于3)

gte : 大于等于参数值,validate:"lte=120,gte=0" (大于等于0小于等于120)

lt:小于参数值,validate:"lt=3" (小于3)

gt:大于参数值,validate:"lt=120,gt=0" (大于0小于120)

len:等于参数值,validate:"len=2"

max:最大值,小于等于参数值,validate:"max=20" (小于等于20)

min:最小值,大于等于参数值,validate:"min=2,max=20" (大于等于2小于等于20)

ne:不等于,validate:"ne=2" (不等于2)

oneof:只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号包围,validate:"oneof=red green"

2:字符串验证

contains:包含参数子串,validate:"contains=tom" (字段的字符串值包含tom)

excludes:包含参数子串,validate:"excludes=tom" (字段的字符串值不包含tom)

startswith:以参数子串为前缀,validate:"startswith=golang"

endswith:以参数子串为后缀,validate:"startswith=world"

 
max=10 # 最大长度为10

min=10 # 最小长度为10

gt=10 # 长度大于10

lt=10 # 长度小于10

gte=10 # 长度大于等于10

let=10 # 长度小于等于10

 
eq=aaa # 值为aaa

ne=aaa # 值不能为aaa

oneof=a b c # 枚举,只能为a、b 或 c

len=3 # 字符长度为3

3:字段验证

eqcsfield : 跨不同结构体字段验证,比如说 struct filed1 与 struct filed2 相等

necsfield :跨不同结构体字段不相等

eqfield :同一结构体字段验证相等,最常见的就是输入2次密码验证

【

  Password   string `json:"password" binding:"required"`
    RePassword string `json:"re_password" binding:"required,eqfield=Password"`
】

nefield :同一结构体字段验证不相等

gtefield:大于等于同一结构体字段,validate:"gtefiled=Field2"

ltefield:小于等于同一结构体字段

 一个层级内部校验

qfield=AAA # 和字段AAA值相等
nefield=AAA # 和字段AAA值不相等
gtfield=AAA # 大于字段AAA的值
gtefield=AAA # 大于等于字段AAA的值
ltfield=AAA # 小于字段AAA的值
ltefield=AAA # 小于等于AAA字段的值
数组校验 dive

Domains []string `binding:"gt=0,dive,required,min=1,max=100"`

检验内容:[]string长度必须大于0,数组中元素string长度必须在1-100之间

 

dive对Map校验

ReqHeaders map[string]string `binding:"dive,keys,min=1,max=100,endkeys,required,min=1,max=100"`

这里使用了 keys 和 endkeys来标记key值的校验范围,从keys开始,至endkeys结束

校验内容:未对整体做校验,限制key值长度必须在1-100之间,value值的长度也必须在1-100之间

 

structonly

当一个结构体定义了校验规则,但是在某些地方,不需要这些校验规则生效的时候,可以使用structonly标记,存在此标记的结构体内部的校验规则将不会再生效。如下:


type Timeout struct {
    Connect int `json:"connect" binding:"required"`
    Read    int `json:"read" binding:"required"`
    Send    int `json:"send" binding:"required"`
}

type MyStruct struct {
    Name    string   `json:"name" binding:"required"`
    Timeout Timeout `json:"timeout" binding:"structonly"` 
}
结构体Timeout各个字段定义了校验内容,但是我在MyStruct的Timeout字段使用了structonly标记,那么Timeout中定义的校验内容将不再生效

4:网络验证

ip:字段值是否包含有效的IP地址,validate:"ip"

ipv4:字段值是否包含有效的ipv4地址,validate:"ipv4"

ipv6:字段值是否包含有效的ipv6地址,validate:"ipv6"

uri:字段值是否包含有效的uri,validate:"uri"

url:字段值是否包含有效的uri,validate:"url"

5:日期校验

Date       string `json:"date" binding:"required,datetime=2006-01-02,checkDate"`
其中datetime=2006-01-02是内置的用于校验日期类参数是否满足指定格式要求的tag。 
如果传入的date参数不满足2006-01-02这种格式就会提示如下错误:

 

例子


type SignUpParam struct {
   Age uint8 `json:"age" binding:"gte=1,lte=130"`
   Name string `json:"name" binding:"required"`
   Email string `json:"email" binding:"required,email"`
   Password string `json:"password" binding:"required,max=13,min=1"`
   RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

三:翻译校验错误提示信息

  validator 库本身是支持国际化的,借助相应的语言包可以实现校验错误提示信息的自动翻译。下面例子是翻译为中文的。

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/locales/en"
    "github.com/go-playground/locales/zh"
    ut "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"
)

// 定义一个全局翻译器T
var trans ut.Translator

// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
    // 修改gin框架中的Validator引擎属性,实现自定制
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {

        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)
        }

        // 注册翻译器
        switch locale {
        case "en":
            err = enTranslations.RegisterDefaultTranslations(v, trans)
        case "zh":
            err = zhTranslations.RegisterDefaultTranslations(v, trans)
        default:
            err = enTranslations.RegisterDefaultTranslations(v, trans)
        }
        return
    }
    return
}

type SignUpParam struct {
    Age        uint8  `json:"age" binding:"gte=1,lte=130"`
    Name       string `json:"name" binding:"required"`
    Email      string `json:"email" binding:"required,email"`
    Password   string `json:"password" binding:"required"`
    RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

func main() {
    if err := InitTrans("zh"); err != nil {
        fmt.Printf("init trans failed, err:%v\n", err)
        return
    }

    r := gin.Default()

    r.POST("/signup", func(c *gin.Context) {
        var u SignUpParam
        if err := c.ShouldBind(&u); err != nil {
            // 获取validator.ValidationErrors类型的errors
            errs, ok := err.(validator.ValidationErrors)
            if !ok {
                // 非validator.ValidationErrors类型错误直接返回
                c.JSON(http.StatusOK, gin.H{
                    "msg": err.Error(),
                })
                return
            }
            // validator.ValidationErrors类型错误则进行翻译
            c.JSON(http.StatusOK, gin.H{
                "msg":errs.Translate(trans),
            })
            return
        }
        // 保存入库等具体业务逻辑代码...

        c.JSON(http.StatusOK, "success")
    })

    _ = r.Run(":8999")
}

自定义错误提示信息的字段名
上面的错误提示看起来是可以了,但是还是差点意思,首先是错误提示中的字段并不是请求中使用的字段,例如:RePassword是我们后端定义的结构体中的字段名,而请求中使用的是re_password字段。如何是错误提示中的字段使用自定义的名称,例如jsontag指定的值呢?

只需要在初始化翻译器的时候像下面一样添加一个获取json tag的自定义方法即可。

// 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)

        //...
}
{"msg":{"SignUpParam.email":"email必须是一个有效的邮箱",
	"SignUpParam.password":"password为必填字段",
	"SignUpParam.re_password":"re_password为必填字段"}
	}

可以看到现在错误提示信息中使用的就是我们结构体中jsontag设置的名称了。

但是还是有点瑕疵,那就是最终的错误提示信息中心还是有我们后端定义的结构体名称——SignUpParam,这个名称其实是不需要随错误提示返回给前端的,前端并不需要这个值。我们需要想办法把它去掉。

这里参考https://github.com/go-playground/validator/issues/633#issuecomment-654382345提供的方法,定义一个去掉结构体名称前缀的自定义方法

func removeTopStruct(fields map[string]string) map[string]string {
    res := map[string]string{}
    for field, err := range fields {
        res[field[strings.Index(field, ".")+1:]] = err
    }
    return res
}

我们在代码中使用上述函数将翻译后的errors做一下处理即可:

if err := c.ShouldBind(&u); err != nil {
    // 获取validator.ValidationErrors类型的errors
    errs, ok := err.(validator.ValidationErrors)
    if !ok {
        // 非validator.ValidationErrors类型错误直接返回
        c.JSON(http.StatusOK, gin.H{
            "msg": err.Error(),
        })
        return
    }
    // validator.ValidationErrors类型错误则进行翻译
    // 并使用removeTopStruct函数去除字段名中的结构体名称标识
    c.JSON(http.StatusOK, gin.H{
        "msg": removeTopStruct(errs.Translate(trans)),
    })
    return
}

看一下最终的效果:

{"msg":{"email":"email必须是一个有效的邮箱",
	"password":"password为必填字段","re_password":"re_password为必填字段"}}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值