关于它

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

  • go validator实现中文翻译

  • validator v10 结合 gin 的使用

版本比较

这里以V9新版本来讲,也是笔者现在在用的版本。V8已经过代,但是还是有很多在用,后面demo简单一展现。

v8与v9差异

tagname

v8默认tag标签为required,v9为validate

required

由于golang的特性,结构体基础数据类型没有赋值会默认零值(int默认0,string默认””等),所以require不能校验出基础类型是默认零值,还是被赋为了零值。比如:
CommType int64json:”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

github

v9

官方的demo

跳转 很详细,推荐看看

没有结合web框架的普通版本

  1. package paramValidator
  2. /**
  3. demo:
  4. var param struct {
  5. Em string `validate:"required,myParam"`
  6. }
  7. err = Validate.Struct(param)
  8. */
  9. import (
  10. "fmt"
  11. "gopkg.in/go-playground/validator.v9"
  12. )
  13. var Validate = validator.New()
  14. func Setup() {
  15. // 我们可以在这里定义自己的验证规则,例:
  16. Validate.RegisterValidation("myParam", myParam)
  17. }
  18. func myParam(fl validator.FieldLevel) bool {
  19. fmt.Println("FieldName:", fl.FieldName())
  20. fmt.Println("StructFieldName", fl.StructFieldName())
  21. fmt.Println("Parm:", fl.Param())
  22. return false
  23. }

与gin结合框架版

  1. package paramValidator
  2. import (
  3. "fmt"
  4. "github.com/gin-gonic/gin/binding"
  5. "gopkg.in/go-playground/validator.v9"
  6. "reflect"
  7. "sync"
  8. )
  9. type defaultValidator struct {
  10. once sync.Once
  11. validate *validator.Validate
  12. }
  13. var _ binding.StructValidator = &defaultValidator{}
  14. func Setup() {
  15. binding.Validator = new(defaultValidator)
  16. }
  17. func (v *defaultValidator) lazyinit() {
  18. v.once.Do(func() {
  19. v.validate = validator.New()
  20. // 绑定tag标签的name
  21. v.validate.SetTagName("binding")
  22. // add any custom validations etc. here
  23. // 我们可以在这里定义自己的验证规则,例:
  24. v.validate.RegisterValidation("myParam", myParam)
  25. })
  26. }
  27. func (v *defaultValidator) ValidateStruct(obj interface{}) error {
  28. if kindOfData(obj) == reflect.Struct {
  29. v.lazyinit()
  30. if err := v.validate.Struct(obj); err != nil {
  31. return error(err)
  32. }
  33. }
  34. return nil
  35. }
  36. func (v *defaultValidator) Engine() interface{} {
  37. v.lazyinit()
  38. return v.validate
  39. }
  40. func kindOfData(data interface{}) reflect.Kind {
  41. value := reflect.ValueOf(data)
  42. valueType := value.Kind()
  43. if valueType == reflect.Ptr {
  44. valueType = value.Elem().Kind()
  45. }
  46. return valueType
  47. }
  48. func myParam(fl validator.FieldLevel) bool {
  49. fmt.Println("FieldName:", fl.FieldName())
  50. fmt.Println("StructFieldName", fl.StructFieldName())
  51. fmt.Println("Parm:", fl.Param())
  52. return false
  53. }
  1. package main
  2. import (
  3. "paramValidator"
  4. "github.com/gin-gonic/gin"
  5. )
  6. func main() {
  7. var r = gin.New()
  8. paramValidator.Setup()
  9. ...
  10. groupApi.POST("test", test)
  11. }
  12. func test(c *gin.Context) {
  13. // 接收参数
  14. var param struct {
  15. Em int64 `binding:"required,myParam"`
  16. TM *string `binding:"required"`
  17. AM string `binding:"required"`
  18. }
  19. err := c.ShouldBind(&param)
  20. fmt.Println(err,param)
  21. }

v8

  1. package main
  2. import (
  3. "net/http"
  4. "reflect"
  5. "time"
  6. "github.com/gin-gonic/gin"
  7. "github.com/gin-gonic/gin/binding"
  8. "gopkg.in/go-playground/validator.v8"
  9. )
  10. type Booking struct {
  11. CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
  12. CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
  13. }
  14. func bookableDate(
  15. v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
  16. field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
  17. ) bool {
  18. if date, ok := field.Interface().(time.Time); ok {
  19. today := time.Now()
  20. if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
  21. return false
  22. }
  23. }
  24. return true
  25. }
  26. func main() {
  27. route := gin.Default()
  28. if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
  29. v.RegisterValidation("bookabledate", bookableDate)
  30. }
  31. route.GET("/bookable", getBookable)
  32. route.Run(":8085")
  33. }
  34. func getBookable(c *gin.Context) {
  35. var b Booking
  36. if err := c.ShouldBindWith(&b, binding.Query); err == nil {
  37. c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
  38. } else {
  39. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  40. }
  41. }
  42. $ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
  43. {"message":"Booking dates are valid!"}
  44. $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
  45. {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}

可以看到在main方法中,gin的binding方法中注册了关于bookabledate的tag的验证,函数为bookableDate