Gin框架 -- 请求
0. RestfulAPI
即Representational State Transfer的缩写。直接翻译的意思是"表现层状态转化",是一种互联网应用程序的API设计理念:URL定位资源,用HTTP描述操作
1.获取文章 /blog/getXxx Get blog/Xxx
2.添加 /blog/addXxx POST blog/Xxx
3.修改 /blog/updateXxx PUT blog/Xxx
4.删除 /blog/delXxxx DELETE blog/Xxx
1. URL参数
0. 示例请求
http://127.0.0.1:8888/user/info/1/张三
1. Params.ByName()
1. 取值方法
context.Params.ByName()
2. 示例
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/user/:id/*name", func(context *gin.Context) {
//:name 只会接收值,*name会接收完URL路径:/xxx
userId := context.Params.ByName("id")
// userName的值为 /张三
userName := context.Params.ByName("name")
// strings.Trim会将"/" 从字符串中切掉,得到的仍旧是string
// 类似python 中的split函数
action := strings.Trim(userName, "/")
context.String(http.StatusOK, name +" is "+action)
})
r.Run(":8002")
}
3. 测试
2. Param()
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/user/:id/*name", func(context *gin.Context) {
//:name 只会接收值,*name会接收完URL路径:/xxx
userId := context.Param("id")
// userName的值为 /张三
userName := context.Param("name")
// strings.Trim会将"/" 从字符串中切掉,得到的仍旧是string
// 类似python 中的split函数
action := strings.Trim(userName, "/")
context.String(http.StatusOK, name +" is "+action)
})
r.Run(":8002")
}
2. 关键字参数
0. 示例请求
http://127.0.0.1:8002/user?id=1&name=xiaoming
1. DefaultQuery()
1. 取值方法
context.DefaultQuery()
2. 示例
name := c.DefaultQuery("name","如果没取到则使用本默认值")
fmt.Println(name)
2. Query()
1. 取值方法
context.Query()
2. 示例
name := c.Query("name")
fmt.Println(name)
//如果没有传name参数, 则为空字符串,因为name是字符串类型,当没有获取到任何值时,name为定义时的空字符串
3. 表单参数
表单传输为post请求,http常见的传输格式为四种:
- application/json
- application/x-www-form-urlencoded
- application/xml
- multipart/form-data
表单参数可以通过PostForm()方法获取,该方法默认解析的是x-www-form-urlencoded或from-data格式的参数
1. PostForm()
1. 取值方法
context.PostForm()
2. 示例
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/login", func(context *gin.Context) {
// 获取值,如果没有获取到则为空字符串
username := context.PostForm("username")
password := context.PostForm("password")
fmt.Println(username,password)
context.String(http.StatusOK, "登录成功")
})
r.Run(":8002")
}
2. GetPostForm()
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/login", func(context *gin.Context) {
// 返回值为值,和bool
username,isgeted := context.GetPostForm("username")
if !isgeted{
context.String(http.StatusBadRequest, "用户名错误")
}
password,isgeted := context.GetPostForm("password")
if !isgeted{
context.String(http.StatusBadRequest, "密码错误")
}
fmt.Println(username,password)
context.String(http.StatusOK, "登录成功")
})
r.Run(":8002")
}
3. DefaultPostForm()
4. 数据绑定
1. 概述
参数绑定模型可以将请求体自动绑定到结构体中,目前支持绑定的请求类型有 JSON 、XML 、YAML 和标准表单 form数据 foo=bar&boo=baz 等。换句话说,只要定义好结构体,就可以将请求中包含的数据自动接收过来,这是 Gin 框架非常神奇的功能。
在定义绑定对应的结构体时,需要给结构体字段设置绑定类型的标签,比如绑定 JSON 数据时,设置字段标签为 json:"fieldname" 。使用绑定可以更快捷地把数据传递给程序中的数据对象。
使用 Gin框架中系列绑定有关方法时,Gin 会根据请求头中 Content-Type 推断如何绑定,也就是自动绑定。但如果明确绑定的类型,开发人员也可以使用 MustBindWith() 方法或 BindJSON() 等方法而不用自动推断。可以指定结构体某字段是必需的,字段需要设置标签 binding:"required" ,但如果绑定时是空值,Gin 会报错。
在 Gin 框架的 binding 包中,定义了 Content-Type 请求头信息的多种 MIME 类型,以便在自动绑定时进行类型判别进而采用对应的处理方法
const (
MIMEJSON = "application/json"
MIMEHTML = "text/html"
MIMEXML = "application/xml"
MIMEXML2 = "text/xml"
MIMEPlain = "text/plain"
MIMEPOSTForm = "application/x-www-form-urlencoded"
MIMEMultipartPOSTForm = "multipart/form-data"
MIMEPROTOBUF = "application/x-protobuf"
MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack"
MIMEYAML = "application/x-yaml"
)
所有绑定的方法中,首先 c.Bind() 方法会根据 Content-Type 推断得到一个 bindding 实例对象。因为它会调用函数 func Default(method, contentType string) Binding ,这个函数根据 HTTP 请求的方法和 Content-Type 来实例化具体的 bindding 对象。一共可以实例化为下面几种类型:
var (
JSON = jsonBinding{}
XML = xmlBinding{}
Form = formBinding{}
Query = queryBinding{}
FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{}
Header = headerBinding{}
)
在 binding 包也就是 binding 目录中,可以看到每种实例结构都单独在一个文件定义了系列处理方法。 c.Bind() 方法得到 binding 实例对象后,会调用 c.MustBindWith(obj, b) 方法, b 为实例化的某类 binding 对象,而像 c.BindJSON() 方法由于知道实例化对象是 JSON ,所以也调用 c.MustBindWith(obj, b) ,这里的 b 是 jsonBinding{} 对象。其他像 XML 等的处理过程类似。
而 c.MustBindWith() 方法会统一调用 c.ShouldBindWith() 方法,在 c.ShouldBindWith() 方法中会调用具体实例的处理方法: b.Bind(c.Request, obj) ,这个 b.Bind()方法很关键,每种 binding 实例对象都有实现这个方法,它实现了参数的绑定功能。
在参数绑定过程中,大致可以认为是这个过程:
Bind->MustBindWith->ShouldBindWith->b.Bind
在参数绑定中,无论是采用 c.Bind() 系列方法、或者是 c.ShouldBindWith() 系列方法,最终都是通过具体实例的 b.Bind() 方法来实现参数绑定到结构体指针。而这个实例可以在 binding 目录中找到其方法的实现文件。如: json.go 、 uri.go 以及 form.go 等等文件,文件名都对应着不同的 Content-Type 。
在 Gin 框架中下列方法可以用处理绑定:
// Bind 检查 Content-Type 来自动选择绑定引擎
// 依靠 "Content-Type" 头来使用不同的绑定
// "application/json" 绑定 JSON
// "application/xml" 绑定 XML
// 否则返回错误信息
// 如果 Content-Type ==“application / json”,JSON 或 XML 作为 JSON 输入,
// Bind 会将请求的主体解析为 JSON。
// 它将 JSON 有效负载解码为指定为指针的结构。
// 如果输入无效,它会写入 400 错误并在响应中设置 Content-Type 标题 “text / plain” 。
func (c *Context) Bind(obj interface{}) error
// BindJSON 是 c.MustBindWith(obj, binding.JSON) 的简写
func (c *Context) BindJSON(obj interface{}) error
// BindXML 是 c.MustBindWith(obj, binding.BindXML) 的简写
func (c *Context) BindXML(obj interface{}) error
// BindQuery 是 c.MustBindWith(obj, binding.Query) 的简写
func (c *Context) BindQuery(obj interface{}) error
// BindYAML 是 c.MustBindWith(obj, binding.YAML) 的简写
func (c *Context) BindYAML(obj interface{}) error
// BindHeader 是 c.MustBindWith(obj, binding.Header) 的简写
func (c *Context) BindHeader(obj interface{}) error
// BindUri 使用 binding.Uri 绑定传递的结构体指针。
// 如果发生任何错误,它将使用 HTTP 400 中止请求。
func (c *Context) BindUri(obj interface{}) error
// MustBindWith 使用指定的绑定引擎绑定传递的 struct 指针。
// 如果发生任何错误,它将使用 HTTP 400 中止请求。
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error
// ShouldBind 检查 Content-Type 来自动选择绑定引擎
// 依靠 "Content-Type" 头来使用不同的绑定
// "application/json" 绑定 JSON
// "application/xml" 绑定 XML
// 否则返回错误信息
// 如果 Content-Type ==“application/json” ,JSON 或 XML 作为 JSON 输入,
// Bind 会将请求的主体解析为JSON。
// 它将 JSON 有效负载解码为指定为指针的结构。
// 类似 c.Bind() ,但这个方法在 JSON 无效时不支持写 400 到响应里。
func (c *Context) ShouldBind(obj interface{}) error
// ShouldBindJSON 是c.ShouldBindWith(obj, binding.JSON)的简写
func (c *Context) ShouldBindJSON(obj interface{}) error
// ShouldBindXML 是c.ShouldBindWith(obj, binding.XML)的简写
func (c *Context) ShouldBindXML(obj interface{}) error
// ShouldBindQuery 是c.ShouldBindWith(obj, binding.Query)的简写
func (c *Context) ShouldBindQuery(obj interface{}) error
// ShouldBindYAML 是c.ShouldBindWith(obj, binding.YAML)的简写
func (c *Context) ShouldBindYAML(obj interface{}) error
// ShouldBindHeader 是c.ShouldBindWith(obj, binding.Header)的简写
func (c *Context) ShouldBindHeader(obj interface{}) error
// ShouldBindUri使用指定的绑定引擎绑定传递的struct指针。
func (c *Context) ShouldBindUri(obj interface{}) error
// ShouldBindWith使用自定的绑定引擎绑定传递的struct指针。
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error
// ShouldBindBodyWith与ShouldBindWith类似,但它存储请求
// ShouldBindBodyWith可进入上下文,并在再次调用时重用。
//
// 注意:此方法在绑定之前读取正文。 所以推荐使用
// 如果只需要调用一次,那么ShouldBindWith可以获得更好的性能。
func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error)
2. Json参数
0. 结构体
package data
type CookieStr struct {
Cookiestr string `json:"cookiestr"`
}
type Login struct {
UserName string `json:"user_name" binding:"required"`
PassWord string `json:"pass_word" binding:"required"`
}
1. ShouldBind()
ShouldBind()会根据content-type推断使用哪个数据绑定函数,如果是json数据必须content-type="application/json"
package auth
import (
"ginproject/data"
"github.com/gin-gonic/gin"
"net/http"
)
func loginHandler(ctx *gin.Context) {
var loginJson data.Login
// 你可以使用显式绑定声明绑定 multipart form:
// c.ShouldBindWith(&loginJson, binding.Form)
// 或者简单地使用 ShouldBind 方法自动绑定:
// 在这种情况下,将自动选择合适的绑定
// 如果是 `GET` 请求,只使用 `Form` 绑定引擎(`query`)。
// 如果是 `POST` 请求,首先检查 `content-type` 是否为 `JSON` 或 `XML`,
// 然后再使用 `Form`(`form-data`)。
if err := ctx.ShouldBind(&loginJson); err != nil {
ctx.JSON(http.StatusOK, gin.H{
"err_message":"数据错误",
})
return
}
// 判断用户名密码是否正确
if loginJson.UserName != "xiaoming" && loginJson.PassWord != "123" {
ctx.JSON(http.StatusOK, gin.H{
"err_message":"账号或密码错误",
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"sucess_message":"Json 登录成功",
"data":&loginJson,
})
return
}
请求与响应
注意必须与结构体定义的tag名,一致

2. ShouldBindJson()
// 绑定 JSON
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.Default()
// 绑定 JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// 绑定 HTML 表单 (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// 根据 Content-Type Header 推断使用哪个绑定器。
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// 监听并启动服务
router.Run(":8080")
}
3. 表单参数(请求体)
0. 结构体
type Login struct {
UserName string `form:"userName" binding:"required"`
PassWord string `form:"passWord" binding:"required"`
}
1. Bind()
结构体构建
package data
type CookieStr struct {
Cookiestr string `json:"cookiestr"`
}
type Login struct {
UserName string `form:"userName" binding:"required"`
PassWord string `form:"passWord" binding:"required"`
}
后端数据解析
package auth
import (
"ginproject/data"
"github.com/gin-gonic/gin"
"net/http"
)
func loginHandler(ctx *gin.Context) {
var loginForm data.Login
//var cookieJson data.CookieStr
if err := ctx.Bind(&loginForm); err != nil {
ctx.JSON(http.StatusOK, gin.H{
"err_message":"数据错误",
})
return
}
// 判断用户名密码是否正确
if loginForm.UserName != "xiaoming" && loginForm.PassWord != "123" {
ctx.JSON(http.StatusOK, gin.H{
"err_message":"账号或密码错误",
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"sucess_message":"Form 登录成功",
"data":&loginForm,
})
return
}
请求与响应
注意必须与结构体上的tag名,一致

2. ShouldBind()
// 同上逻辑一样
// 可显式绑定表单
// c.ShouldBindWith(&form, binding.Form)
// 或者简单地使用 ShouldBind 方法自动绑定
if c.ShouldBind(&form) == nil {}
4. URL关键字参数
0. 结构体
type UrlParams struct {
Id string `form:"id"`
Page string `form:"page"`
}
1. ShouldBind()
func UrlParamsHandler(ctx *gin.Context) {
var urlParams data.UrlParams
//
err := ctx.ShouldBind(&urlParams)
// 只绑定?后面的查询字符串
// if c.ShouldBindQuery(&person) == nil {}
if err != nil{
fmt.Println(err.Error())
}
fmt.Println(urlParams)
}
5. URL位置参数
0. 结构体
package data
type CookieStr struct {
Cookiestr string `json:"cookiestr"`
}
type Login struct {
UserName string ` uri:"uname" binding:"required"`
PassWord string ` uri:"pwd" binding:"required"`
}
1. ShouldBindUri()
routers.go
e.GET("/home/:uname/:pwd",HomeHandler)
handler.go
package auth
import (
"ginproject/data"
"github.com/gin-gonic/gin"
"net/http"
)
func HomeHandler(ctx *gin.Context) {
var loginUrl data.Login
//var cookieJson data.CookieStr
if err := ctx.ShouldBindUri(&loginUrl); err != nil {
ctx.JSON(http.StatusOK, gin.H{
"err_message":"数据错误",
})
return
}
// 判断用户名密码是否正确
if loginUrl.UserName != "xiaoming" && loginUrl.PassWord != "123" {
ctx.JSON(http.StatusOK, gin.H{
"err_message":"账号或密码错误",
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"sucess_message":"Url 登录成功",
"data":&loginUrl,
})
return
}
请求与响应,注意必须与结构体上的tag名,一致
6. HTML复选框参数
0. HTML
Gin 框架很方便地通过绑定得到 HTML FORM 元素的值,需要在结构体中指定字段标签form:filedname 。
index.html 文件放在程序目录下 public 目录中。
<form action="/check" 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>
1. 结构体
type CheckForm struct {
Colors []string `form:"colors[]"` // 与html中的name值相同
}
注意,上面程序中结构体标签: colors[] 与复选框的名字一致,这里表示数组所以可以得到多个已选项的值。
运行程序,通过浏览器访问 http://localhost:8080/ ,出现复选框表单,选择两个以上选项,这里选择红,绿两种颜色,然后提交表单(请求发送到 http://localhost:8080/check )。
2. ShouldBind()
func main() {
router := gin.Default()
router.Static("/", "./public")
router.POST("/check", func(c *gin.Context) {
var form CheckForm
// 简单地使用 ShouldBind 方法自动绑定
if c.ShouldBind(&form) == nil {
c.JSON(200, gin.H{"color": form.Colors})
}
})
router.Run(":8080")
}
页面显示,符合提交的选项:
{"color":["red","green"]}
7. 绑定表单数据至嵌入结构体
上面已经知道通过绑定可以自动取得数据到简单结构体对象,对有嵌入的结构体也可以通过绑定自动得到数据,不过嵌入的结构体后面不要指定标签。
type StructA struct {
FieldA string `form:"field_a"`
}
type StructB struct {
NestedStruct StructA // 不要指定标签
FieldB string `form:"field_b"`
}
type StructC struct {
NestedStructPointer *StructA
FieldC string `form:"field_c"`
}
type StructD struct {
NestedAnonyStruct struct {
FieldX string `form:"field_x"`
}
FieldD string `form:"field_d"`
}
func GetDataB(c *gin.Context) {
var b StructB
c.Bind(&b)
c.JSON(200, gin.H{
"a": b.NestedStruct,
"b": b.FieldB,
})
}
func GetDataC(c *gin.Context) {
var b StructC
c.Bind(&b)
c.JSON(200, gin.H{
"a": b.NestedStructPointer,
"c": b.FieldC,
})
}
func GetDataD(c *gin.Context) {
var b StructD
c.Bind(&b)
c.JSON(200, gin.H{
"x": b.NestedAnonyStruct,
"d": b.FieldD,
})
}
func main() {
router := gin.Default()
router.GET("/getb", GetDataB)
router.GET("/getc", GetDataC)
router.GET("/getd", GetDataD)
router.Run()
}
输入输出结果:
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
Go{"a":{"FieldA":"hello"},"b":"world"}
curl "http://localhost:8080/getc?field_a=hello&field_c=world"
Go{"a":{"FieldA":"hello"},"c":"world"}
curl "http://localhost:8080/getd?field_x=hello&field_d=world"
Go{"d":"world","x":{"FieldX":"hello"}}
8. 将请求体绑定到不同的结构体中
一般通过调用 ShouldBind() 方法绑定数据,但注意某些情况不能多次调用这个方法。
type formA struct {
Foo string `json:"foo" xml:"foo" binding:"required"`
}
type formB struct {
Bar string `json:"bar" xml:"bar" binding:"required"`
}
func BindHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// c.ShouldBind 使用了 c.Request.Body ,不可重用。
if errA := c.ShouldBind(&objA); errA != nil {
fmt.Println(errA)
c.String(http.StatusOK, `the body should be formA`)
// 因为现在 c.Request.Body 是 EOF,所以这里会报错。
} else if errB := c.ShouldBind(&objB); errB != nil {
fmt.Println(errB)
c.String(http.StatusOK, `the body should be formB`)
} else {
c.String(http.StatusOK, `Success`)
}
}
func main() {
route := gin.Default()
route.Any("/bind", BindHandler)
route.Run(":8080")
}
运行程序,通过浏览器访问 http://localhost:8080/bind?foo=foo&bar=bar ,页面显示:
the body should be formA
程序运行在 Debug 模式时,在命令行运行下面命令:
curl -H "Content-Type:application/json" -v -X POST -d '{"foo":"foo","bar":"bar"}' http://localhost:8080/bind
命令返回:
the body should be formB
表明在第二次运行 ShouldBind() 方法时出错,要想多次绑定,可以使用 c.ShouldBindBodyWith() 方法。
func BindHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// ShouldBindBodyWith() 读取 c.Request.Body 并将结果存入上下文。
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA != nil {
fmt.Println(errA)
c.String(http.StatusOK, `the body should be formA`)
// 这时, 复用存储在上下文中的 body 。
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB != nil {
fmt.Println(errB)
c.String(http.StatusOK, `the body should be formB JSON`)
// 可以接受其他格式
} else {
c.String(http.StatusOK, `Success`)
}
}
c.ShouldBindBodyWith() 会在绑定之前将请求体存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。
只有某些格式需要此功能,如 JSON ,XML ,MsgPack ,ProtoBuf 。对于其他格式,如 Query ,Form ,FormPost ,FormMultipart 可以多次调用 c.ShouldBind() 而不会造成任任何性能损失,这也是前面结构体中的标签没有定义 form ,只有定义 json:"foo" xml:"foo" binding:"required" 的原因。
9. 只绑定 URL Query 参数
houldBind() 方法支持 URL Query 参数绑定,也支持 POST 参数绑定。而 ShouldBindQuery() 方法只绑定 URL Query 参数而忽略 POST 数据。
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
}
func startPage(c *gin.Context) {
var person Person
if c.ShouldBindQuery(&person) == nil {
fmt.Println(person.Name)
fmt.Println(person.Address)
c.String(200, "Success")
} else {
c.String(400, "Error")
}
}
func main() {
route := gin.Default()
route.Any("/bindquery", startPage)
route.Run(":8080")
}
运行程序,通过浏览器访问 http://localhost:8080/ ,页面显示 “Sucess” 。输出结果:
[GIN-debug] GET /bindquery --> main.startPage (3 handlers)
[GIN-debug] POST /bindquery --> main.startPage (3 handlers)
[GIN-debug] PUT /bindquery --> main.startPage (3 handlers)
[GIN-debug] PATCH /bindquery --> main.startPage (3 handlers)
[GIN-debug] HEAD /bindquery --> main.startPage (3 handlers)
[GIN-debug] OPTIONS /bindquery --> main.startPage (3 handlers)
[GIN-debug] DELETE /bindquery --> main.startPage (3 handlers)
[GIN-debug] CONNECT /bindquery --> main.startPage (3 handlers)
[GIN-debug] TRACE /bindquery --> main.startPage (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
titan
cs
[GIN] 2019/07/13 - 17:06:23 | 200 | 0s | ::1 | GET /bindquery?name=titan&address=cs
输出表明 URL Query 参数通过 GET 方法能被程序正常绑定,注意上面程序中使用了 Any() 方法,它能匹配众多的 HTTP 方法。
如果程序继续运行在 Debug 模式时,在命令行运行下面命令:
curl -v -X POST -d "name=titan&address=cs" http://localhost:8080/bindquery
* Connected to localhost (::1) port 8080 (#0)
> POST /bindquery HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Content-Length: 21
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 21 out of 21 bytes
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=utf-8
< Date: Sat, 13 Jul 2019 17:12:37
< Content-Length: 7
<
Success
命令行的返回表明通过 POST 方法已经成功提交请求,服务端成功返回,状态代码: 200 ,返回内容: Success 。
控制台输出结果:
[GIN] 2019/07/13 - 17:12:37 | 200 | 0s | ::1 | POST /bindquery
从控制台输出可以看到,通过 POST 提交的数据没有正常绑定。但是前面通过 ShouldBind() 方法可以正常绑定。这表明 ShouldBindQuery() 只绑定 URL Query 参数而忽略 POST 数据。
10. Header 参数
Header 也可以传递参数,程序通过绑定的方式得到参数值,在结构体的字段标签上需要指定为 header 。
type testHeader struct {
Rate int `header:"Rate"`
Domain string `header:"Domain"`
}
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
h := testHeader{}
if err := c.ShouldBindHeader(&h); err != nil {
c.JSON(200, err)
}
fmt.Printf("%#v\\n", h)
c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain})
})
router.Run(":8080")
}
运行命令
curl -H "rate:300" -H "domain:music" http://localhost:8080/
{"Domain":"music","Rate":300}
通过 curl 命令带上自定义的头部信息给 Handler 处理程序, ShouldBindHeader() 方法自动绑定头部变量到结构体。
11. 请求体参数绑定到不同的结构体
一般通过调用 c.Request.Body 方法绑定数据,但不能多次调用这个方法。
type formA struct {
Foo string `json:"foo" xml:"foo" binding:"required"`
}
type formB struct {
Bar string `json:"bar" xml:"bar" binding:"required"`
}
func SomeHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// c.ShouldBind 使用了 c.Request.Body,不可重用。
if errA := c.ShouldBind(&objA); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// 因为现在 c.Request.Body 是 EOF,所以这里会报错。
} else if errB := c.ShouldBind(&objB); errB == nil {
c.String(http.StatusOK, `the body should be formB`)
} else {
...
}
}
要想多次绑定,可以使用 c.ShouldBindBodyWith.
func SomeHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// 读取 c.Request.Body 并将结果存入上下文。
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// 这时, 复用存储在上下文中的 body。
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
c.String(http.StatusOK, `the body should be formB JSON`)
// 可以接受其他格式
} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
c.String(http.StatusOK, `the body should be formB XML`)
} else {
...
}
}
注意事项
c.ShouldBindBodyWith 会在绑定之前将 body 存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。
只有某些格式需要此功能,如 JSON, XML, MsgPack, ProtoBuf。 对于其他格式, 如 Query, Form, FormPost, FormMultipart 可以多次调用 c.ShouldBind() 而不会造成任任何性能损失 (详见 https://github.com/gin-gonic/gin/pull/1341)。
12. 多种形式的参数
一个post请求,既有url参数,又有请求体参数,如何绑定到结构体
// http://127.0.0.1:8009/url_params2/1/5/
路由
e.POST("/url_params2/:id/:page",UrlParams2Handler)
创建结构体
type UrlParams struct {
Id string `uri:"id" binding:"required"`
Page string `uri:"page" binding:"required"`
Name string `form:"name" binding:"required"`
Message string `form:"message" binding:"required"`
}
取值
func UrlParams2Handler(ctx *gin.Context) {
var urlParamsQuery data.UrlParams
var urlParamsParams data.UrlParams
err := ctx.ShouldBindUri(&urlParamsQuery)
err = ctx.Bind(&urlParamsParams)
if err != nil{
fmt.Println(err.Error())
}
fmt.Println(urlParamsQuery,urlParamsParams)
}
5. 校验器
1. 内置校验器
1. 结构体tag校验
用gin框架的数据验证,可以不用解析数据,减少if else,会简洁许多。
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
//Person ..
type Person struct {
//不能为空并且大于10
Age int `form:"age" binding:"required,gt=10"`
Name string `form:"name" binding:"required"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}
func main() {
r := gin.Default()
r.GET("/5lmh", func(c *gin.Context) {
var person Person
if err := c.ShouldBind(&person); err != nil {
c.String(500, fmt.Sprint(err))
return
}
c.String(200, fmt.Sprintf("%#v", person))
})
r.Run()
}
2. 常用校验参数
多个条件以 , 分割
required // 必填
email // 应用email来限度字段必须是邮件模式,间接写eamil即可,无需加任何指定。
omitempty // 字段未设置,则疏忽
- // 跳过该字段,不测验;
| // 应用多个束缚,只须要满足其中一个,例如rgb|rgba;
unique // 指定唯一性束缚,不同类型解决不同:
// 1. 对于map,unique束缚没有反复的值
// 2. 对于数组和切片,unique没有反复的值
// 3. 对于元素类型为构造体的碎片,unique束缚构造体对象的某个字段不反复,应用unique=field指定字段名
ne= // 不等于参数值,例如ne=5;
gt= // 大于
gte= // 大于等于
lt= // 小于
lte= // 小于等于
eq= // 等于参数值,留神与len不同。对于字符串,eq束缚字符串自身的值,而len束缚字符串长度。例如eq=10;
len= // 等于参数值,例如len=10;
max= // 小于等于参数值,例如max=10;
min= // 大于等于参数值,例如min=10
oneof= // 只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号突围,例如 oneof=male female。
excludesall= // 不蕴含参数中任意的 UNICODE 字符,例如excludesall=ab,不能包含ab
excludesrune= // 不蕴含参数示意的 rune 字符,excludesrune=asong;
startswith= // 以参数子串为前缀,例如startswith=hi;
endswith= // 以参数子串为后缀,例如endswith=bye。
contains= // 蕴含参数子串,例如contains=email;
containsany= // 蕴含参数中任意的 UNICODE 字符,例如containsany=ab;
containsrune= // 蕴含参数示意的 rune 字符,例如`containsrune=asong;
excludes= // 不蕴含参数子串,例如excludes=email;
eqfield= // 定义字段间的相等束缚,用于束缚同一构造体中的字段。例如:eqfield=Password
eqcsfield= // 束缚对立构造体中字段等于另一个字段(绝对),确认明码时能够应用,例如:eqfiel=ConfirmPassword
nefield= // 用来束缚两个字段是否雷同,确认两种色彩是否统一时能够应用,例如:nefield=Color1
necsfield= // 束缚两个字段是否雷同(绝对)
msg:"" // 当字段检验未通过时, 返回前端的错误信息
2. 第三方校验器校验
1. 包下载
github.com/go-playground/validator/v10
2. 语法格式
type Test struct {
Field `validate:"min=10,max=0"`
}
3. 常用校验参数
required // 必填
email // 应用email来限度字段必须是邮件模式,间接写eamil即可,无需加任何指定。
omitempty // 字段未设置,则疏忽
- // 跳过该字段,不测验;
| // 应用多个束缚,只须要满足其中一个,例如rgb|rgba;
unique // 指定唯一性束缚,不同类型解决不同:
// 1. 对于map,unique束缚没有反复的值
// 2. 对于数组和切片,unique没有反复的值
// 3. 对于元素类型为构造体的碎片,unique束缚构造体对象的某个字段不反复,应用unique=field指定字段名
ne="" // 不等于参数值,例如ne=5;
gt="" // 大于
gte="" // 大于等于
lt="" // 小于
lte="" // 小于等于
eq="" // 等于参数值,留神与len不同。对于字符串,eq束缚字符串自身的值,而len束缚字符串长度。例如eq=10;
len="" // 等于参数值,例如len=10;
max="" // 小于等于参数值,例如max=10;
min="" // 大于等于参数值,例如min=10
oneof= "" // 请求参数必须只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号突围,例如 oneof=male female。
excludesall="" // 不蕴含参数中任意的 UNICODE 字符,例如excludesall=ab,不能包含ab
excludesrune="" // 不蕴含参数示意的 rune 字符,excludesrune=asong;
startswith="" // 以参数子串为前缀,例如startswith=hi;
endswith="" // 以参数子串为后缀,例如endswith=bye。
contains="" // 蕴含参数子串,例如contains=email;
containsany="" // 蕴含参数中任意的 UNICODE 字符,例如containsany=ab;
containsrune="" // 蕴含参数示意的 rune 字符,例如`containsrune=asong;
excludes="" // 不蕴含参数子串,例如excludes=email;
eqfield="" // 定义字段间的相等束缚,用于束缚同一构造体中的字段。例如:eqfield=Password
eqcsfield="" // 束缚对立构造体中字段等于另一个字段(绝对),确认明码时能够应用,例如:eqfiel=ConfirmPassword
nefield="" // 用来束缚两个字段是否雷同,确认两种色彩是否统一时能够应用,例如:nefield=Color1
necsfield="" // 束缚两个字段是否雷同(绝对)
3. 自定义校验器
1. 官网
https://pkg.go.dev/gopkg.in/go-playground/validator.v10
2. 示例
package main
import (
"net/http"
"reflect"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v8"
)
/*
对绑定解析到结构体上的参数,自定义验证功能
比如我们要对 name 字段做校验,要不能为空,并且不等于 admin ,类似这种需求,就无法 binding 现成的方法
需要我们自己验证方法才能实现 官网示例(https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Custom_Functions)
这里需要下载引入下 gopkg.in/go-playground/validator.v8
*/
type Person struct {
Age int `form:"age" binding:"required,gt=10"`
// 2、在参数 binding 上使用自定义的校验方法函数注册时候的名称
Name string `form:"name" binding:"NotNullAndAdmin"`
Address string `form:"address" binding:"required"`
}
// 1、自定义的校验方法
func nameNotNullAndAdmin(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
if value, ok := field.Interface().(string); ok {
// 字段不能为空,并且不等于 admin
return value != "" && !("5lmh" == value)
}
return true
}
func main() {
r := gin.Default()
// 3、将我们自定义的校验方法注册到 validator中
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
v.RegisterValidation("NotNullAndAdmin", nameNotNullAndAdmin)
}
/*
curl -X GET "http://127.0.0.1:8080/testing?name=&age=12&address=beijing"
curl -X GET "http://127.0.0.1:8080/testing?name=lmh&age=12&address=beijing"
curl -X GET "http://127.0.0.1:8080/testing?name=adz&age=12&address=beijing"
*/
r.GET("/5lmh", func(c *gin.Context) {
var person Person
if e := c.ShouldBind(&person); e == nil {
c.String(http.StatusOK, "%v", person)
} else {
c.String(http.StatusOK, "person bind err:%v", e.Error())
}
})
r.Run()
}
3. 示例
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"net/http"
"time"
)
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
date, ok := fl.Field().Interface().(time.Time)
if ok {
today := time.Now()
if today.After(date) {
return false
}
}
return true
}
type Booking struct {
// binding中绑定校验器bookableDate,time_format:"2006-01-02"为goalng的时间格式化规则
CheckIn time.Time `json:"check_in" form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
}
func main() {
route := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册校验器
v.RegisterValidation("bookabledate", bookableDate)
}
route.GET("/bookable", func(context *gin.Context) {
var b Booking
// 将数据进行数据绑定,如果发生错误,则表示没有通过校验
if err := context.ShouldBindWith(&b, binding.Query); err == nil {
context.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
} else {
context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
route.Run(":8999")
}
4. 多语言翻译检验信息响应
1. 官网
https://pkg.go.dev/gopkg.in/go-playground/validator.v10
2. 示例
当业务系统对验证信息有特殊需求时,例如:返回信息需要自定义,手机端返回的信息需要是中文而pc端发挥返回的信息需要时英文,如何做到请求一个接口满足上述三种情况。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
"github.com/go-playground/locales/zh_Hant_TW"
ut "github.com/go-playground/universal-translator"
"gopkg.in/go-playground/validator.v9"
en_translations "gopkg.in/go-playground/validator.v9/translations/en"
zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
)
var (
Uni *ut.UniversalTranslator
Validate *validator.Validate
)
type User struct {
Username string `form:"user_name" validate:"required"`
Tagline string `form:"tag_line" validate:"required,lt=10"`
Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
}
func main() {
en := en.New()
zh := zh.New()
zh_tw := zh_Hant_TW.New()
Uni = ut.New(en, zh, zh_tw)
Validate = validator.New()
route := gin.Default()
route.GET("/5lmh", startPage)
route.POST("/5lmh", startPage)
route.Run(":8080")
}
func startPage(c *gin.Context) {
//这部分应放到中间件中
locale := c.DefaultQuery("locale", "zh")
trans, _ := Uni.GetTranslator(locale)
switch locale {
case "zh":
zh_translations.RegisterDefaultTranslations(Validate, trans)
break
case "en":
en_translations.RegisterDefaultTranslations(Validate, trans)
break
case "zh_tw":
zh_tw_translations.RegisterDefaultTranslations(Validate, trans)
break
default:
zh_translations.RegisterDefaultTranslations(Validate, trans)
break
}
//自定义错误内容
Validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required", fe.Field())
return t
})
//这块应该放到公共验证方法中
user := User{}
c.ShouldBind(&user)
fmt.Println(user)
err := Validate.Struct(user)
if err != nil {
errs := err.(validator.ValidationErrors)
sliceErrs := []string{}
for _, e := range errs {
sliceErrs = append(sliceErrs, e.Translate(trans))
}
c.String(200, fmt.Sprintf("%#v", sliceErrs))
}
c.String(200, fmt.Sprintf("%#v", "user"))
}
正确的连接:http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=33&locale=zh
返回英文的验证信息: http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=3&locale=en
返回中文的验证信息: http://localhost:8080/testing?user_name=枯藤&tag_line=9&tag_line2=3&locale=zh
查看更多的功能可以查看官网
6. 常用方法
1. 原生请求数据
context.GetRawData()

浙公网安备 33010602011771号