关于它
validator (github)是一个验证数据的工具,可以验证struct(通过tag标签)以及其他一些基本类型string,int…,通常用于验证客户端传过来的数据合法性,再结合一些web框架,(如 gin等)更是非常好用。
简单讲
常见类型验证
- 验证值:
validate.Var("xxx@mail.com", "required,email")
验证struct:
err := validate.Struct(user)
validator 验证原理是通过对struct的tag标签进行验证,user 是个struct 实例,struct的声明中,必须有tag说明验证规则,可见下面demo。验证后的返回值
语句err := validate.xxxx(user)进行验证,返回值的处理顺序参考demo, 返回结果主要有三种:
nil正常返回值,验证成功
- InvalidValidationError: 传递的参数不合法,不是要验证的类型。这个很少需要验证,一般不会这种错误
ValidationErrors:一个数组,包含多个验证失败的信息
自带规则
validator 已经帮我们定义好了很多默认的验证规则,(比如必须有值(required), 验证长度(len), 有效邮箱(email) ),我们可以直接拿来使用,规则详细见: 跳转 ,文档见: 跳转,如果自带的规则无法满足需求,我们也可以自己定制,见下面demo
blog
- validator v10 结合 gin 的使用
版本比较
这里以V9新版本来讲,也是笔者现在在用的版本。V8已经过代,但是还是有很多在用,后面demo简单一展现。
v8与v9差异
tagname
v8默认tag标签为required,v9为validate
required
由于golang的特性,结构体基础数据类型没有赋值会默认零值(int默认0,string默认””等),所以require不能校验出基础类型是默认零值,还是被赋为了零值。比如:CommType int64
json:”comm_type”validate:”exists”<br />这样无法判断是传入了0表示某种商品类型,还是根本就没传,一种解决办法是:<br />`CommType *int64 `json:"comm_type" validate:"exists"
改成指针类型,这样没传就是nil,传了0就不是nil,这样就区分开了,如果没传就不能通过校验。
v8和v9两个版本,在零值和nil的校验上有一些区别:
v8 required 和 exists 的区别:required nil和零值都不能通过检验,exists 零值能通过,nil不能通过;
v8 和 v9 的区别:v9没有exists了,统一用require,用在基础类型上零值不能通过,用在指针上nil不能通过而零值能通过。
v9的做法比较好,做了统一,因为指针的零值就是nil,所以统一的来说:require零值不能通过。简洁有效!
对比表:
v8:
exists | nil | 0 | 1 |
---|---|---|---|
int64 | √ | √ | √ |
*int64 | X | √ | √ |
require | nil | 0 | 1 |
---|---|---|---|
int64 | X | X | √ |
*int64 | X | X | √ |
v9:
require | nil | 0 | 1 |
---|---|---|---|
int64 | X | X | √ |
*int64 | X | √ | √ |
require | nil | “” | “hello” |
---|---|---|---|
string | X | X | √ |
*string | X | √ | √ |
v8代码迁移到v9
可以见:跳转 , 或者直接看下面v9demo
主要版本的DEMO
v10
v9
官方的demo
跳转 很详细,推荐看看
没有结合web框架的普通版本
package paramValidator
/**
demo:
var param struct {
Em string `validate:"required,myParam"`
}
err = Validate.Struct(param)
*/
import (
"fmt"
"gopkg.in/go-playground/validator.v9"
)
var Validate = validator.New()
func Setup() {
// 我们可以在这里定义自己的验证规则,例:
Validate.RegisterValidation("myParam", myParam)
}
func myParam(fl validator.FieldLevel) bool {
fmt.Println("FieldName:", fl.FieldName())
fmt.Println("StructFieldName", fl.StructFieldName())
fmt.Println("Parm:", fl.Param())
return false
}
与gin结合框架版
package paramValidator
import (
"fmt"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v9"
"reflect"
"sync"
)
type defaultValidator struct {
once sync.Once
validate *validator.Validate
}
var _ binding.StructValidator = &defaultValidator{}
func Setup() {
binding.Validator = new(defaultValidator)
}
func (v *defaultValidator) lazyinit() {
v.once.Do(func() {
v.validate = validator.New()
// 绑定tag标签的name
v.validate.SetTagName("binding")
// add any custom validations etc. here
// 我们可以在这里定义自己的验证规则,例:
v.validate.RegisterValidation("myParam", myParam)
})
}
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
if kindOfData(obj) == reflect.Struct {
v.lazyinit()
if err := v.validate.Struct(obj); err != nil {
return error(err)
}
}
return nil
}
func (v *defaultValidator) Engine() interface{} {
v.lazyinit()
return v.validate
}
func kindOfData(data interface{}) reflect.Kind {
value := reflect.ValueOf(data)
valueType := value.Kind()
if valueType == reflect.Ptr {
valueType = value.Elem().Kind()
}
return valueType
}
func myParam(fl validator.FieldLevel) bool {
fmt.Println("FieldName:", fl.FieldName())
fmt.Println("StructFieldName", fl.StructFieldName())
fmt.Println("Parm:", fl.Param())
return false
}
package main
import (
"paramValidator"
"github.com/gin-gonic/gin"
)
func main() {
var r = gin.New()
paramValidator.Setup()
...
groupApi.POST("test", test)
}
func test(c *gin.Context) {
// 接收参数
var param struct {
Em int64 `binding:"required,myParam"`
TM *string `binding:"required"`
AM string `binding:"required"`
}
err := c.ShouldBind(¶m)
fmt.Println(err,param)
}
v8
package main
import (
"net/http"
"reflect"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v8"
)
type Booking struct {
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
func bookableDate(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
if date, ok := field.Interface().(time.Time); ok {
today := time.Now()
if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
return false
}
}
return true
}
func main() {
route := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
route.GET("/bookable", getBookable)
route.Run(":8085")
}
func getBookable(c *gin.Context) {
var b Booking
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}
$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
{"message":"Booking dates are valid!"}
$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
可以看到在main方法中,gin的binding方法中注册了关于bookabledate的tag的验证,函数为bookableDate