导读

在第二节,我们学习了Gin框架的路由定义与参数接收,今天应一位同学的要求,来讲解一下参数的绑定与校验。

为什么校验参数?

本不必抛出这个问题的,但顾及到初出茅庐的同学,这里解释一下。

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

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

激情演示

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

定义结构体与接口

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

type Login struct {
User string
Password string
}

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

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

})

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

绑定参数

我们去翻一下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":""}

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

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

绑定失败,剖析源码

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

事情是这样的,我们调用的Bind方法实际调用了两个方法binding.Defaultc.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)
})

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

鉴于官方文档给的信息太少了,我们还是通过源码去找更多线索吧,通过源代码可以看到,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的文件,有非常多的示例与解释,简直找到宝藏了,去他的官方文档。

我在里面找到了长度限制的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"`
Password string `form:"password" binding:"required,min=6,max=12"`
}

今天我的任务结束了,各位是不是需要查看源码,来吧来吧,点击查看源码,顺便STAR一下我哈。


Go语言库示例开源项目「golang-examples」欢迎star~

https://github.com/pingyeaa/golang-examples

感谢大家的观看,如果觉得文章对你有所帮助,欢迎关注公众号「平也」,聚焦Go语言与技术原理。

Gin框架04:趣谈参数绑定与校验的更多相关文章

  1. gin框架中请求参数的绑定与多数据格式处理

    package main import ( "fmt" "github.com/gin-gonic/gin" ) // gin框架提供给开发者表单实体绑定的功能 ...

  2. Gin框架之参数绑定

    为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString.form表单.JSON.XML等参数到结构 ...

  3. gin框架中的参数验证

    结构体验证 用gin框架的数据验证,可以不用解析数据,减少if else,会简洁许多. 处理请求方法 func structValidator(context *gin.Context) { var ...

  4. go的gin框架从请求中获取参数的方法

    前言: go语言的gin框架go里面比较好的一个web框架, github的start数超过了18000.可见此框架的可信度 如何获取请求中的参数 假如有这么一个请求: POST   /post/te ...

  5. SSM框架之SpringMVC(2)参数绑定及自定义类型转换

    SpringMVC(2)参数绑定及自定义类型转换 1.请求参数的绑定 1.1. 请求参数的绑定说明 1.1.1.绑定机制 表单提交的数据都是k=v格式的 username=haha&passw ...

  6. 05 SpringMVC:02.参数绑定及自定义类型转换&&04.SpringMVC返回值类型及响应数据类型&&05.文件上传&&06.异常处理及拦截器

    springMVC共三天 第一天: 01.SpringMVC概述及入门案例 02.参数绑定及自定义类型转换 03.SpringMVC常用注解 第二天: 04.SpringMVC返回值类型及响应数据类型 ...

  7. Gin框架系列02:路由与参数

    回顾 上一节我们用Gin框架快速搭建了一个GET请求的接口,今天来学习路由和参数的获取. 请求动词 熟悉RESTful的同学应该知道,RESTful是网络应用程序的一种设计风格和开发方式,每一个URI ...

  8. SpringMVC框架三:参数绑定

    这篇文章整合了SpringMVC和MyBatis: https://www.cnblogs.com/xuyiqing/p/9419144.html 接下来看看参数绑定: 默认Conrtroller可以 ...

  9. SpringMVC框架笔记02_参数绑定返回值文件上传异常处理器JSON数据交互_拦截器

    目录 第1章 高级参数的绑定 1.1 参数的分类 1.2 数组类型的参数的绑定 1.3 集合类型的参数的绑定 第2章 @RequestMapping的用法 2.1 URL路径映射 2.2 请求方法限定 ...

随机推荐

  1. disruptor 链路实战 三

    一.创建Event类 Trade import java.util.concurrent.atomic.AtomicInteger; public class Trade { private Stri ...

  2. 简单的编写java的helloWord

    那么在上一章章节 http://www.cnblogs.com/langjunnan/p/6814641.html 我们简单的俩了解了一下什么是java和配置编写java的环境,本章呢我们学习如何编写 ...

  3. Design Patterns | 02 什么样的代码是好代码

    目录 01 - 什么是好的代码? 02 - 评价代码的标准有哪些 2.1 可维护性(maintainability) 2.2 可读性(readability) 2.3 可扩展性(extensibili ...

  4. Spring Boot从入门到精通(九)整合Spring Data JPA应用框架

    JPA是什么? JPA全称Java Persistence API,是Sun官方提出的Java持久化规范.是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中. ...

  5. juery 弹出框

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. 建议20:建议通过Function扩展类型

    JavaScript允许为语言的基本数据类型定义方法.通过Object.prototype添加原型方法,该方法可被所有的对象,.这样的方法对函数,数组,字符串,数字,正则表达式和布尔值都适用.例如,通 ...

  7. table 上下左右 4根线的写法 :before :after 他们就能把td里面右下的那颗线给盖上 还有body和header横向滚动的联动 || 不能把body套在header上是为了上header表头固定 || 还有表头header的右侧overflow-y 是否出现滚动条的位置 记得有一个$nextTick 要不然会获取不到高度 高度就为0了 || 横向滚动条纵向滚动条

    table 上下左右 4根线的写法 <!--* @description 重点查核人员表!--><template> <div class="keyChecke ...

  8. C# MP3播放帮助类

    本文为原创文章如需转载请注明出处: /// <summary> /// ************************************************* /// 类名:M ...

  9. JavaScript每日学习日记(1)

    8.11.2019 1. lastIndexOf() 方法从尾到头进行检索. 2. 有三种提取部分字符串的方法: 2.1 slice(start, end)  如果某个参数为负,则从字符串的结尾开始计 ...

  10. python深浅拷贝&垃圾回收&上下文管理(with语句)

    深浅拷贝 在Python中使用copy模块用于对象的拷贝操作. 该模块提供了两个主要的方法:浅拷贝 copy.copy() 深拷贝 copy.deepcopy() 1.浅拷贝(copy) 浅拷贝: 不 ...