Gin框架系列

传过来的用户名是不是不能太骚气?比如一堆空格和符号之类的;密码是不是不能太长也不能太短?手机号是不是要符合规则?性别是不是不能填人妖?

file

另外,登录的时候我们也需要验证账号密码是不是正确的,那么为了方便上手,咱就先来个简单示例,做登录验证。

激情演示

做登录之前得先想清楚需要对用户名密码做什么样的限制,比如他们都不能为空、用户名只能是字母或数字、密码长度只能在6位到12位之间等,如果各位看官没有异议,接下来我就拿上述的这几个条件来演示了。

file

定义结构体与接口

首先得有个地方存接收到的用户名、密码参数,那就定一个名叫Login的结构体吧。

type Login struct {

 User string

 Password string

}

然后再定义一个登录接口,这块不知道啥意思的同学可以去看我的第二篇教程。

router.POST("/login", func(c *gin.Context) {

})

接口是定了,怎么才能把接收的参数放到Login结构体里去呢?

file

绑定参数

我们去翻一下gin.Context下面都有些什么好东西可以拿来用。看到了一个叫做Bind的东东,官方说它可以自动根据Content-Type设置的值解析请求过来的参数,然后把参数设置到结构体里。

func (c *Context) Bind(obj interface{}) error {

 b := binding.Default(c.Request.Method, c.ContentType())

 return c.MustBindWith(obj, b)

}

哇塞,这就有意思了,调用一下试试。记得把Login的引用传过去,毕竟人家还要赋值的。

router.POST("/login", func(c *gin.Context) {

  var login Login

  c.Bind(&login)

  c.JSON(200, login)

})

果不其然,跑起来的同学应该可以发现,它失败了,并没有取到我想要的参数值。

curl -d "user=pingye&password=123" http://localhost:8080/login

{"User":"","Password":""}

这不对啊,不是说好做彼此的天使吗?

file

不行,我要一层一层剥开gin框架是怎么处理的。

file

绑定失败,剖析源码

经过长达60分钟的精心研究,我终于发现了终极奥义,先把我绘制的图贴上。

file

事情是这样的,我们调用的Bind方法实际调用了两个方法binding.Default和c.MustBindWith,前者的主要作用是根据终端请求的Content-Type选择处理器,没办法,gin太强,支持的类型太多了。我们刚才的请求方式被理所应当的分配给了formBinding。

var (

 JSON = jsonBinding{}

 XML = xmlBinding{}

 Form = formBinding{}

 Query = queryBinding{}

 FormPost = formPostBinding{}

 FormMultipart = formMultipartBinding{}

 ProtoBuf = protobufBinding{}

 MsgPack = msgpackBinding{}

 YAML = yamlBinding{}

 Uri = uriBinding{}

 Header = headerBinding{}

)

然后呢?在拿到了formBinding之后,就来到了c.MustBindWith方法,它的作用就是调用formBinding的Bind方法,原来这哥们就是个中间商在赚差价。

func (formBinding) Bind(req *http.Request, obj interface{}) error {

 if err := req.ParseForm(); err != nil {

  return err

 }

 if err := req.ParseMultipartForm(defaultMemory); err != nil {

  if err != http.ErrNotMultipart {

   return err

  }

 }

 if err := mapForm(obj, req.Form); err != nil {

  return err

 }

 return validate(obj)

}

Bind方法主要就干了两件事情,第一是解析传过来的表单参数,第二是找到结构体里的tagform进行匹配赋值。看到这里我就明白了,原来只需要在Login后面加上一个tag。

继续绑定参数

那我们加上tag试一下。

type Login struct {

 User string `form:"user"`

 Password string `form:"password"`

}

跑起来果然很完美。

curl -d "user=pingye&password=123" http://localhost:8080/login

{"User":"pingye","Password":"123"}

看到了这里,聪明的你应该涌出了很多想法,刚才说支持那么多类型,前端传的是json咋搞呢?同学们可以自己试一下,现有的这套代码啥都不用改就可以解析json,因为jsonBinding并没有去Login结构体找tag,所以不用在后面加上json:"user"的标识。

curl -H "Content-Type:application/json" -d '{"user":"pingye","password":"123455"}' http://localhost:8080/login

{"User":"pingye","Password":"123455"}

至于其他的类型,同学们可以自己去动手试验一下,我们必须得到参数验证环节了。

参数验证

OK,这就到了激动人心的参数验证时刻了,再回顾一下刚才的需求,用户名和密码不能为空,用户名只能是英文和数字,密码长度必须得在6到12位。

gin官方给出的示例是直接在tag中加校验规则,比如不能为空,就加上binding:"required"。

type Login struct {

 User string `form:"user" binding:"required"`

 Password string `form:"password" binding:"required"`

}

在验证的地方也做一下处理,Bind会自动帮我们进行校验,如果校验失败会返回一个error,我们把它输出即可。

router.POST("/login", func(c *gin.Context) {

  var login Login

  err := c.Bind(&login)

  if err != nil {

    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

    return

  }

  c.JSON(200, login)

})

放心,参数验证的演示不会就这么结束的。

file

鉴于官方文档给的信息太少了,我们还是通过源码去找更多线索吧,通过源代码可以看到,gin的参数验证实际上并不是自己实现的,而是使用了一个叫做go-playground/validator的库。

.

├── LICENSE

├── Makefile

├── README.md

├── _examples

├── baked_in.go

├── benchmarks_test.go

├── cache.go

├── doc.go

├── errors.go

├── field_level.go

├── go.mod

├── go.sum

├── logo.png

├── non-standard

├── regexes.go

├── struct_level.go

├── testdata

├── translations

├── translations.go

├── util.go

├── validator.go

├── validator_instance.go

└── validator_test.go

4 directories, 19 files

里面有一个叫做doc.go的文件,有非常多的示例与解释,简直找到宝藏了,去他的官方文档。

file

我在里面找到了长度限制的demo,很简单,min和max两个标签就搞定了。跑一下完全没有问题。

type Login struct {

 User string `form:"user" binding:"required"`

 Password string `form:"password" binding:"required,min=6,max=12"`

}

curl -d "user=pingye&password=12345" http://localhost:8080/login 

{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'min' tag"}

curl -d "user=pingye&password=1234567890123" http://localhost:8080/login

{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'max' tag"}

实现了两个校验了,还剩下用户名的字母和数字限制,让我震惊的是,我以为随便说的这个限制要用多种组合来实现,竟然轻松就找到了一个对应的,简直太棒了(强烈推荐这个库,看来后面有必要出一期这个库的介绍),很简单,加上一个名叫alphanum的规则就可以实现了。

type Login struct {

 User string `form:"user" binding:"required,alphanum"

猜你喜欢

转载自www.cnblogs.com/ihdci/p/12674685.html