Gin介绍
Gin 是一个用于构建基于 Go 语言的 Web 应用程序的轻量级框架。具有良好的性能和生产力,可以用于快速构建 RESTful API、Web 服务以及其他网络应用。
具有以下特性:
快速
基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。
支持中间件
传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。
Crash 处理
Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!
JSON 验证
Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。
路由组
更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。
错误管理
Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。
内置渲染
Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。
可扩展性
新建一个中间件非常简单。
RESTful API规范
可参考-->RESTful API 设计规范 - 掘金 (juejin.cn)
http动词 由之前的基本只使用 GET, POST 到基本使用
- GET(SELECT):从服务器取出资源(一项或多项)
- POST(CREATE):在服务器新建一个资源
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
- DELETE(DELETE):从服务器删除资源
下载并安装Gin
go get -u github.com/gin-gonic/gin
Gin 快速入门
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 相当于http://localhost:8080/hello
r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello world",
})
})
err := r.Run()
// 这里可以指定端口号,如果没有指定默认8080端口
// 例如我们可以r.Run(":8082")来指定为8082端口
if err != nil {
panic(err)
}
}
在这个程序中,我定义了一个 GET 请求的路由。当客户端发送 GET 请求到 "http://localhost:8080/hello" 路径时,Gin 框架将调用您提供的匿名函数(路由处理程序)来处理这个请求。
在这个匿名函数中,使用 c.JSON(200, gin.H{ "message": "hello world" }) 返回了一个 JSON 格式的响应。这意味着当客户端请求 "/hello" 路径时,服务器会返回一个状态码为 200(表示成功)的响应,其中包含一个 JSON 对象,该对象包含一个名为 "message" 的字段,其值为 "hello world"。
状态码的定义请参考 RESTful API规范
结果:
请求参数
URL拼接参数是一种常见的技术,可以有效地改善网络应用的性能和安全。通常的URL地址参数拼接格式是使用 ? 隔开地址与参数,使用&符号来拼接参数,参数名和参数值之间使用=号。如果需要在URL后面拼接参数,可以在URL后面先加上一个?,然后每个参数名前面加上&。
我们会在URL中遇到很多参数,在服务端我们该如何获取到这些参数呢
GET
1. 普通参数
1.1 Query
例如:http://localhost:8080/hello?id=1&name=zhangsan
这里? 后面的id 和name都是参数名,我们现在需要通过GET来获取到这两个参数的值。(获取客户端的参数值)
现在我们利用Gin框架获取到用户输入的参数值,并把这些参数值返回到前端页面
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
id := c.Query("id")
name := c.Query("name")
c.JSON(200, gin.H{
"id": id,
"name": name,
})
})
err := r.Run()
if err != nil {
panic(err)
}
}
GET : http://localhost:8080/hello?id=1&name=zhangsan
结果可以看到页面为:
1.2 DefaultQuery
我们还可以给参数设置默认值,只需要使用DefaultQuery ,然后再参数名后加一个默认值即可
例如,设置默认性别为男性
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
id := c.Query("id")
name := c.Query("name")
sex := c.DefaultQuery("sex", "male") //设置默认值
c.JSON(200, gin.H{
"id": id,
"name": name,
"sex": sex,
})
})
1.3 GetQuery
我们可以使用GetQuery来确定参数是否存在,该方法返回两个value,如果参数存在,则返回参数值和true, 如果参数不存在则返回空值和false
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
id := c.Query("id")
name := c.Query("name")
sex, ok := c.GetQuery("sex") // ok返回参数是否存在
c.JSON(200, gin.H{
"id": id,
"name": name,
"sex": sex,、
"sexExist": ok,
})
})
1.4 BindQuery 与 ShouldBindQuery
因为结构体也可以和json数据一一对应,所以我们也可以用结构体对象来返回json数据给前端
上述方式获取的都是string类型的值,通过这种方法我们也可以根据类型需要来获取
还是这个链接 http://localhost:8080/hello?id=1&name=zhangsan
因为有两个参数,我们可以这样定义一个结构体
type User struct {
Id int `form:"id"` //这里我们打标记来转成参数名
Name string `form:"name"`
}
然后再这样使用ShouldBindQuery 或者 BindQuery
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
var user User
err := c.ShouldBindQuery(&user) // 用结构体对象来装数据
if err != nil {
panic(err)
}
c.JSON(200, user)
})
我们可以在标记中使用binding:"required" 来说明一个参数为必须的
例如:
type User struct {
Id int `form:"id"`
Name string `form:"name"`
Sex string `form:"sex" binding:"required"`
}
这个时候如果我们输入的URL中没有sex这个参数的话,就会报错
BindQuery 与 ShouldBindQuery的区别:
当bind是必须的时候,ShouldBindQuery会报错,状态码不变还是200,错误由开发者自行处理。
BindQuery则在报错的同时,会将状态码改为400,意为失败。所以一般建议是使用Should开头的bind。
2. 数组参数
http://localhost:8080/hello?id=1&name=zhangsan&name=zhangsi&name=zhangwu
上面这个链接一个参数name的值却有三个,我们可以利用数组参数的方法来获取值,gin中有对应的array方法
2.1 QueryArray
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
var names []string
names = c.QueryArray("name")
id := c.Query("id")
c.JSON(200, gin.H{
"id": id,
"names": names,
})
})
2.2 GetQueryArray
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
var names []string
var ok bool
names, ok = c.GetQueryArray("name")
id := c.Query("id")
c.JSON(200, gin.H{
"id": id,
"names": names,
"namesExist": ok,
})
})
2.3 BindQuery 与 ShouldBindQuery
把结构体中的Name的类型改为切片
type User struct {
Id int `form:"id"`
Name []string `form:"name"`
}
后面一样的操作就行了
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
var user User
err := c.ShouldBindQuery(&user)
if err != nil {
log.Println(err)
}
c.JSON(200, user)
})
3. map参数
http://localhost:8080/hello?id=1&members[name1]=zhangsan&members[name2]=lisi
可以看到这个members参数是个map类型,怎么办呢?
其实也是一样的,Gin框架中有对应的 map方法
1. QueryMap
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
id := c.Query("id")
members := c.QueryMap("members")
c.JSON(200, gin.H{
"id": id,
"members": members,
})
})
2. GetQueryMap
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
id := c.Query("id")
members, ok := c.GetQueryMap("members")
c.JSON(200, gin.H{
"id": id,
"members": members,
"membersExist": ok,
})
})
map 参数不能通过 bind 绑定获取
POST
post请求一般是表单参数和json参数
1. 表单参数
现在我们从客户端发送表单数据到服务端,并从服务端获取表单数据返回到前端页面
表单中同样有普通参数、数组参数和map参数,这次统一获取
利用 Apifox 发送一个post请求,表单如上
Gin框架获取并返回到前端页面:
func main() {
r := gin.Default()
r.POST("/hello", func(c *gin.Context) {
id := c.PostForm("id")
members := c.PostFormArray("member")
address, ok := c.GetPostFormMap("address")
c.JSON(200, gin.H{
"id": id,
"members": members,
"address": address,
"addressExist": ok,
})
})
err := r.Run()
if err != nil {
panic(err)
}
}
成功获取到数据:
可以看到,Gin 的这些方法还是十分通俗易懂并且通用的,让我们一眼就能看懂这个方法是干嘛的。
2. json参数
如果传的不是普通参数,而是像 json 一样的数据的话,我们可以使用 shouldbind 方法来绑定
以 json 为例,我们就发送之前获取到的json数据, 这里也是使用apifox实现
{
"address": {
"home": "jian",
"school": "nanchang"
},
"addressExist": true,
"id": "1",
"members": [
"zhangsan",
"lisi"
]
}
我们先定义一个结构体
type User struct {
Id string `form:"id"`
Members []string `form:"members"`
Address map[string]string `form:"address"`
AddressExist bool `form:"addressExist"`
}
然后利用Gin 框架获取之前发送的json数据,并将获取到的数据返回前端
func main() {
r := gin.Default()
r.POST("/hello", func(c *gin.Context) {
var user User
err := c.ShouldBindJSON(&user)
if err != nil {
panic(err)
}
c.JSON(200, user)
})
err := r.Run()
if err != nil {
panic(err)
}
}
结果:
但是我们发现这里的字段大写了,我们标记
` form : " "`好像没有起到作用
事实上这里标记需要改成json的标记,像下面这样
type User struct {
Id string `json:"id"`
Members []string `json:"members"`
Address map[string]string `json:"address"`
AddressExist bool `json:"addressExist"`
}
再重新操作一次,发现结果ok了
响应
之前我们一直是使用的c.json 响应客户端,返回json数据,实际上我们能使用的方式有很多很多
我们以GET 请求和 http://localhost:8080/hello URL 为例,返回各种格式的数据
1. 字符串方式
r.GET("/hello", func(c *gin.Context) {
c.String(200, "hello world")
})
2. json方式
r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"json": "qwq",
})
})
3. 文件
r.GET("/hello", func(c *gin.Context) {
c.FileAttachment("./image/1.jpg", "有马加奈")
})
4. 重定向
r.GET("/hello", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
})
这里注意重定向的状态码不一样,是301,并且浏览器会有缓存,如果出不来页面可以换一个浏览器试试
5. http响应头
r.GET("/hello", func(c *gin.Context) {
c.Header("test", "testqwq")
c.String(200, "qwq")
})
这里看看 header里面,可以发现多了一条test testqwq
剩下还有类似XML 和 YAML, 可以参考官方文档或者源码。