1. 问题出现

使用gin的参数校验,很自然想到参考官方例子https://pkg.go.dev/github.com/gin-gonic/gin#readme-model-binding-and-validation

  1. type Login struct {
  2. User string `form:"user" json:"user" xml:"user" binding:"required"`
  3. Password string `form:"password" json:"password" xml:"password" binding:"required"`
  4. }
  5. func main() {
  6. router := gin.Default()
  7. // Example for binding JSON ({"user": "manu", "password": "123"})
  8. router.POST("/loginJSON", func(c *gin.Context) {
  9. var json Login
  10. if err := c.ShouldBindJSON(&json); err != nil {
  11. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  12. return
  13. }
  14. if json.User != "manu" || json.Password != "123" {
  15. c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  16. return
  17. }
  18. c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  19. })
  20. ...
  21. }

gin参数校验用的是github.com/go-playground/validator库,必须了解一下呀。
官网例子https://github.com/go-playground/validator/blob/master/_examples/simple/main.go

  1. ...
  2. // User contains user information
  3. type User struct {
  4. FirstName string `validate:"required"`
  5. LastName string `validate:"required"`
  6. Age uint8 `validate:"gte=0,lte=130"`
  7. Email string `validate:"required,email"`
  8. FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla'
  9. Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
  10. }
  11. ...

好家伙,一看就冒出疑惑了,gin中使用的binding tag ,validator的官方例子使用的是validate ,那我到底要用谁?什么情况下该用谁?

2. 源码探究

Go方便就方便在看源码实在太方便了,第一想法就是看看gin中是如何校验的。
首先,从c.ShouldBind()进入看看
context.go

  1. ...
  2. // ShouldBind checks the Content-Type to select a binding engine automatically,
  3. // Depending on the "Content-Type" header different bindings are used:
  4. // "application/json" --> JSON binding
  5. // "application/xml" --> XML binding
  6. // otherwise --> returns an error
  7. // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
  8. // It decodes the json payload into the struct specified as a pointer.
  9. // Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.
  10. func (c *Context) ShouldBind(obj interface{}) error {
  11. b := binding.Default(c.Request.Method, c.ContentType())
  12. return c.ShouldBindWith(obj, b)
  13. }
  14. ...

在进入ShouldBindWith

  1. ...
  2. // ShouldBindWith binds the passed struct pointer using the specified binding engine.
  3. // See the binding package.
  4. func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
  5. return b.Bind(c.Request, obj)
  6. }
  7. ...

进入Bind,发现是接口里的方法,

  1. ...
  2. // Binding describes the interface which needs to be implemented for binding the
  3. // data present in the request such as JSON request body, query parameters or
  4. // the form POST.
  5. type Binding interface {
  6. Name() string
  7. Bind(*http.Request, interface{}) error
  8. }
  9. ...

然后我们看看该接口有哪些实现
image.png
进入formBinding

  1. ...
  2. func (formBinding) Bind(req *http.Request, obj interface{}) error {
  3. if err := req.ParseForm(); err != nil {
  4. return err
  5. }
  6. if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
  7. return err
  8. }
  9. if err := mapForm(obj, req.Form); err != nil {
  10. return err
  11. }
  12. return validate(obj)
  13. }
  14. ...

进入validate

  1. ...
  2. func validate(obj interface{}) error {
  3. if Validator == nil {
  4. return nil
  5. }
  6. return Validator.ValidateStruct(obj)
  7. }

进入Validator.ValidateStruct,发现也是接口里的方法

  1. ...
  2. type StructValidator interface {
  3. // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
  4. // If the received type is a slice|array, the validation should be performed travel on every element.
  5. // If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
  6. // If the received type is a struct or pointer to a struct, the validation should be performed.
  7. // If the struct is not valid or the validation itself fails, a descriptive error should be returned.
  8. // Otherwise nil must be returned.
  9. ValidateStruct(interface{}) error
  10. // Engine returns the underlying validator engine which powers the
  11. // StructValidator implementation.
  12. Engine() interface{}
  13. }
  14. ...

看看有哪些实现?噢,原来是defaultValidator
image.png
进入defaultValidatorValidateStruct

  1. ...
  2. // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
  3. func (v *defaultValidator) ValidateStruct(obj interface{}) error {
  4. if obj == nil {
  5. return nil
  6. }
  7. value := reflect.ValueOf(obj)
  8. switch value.Kind() {
  9. case reflect.Ptr:
  10. return v.ValidateStruct(value.Elem().Interface())
  11. case reflect.Struct:
  12. return v.validateStruct(obj)
  13. case reflect.Slice, reflect.Array:
  14. count := value.Len()
  15. validateRet := make(SliceValidationError, 0)
  16. for i := 0; i < count; i++ {
  17. if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
  18. validateRet = append(validateRet, err)
  19. }
  20. }
  21. if len(validateRet) == 0 {
  22. return nil
  23. }
  24. return validateRet
  25. default:
  26. return nil
  27. }
  28. }
  29. ...

进入v.validateStruct(obj)

  1. ...
  2. // validateStruct receives struct type
  3. func (v *defaultValidator) validateStruct(obj interface{}) error {
  4. v.lazyinit()
  5. return v.validate.Struct(obj)
  6. }
  7. // Engine returns the underlying validator engine which powers the default
  8. // Validator instance. This is useful if you want to register custom validations
  9. // or struct level validations. See validator GoDoc for more info -
  10. // https://pkg.go.dev/github.com/go-playground/validator/v10
  11. func (v *defaultValidator) Engine() interface{} {
  12. v.lazyinit()
  13. return v.validate
  14. }
  15. func (v *defaultValidator) lazyinit() {
  16. v.once.Do(func() {
  17. v.validate = validator.New()
  18. v.validate.SetTagName("binding")
  19. })
  20. }

找到了找到了,v.validate.SetTagName("binding"),原来是gin封装validator库并设置了标签名;接下来我们看看validator库的默认tag

  1. const (
  2. defaultTagName = "validate"
  3. ...
  4. )
  5. ...
  6. // SetTagName allows for changing of the default tag name of 'validate'
  7. func (v *Validate) SetTagName(name string) {
  8. v.tagName = name
  9. }

3. 总结

binding tag 是gin封装validator库后设置的标签名,如果要使用gin封装的校验就使用binding tag ;
如果直接使用validator库就用validate tag