一、介绍
- gin框架提供了相关接口,可以用来解析HTTP请求中的各种数据,然后将解析出来的数据绑定到Go语言中的结构体上
- 目前支持JSON、XML、YAML和标准表单值(foo=bar&boo=baz)的绑定
- gin使用https://github.com/go-playground/validator验证参数,查看完整文档https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags
结构体成员标签
- Go语言中的结构体有一个"成员标签(field tag)"语法
- Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来
- 例如,下面的结构体Name成员都一系列标签,其中:
- 绑定到JSON中时,对应JSON中的name字段
- 绑定到DB中时,对应DB中的db_name字段
- 绑定到INI中时,对应INI中的ini_name字段
1 2 3
| type person struct {
Name string 'json: "name" db: "db_name" ini: "ini_name"'
} |
- 本文不是着重讲解该语法的,可以参阅本文在另一篇文章中对结构体标签的讲解https://dongshao.blog.csdn.net/article/details/110677325
- 备注:如果一个字段用binding:"required"修饰,并且在绑定时该字段的值为空,那么将返回一个错误
1 2 3
| type Login struct {
Name string `form: "user_name" xml: "name" binding: "required"`
} |
两套绑定方法
- 第一类是以"ShouldBind"系列开头的方法:这些方法底层使用ShouldBindWith,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误
- 第二类是以"Bind"系列开头的方法:
- 这些方法底层使用MustBindWith
- 如果存在绑定错误,请求将被以下指令中止 c.AbortWithError(400, err).SetType(ErrorTypeBind),响应状态代码会被设置为400,请求头Content-Type被设置为text/plain; charset=utf-8
- 注意,如果你试图在此之后设置响应代码,将会发出一个警告 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422,如果你希望更好地控制行为,请使用ShouldBind相关的方法
- 当我们使用绑定方法时,Gin会根据Content-Type推断出使用哪种绑定器。如果你确定你绑定的是什么,你可以使用MustBindWith或者BindingWith
二、演示:JSON数据解析和绑定
ShouldBindJSON
1 2 3
| func (c *Context) ShouldBindJSON(obj interface{}) error {
return c.ShouldBindWith(obj, binding.JSON)
} |
演示案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package main
import "C"
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 绑定数据用的结构体
type Login struct {
// 每个成员后面都有标签, 表示在解析对应数据格式时绑定到对方的字段名
// 注意, 这里使用到了binding标签
Name string `form: "user_name" json: "name" uri: "name" xml: "name" binding: "required"`
Password string `form: "user_password" json: "password" uri: "password" xml: "password" binding: "required"`
}
func main() {
r := gin.Default()
// 处理POST请求
r.POST("/loginJson", func(c *gin.Context) {
// 创建一个结构体, 用来保存数据
var loginResult Login
// 对端发来的数据是JSON格式的, 那么调用这个函数将对端的JSON数据绑定到结构体上
if err := c.ShouldBindJSON(&loginResult); err != nil {
c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() })
}
// 判断账号密码是否正确, 然后给对端返回结果
if loginResult.Name != "dongshao" && loginResult.Password != "123456" {
c.JSON(http.StatusBadRequest, gin.H{ "status": "304"})
} else {
c.JSON(http.StatusOK, gin.H{ "status": "200"})
}
})
r.Run(":8000")
} |
- 构造HTTP请求,数据格式为"application/json",然后在实体部分发送row类型JSON消息
三、演示:XML数据解析和绑定
演示案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package main
import "C"
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Login struct {
Name string `form:"username" json:"name" uri:"name" xml:"name" binding:"required"`
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main() {
r := gin.Default()
// 处理POST请求
r.POST("/loginXML", func(c *gin.Context) {
var loginResult Login
// 调用Bind()函数, 该函数默认绑定解析Form表单数据
if err := c.ShouldBindXML(&loginResult); err != nil {
c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() })
return
}
// 判断账号密码是否正确, 然后给对端返回结果
if loginResult.Name != "dongshao" || loginResult.Password != "123456" {
c.JSON(http.StatusBadRequest, gin.H{ "status": "304"})
} else {
c.JSON(http.StatusOK, gin.H{ "status": "200"})
}
})
r.Run(":8000")
} |
- 构造HTTP请求:请求头的Context-Type字段的值设置为"application/xml",实体部分输入下面的XML文件内容,响应成功
1 2 3 4 5
| <?xml version="1.0" encoding="UTF-8"?>
<root>
<name>dongshao</name>
<password>123456</password>
</root> |
四、演示:只绑定GET参数
ShouldBindQuery
- 该方法只能用来绑定GET数据,不能绑定其他类型请求的数据
1
| func (c *Context) ShouldBindQuery(obj interface{}) error |
演示案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package main
import "C"
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
}
func main() {
r := gin.Default()
// 处理GET请求
r.GET("/testing", func(c *gin.Context) {
var person Person
// 绑定GET
if err := c.ShouldBindQuery(&person); err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
message := "name: " + person.Name + "address: " + person.Address
c.String(http.StatusOK, message)
})
r.Run(":8000")
} |
五、Bind/ShouldBind:绑定GET或者POST、绑定POST、绑定HTML
- GET或POST参数,HTML表单等参数可以使用这两个方法进行绑定
绑定GET参数
- 在GET()请求中调用该方法,其即可以解析URL中的GET参数,也可以解析实体部分的表单数据;如果GET参数和实体部分的表单数据参数重复,那么优先处理URL中的GET参数(见下面演示)
- 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
func main() {
route := gin.Default()
route.GET("/testing", func(c *gin.Context) {
var person Person
if err := c.ShouldBind(&person); err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
message := "name: " + person.Name + ", address: " + person.Address + ", birthday: " + fmt.Sprint(person.Birthday)
c.String(http.StatusOK, message)
})
route.Run(":8000")
} |
- 构造GET请求:URL中和实体部分都有参数,那么优先处理URL中的GET参数
绑定POST参数
- 在POST()请求中调用该方法,其即可以解析URL中的参数,也可以解析实体部分的表单数据;如果URL中的参数和实体部分的表单数据参数重复,那么优先处理实体部分的表单数据(见下面演示)
- 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package main
import (
"github.com/gin-gonic/gin"
)
type LoginForm struct {
User string `form:"user" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
var form LoginForm
if c.ShouldBind(&form) == nil {
if form.User == "dongshao" && form.Password == "password" {
c.JSON(200, gin.H{"status": "you are logged in"})
} else {
c.JSON(401, gin.H{"status": "unauthorized"})
}
}
})
router.Run(":8000")
} |
- 运行程序,然后构造POST请求,本次只有URL中有参数,响应成功
- 构造POST请求,本次只有实体部分有参数,响应成功
- 构造POST请求,本次URL和实体部分都有数据,优先响应实体部分的
表单数据处理演示1:绑定HTML复选框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <html lang="en">
<head>
<meta charset="UTF-8">
<title>复选框</title>
</head>
<body>
<form action="http://127.0.0.1:8000/test" method="POST">
<p>Check some colors</p>
<label for="red">Red</label>
<input type="checkbox" name="colors[]" value="red" id="red">
<label for="green">Green</label>
<input type="checkbox" name="colors[]" value="green" id="green">
<label for="blue">Blue</label>
<input type="checkbox" name="colors[]" value="blue" id="blue">
<input type="submit">
</form>
</body>
</html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import (
"github.com/gin-gonic/gin"
)
type myForm struct {
Colors []string `form:"colors[]"`
}
func main() {
route := gin.Default()
route.POST("/test", func(c *gin.Context) {
var fakeForm myForm
c.ShouldBind(&fakeForm)
c.JSON(200, gin.H{"color": fakeForm.Colors})
})
route.Run(":8000")
} |
表单数据处理演示2
- 编写下面的HTML文件,内容如下,表单的enctype为multipart/form-data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="http://127.0.0.1:8000/loginForm" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username">
<br>
密  码<input type="password" name="password">
<input type="submit" value="登录">
</from>
</body>
</html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package main
import "C"
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Login struct {
Name string `form:"username" json:"name" uri:"name" xml:"name" binding:"required"`
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main() {
r := gin.Default()
// 处理POST请求
r.POST("/loginForm", func(c *gin.Context) {
var loginResult Login
// 调用Bind()函数, 该函数默认绑定解析Form表单数据
if err := c.Bind(&loginResult); err != nil {
c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() })
return
}
// 判断账号密码是否正确, 然后给对端返回结果
if loginResult.Name != "dongshao" || loginResult.Password != "123456" {
c.JSON(http.StatusBadRequest, gin.H{ "status": "304"})
} else {
c.JSON(http.StatusOK, gin.H{ "status": "200"})
}
})
r.Run(":8000")
} |
六、演示:URI数据解析和绑定
ShouldBindJSON
1
| func (c *Context) ShouldBindUri(obj interface{}) error |
演示案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package main
import "C"
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Login struct {
Name string `form:"username" json:"name" uri:"name" xml:"name" binding:"required"`
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main() {
r := gin.Default()
// 此处为GET请求
// 设置URI规则
r.GET("/:name/:password", func(c *gin.Context) {
var loginResult Login
// 绑定解析Uri数据
if err := c.ShouldBindUri(&loginResult); err != nil {
c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() })
return
}
// 判断账号密码是否正确, 然后给对端返回结果
if loginResult.Name != "dongshao" || loginResult.Password != "admin" {
c.JSON(http.StatusBadRequest, gin.H{ "status": "304"})
} else {
c.JSON(http.StatusOK, gin.H{ "status": "200"})
}
})
r.Run(":8000")
} |
- 运行程序,使用浏览器访问,输入指定的URI,运行成功