【Go学习】04-1-Gin框架
- 初识框架
- go流行的web框架
- Gin
- iris
- Beego
- fiber
- Gin介绍
- Gin快速入门
- 路由
- RESTful API规范
- 请求方法
- URI
- 静态url
- 路径参数
- 模糊匹配
- 处理函数
- 分组路由
- 请求参数
- GET请求参数
- 普通参数
- 数组参数
- map参数
- POST请求参数
- 表单参数
- JSON参数
- 路径参数
- 文件参数
- 响应
- 字符串方式
- JSON方式
- XML方式
- 文件方式
- 设置http响应头
- 重定向
- YAML方式
初识框架
框架是一系列工具的集合,能让开发变的便捷。
学习框架的目的就是为了提供项目的开发效率,使我们更加专注业务,而不是和业务无关的底层代码。
go流行的web框架
如果学习过其他语言,可能知道Java用的比较多的是Spring框架,PHP用的比较多的是Laravel,python用的多的是Django,都在各自的语言中具有强大的统治力。
go
从诞生之初就带有浓重的开源属性,其原生库已经很强大,即使不依赖框架,也能进行高性能开发,又因为其语言并没有一定的设计标准,所以较为灵活,也就诞生了众多的框架,各具有特色,满足不同的喜好。
Gin
地址:https://github.com/gin-gonic/gin
号称最快的go语言web框架,目前是go官方的推荐框架(https://go.dev/doc/tutorial/)。
iris
地址:https://github.com/kataras/iris
性能比gin高一些,支持MVC,但这款框架评价不太好,使用上问题较多,近些年很少去选择使用
Beego
地址:https://github.com/beego/beego
国人开发,最早的go web框架之一,工具集比较完善,性能较差,据传言作者是php转行,所以框架带有浓厚的php特色,早期国内使用的多,目前少有人选择。
fiber
地址:https://github.com/gofiber/fiber
2020年发布的框架,发展迅速,建立在fasthttp之上,性能目前最高,受Express启发,比较简洁,上手较快,和gin类似。
当然还有其他一些框架,但从star数上,以及流行程度上看,gin一骑绝尘,gin的好处在于其简洁,扩展性,稳定性以及性能都比较出色。
go的框架其实是可以理解为库,并不是用了某一个框架就不能用别的框架,可以选择性的使用各个库中的优秀组件,进行组合。
Gin介绍
特性:
-
快速
基于 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。
-
可扩展性
新建一个中间件非常简单。
Gin快速入门
go版本需求:go1.13及以上
环境:windows 11
# 创建工作区
F:\Code\Golang\TuLing\workPath>mkdir ginlearn
F:\Code\Golang\TuLing\workPath>cd ginlearn
# 初始化工作区
F:\Code\Golang\TuLing\workPath\ginlearn>go work init
# 创建模块
F:\Code\Golang\TuLing\workPath\ginlearn>mkdir helloworld
F:\Code\Golang\TuLing\workPath\ginlearn>cd helloworld
# 初始化模块
F:\Code\Golang\TuLing\workPath\ginlearn\helloworld>go mod init test.com/helloworld
go: creating new go.mod: module test.com/helloworld
F:\Code\Golang\TuLing\workPath\ginlearn\helloworld>cd ..
# 将模块加入工作区
F:\Code\Golang\TuLing\workPath\ginlearn>go work use ./helloworld
使用goland打开
下载gin
PS F:\Code\Golang\TuLing\workPath\ginlearn> cd .\helloworld\
PS F:\Code\Golang\TuLing\workPath\ginlearn\helloworld> go get -u github.com/gin-gonic/gin
示例程序,创建main.go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
运行后,apifox进行测试
符合预期,这样简单的代码就实现了一个http的服务
路由
路由是URI到函数的映射。
一个URI含: http://localhost:8080/user/find?id=11
- 协议,比如http,https等
- ip端口或者域名,比如127.0.0.1:8080或者
www.test.com
- path,比如 /path
- query,比如 ?query
同时访问的时候,还需要指明HTTP METHOD,比如
-
GET
GET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据.
-
POST
POST方法用于将实体提交到指定的资源,通常会导致在服务器上的状态变化
-
HEAD
HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体.
-
PUT
PUT方法用请求有效载荷替换目标资源的所有当前表示
-
DELETE
DELETE方法删除指定的资源
-
CONNECT
CONNECT方法建立一个到由目标资源标识的服务器的隧道。
-
OPTIONS
OPTIONS方法用于描述目标资源的通信选项。
-
TRACE
TRACE方法沿着到目标资源的路径执行一个消息环回测试。
-
PATCH
PATCH方法用于对资源应用部分修改。
使用的时候,应该尽量遵循其语义
RESTful API规范
RESTful API 的规范建议我们使用特定的HTTP方法来对服务器上的资源进行操作。
比如:
- GET,表示读取服务器上的资源
- POST,表示在服务器上创建资源
- PUT,表示更新或者替换服务器上的资源
- DELETE,表示删除服务器上的资源
- PATCH,表示更新/修改资源的一部分
请求方法
r.GET("/get", func(ctx *gin.Context) {
ctx.JSON(200, "get")
})
r.POST("/post", func(ctx *gin.Context) {
ctx.JSON(200, "post")
})
r.DELETE("/delete", func(ctx *gin.Context) {
ctx.JSON(200, "delete")
})
r.PUT("/put", func(ctx *gin.Context) {
ctx.JSON(200, "put")
})
如果想要支持所有:
r.Any("/any", func(ctx *gin.Context) {
ctx.JSON(200, "any")
})
如果想要支持其中的几种:
r.GET("/hello", func(ctx *gin.Context) {
//数组 map list 结构体
ctx.JSON(200, gin.H{
"name": "hello world",
})
})
r.POST("/hello", func(ctx *gin.Context) {
//数组 map list 结构体
ctx.JSON(200, gin.H{
"name": "hello world",
})
})
URI
URI书写的时候,我们不需要关心scheme和authority这两部分,我们主要通过path和query两部分的书写来进行资源的定位。
静态url
比如/hello
,/user/find
r.POST("/user/find", func(ctx *gin.Context) {
})
路径参数
比如/user/find/:id
r.POST("/user/find/:id", func(ctx *gin.Context) {
param := ctx.Param("id")
ctx.JSON(200, param)
})
模糊匹配
比如/user/*path
r.POST("/user/*path", func(ctx *gin.Context) {
param := ctx.Param("path")
ctx.JSON(200, param)
})
处理函数
定义:
type HandlerFunc func(*Context)
通过上下文的参数,获取http的请求参数,响应http请求等。
分组路由
在进行开发的时候,我们往往要进行模块的划分,比如用户模块,以user开发,商品模块,以goods开头。
或者进行多版本开发,不同版本之间路径是一致的,这种时候,就可以用到分组路由了。
比如
ug := r.Group("/user")
{
ug.GET("find", func(ctx *gin.Context) {
ctx.JSON(200, "user find")
})
ug.POST("save", func(ctx *gin.Context) {
ctx.JSON(200, "user save")
})
}
gg := r.Group("/goods")
{
gg.GET("find", func(ctx *gin.Context) {
ctx.JSON(200, "goods find")
})
gg.POST("save", func(ctx *gin.Context) {
ctx.JSON(200, "goods save")
})
}
请求路径则为
[GIN-debug] GET /user/find --> main.main.func2 (3 handlers)
[GIN-debug] POST /user/save --> main.main.func3 (3 handlers)
[GIN-debug] GET /goods/find --> main.main.func4 (3 handlers)
[GIN-debug] POST /goods/save --> main.main.func5 (3 handlers)
请求参数
GET请求参数
使用Get请求传参时,类似于这样
http://localhost:8080/user/save?id=11&name=zhangsan
如何获取呢?
普通参数
request url: http://localhost:8080/user/save?id=11&name=zhangsan
-
Query:匹配字段
r.GET("/user/save", func(ctx *gin.Context) { id := ctx.Query("id") name := ctx.Query("name") ctx.JSON(200, gin.H{ "id": id, "name": name, }) })
如果参数不存在,就给一个默认值:
-
DefaultQuery:query为空时回返回个默认值
r.GET("/user/save", func(ctx *gin.Context) { id := ctx.Query("id") name := ctx.Query("name") address := ctx.DefaultQuery("address", "北京") ctx.JSON(200, gin.H{ "id": id, "name": name, "address": address, }) })
-
GetQuery:多了个query成功与否的返回值
r.GET("/user/save", func(ctx *gin.Context) { id, ok := ctx.GetQuery("id") address, aok := ctx.GetQuery("address") ctx.JSON(200, gin.H{ "id": id, "idok": ok, "address": address, "aok": aok, }) })
id是数值类型,上述获取的都是string类型,根据类型获取:通过form进行字段匹配
-
BindQuery:与结构体字段进行匹配
type User struct { Id int64 `form:"id"` Name string `form:"name"` } r.GET("/user/save", func(ctx *gin.Context) { var user User err := ctx.BindQuery(&user) if err != nil { log.Println(err) } ctx.JSON(200, user) })
-
ShouldBindQuery:有binding字段的要求必填,否则报错
r.GET("/user/save", func(ctx *gin.Context) { var user User err := ctx.ShouldBindQuery(&user) if err != nil { log.Println(err) } ctx.JSON(200, user) })
区别:
当bind是必须的时候,ShouldBindQuery会报错,开发者自行处理,状态码不变。
type User struct { Id int64 `form:"id"` Name string `form:"name"` Address string `form:"address" binding:"required"` }
BindQuery则报错的同时,会将状态码改为400。所以一般建议是使用Should开头的bind。
数组参数
请求url:http://localhost:8080/user/save?address=Beijing&address=shanghai
-
QueryArray:重复查询字段组装成数组
r.GET("/user/save", func(ctx *gin.Context) { address := ctx.QueryArray("address") ctx.JSON(200, address) })
-
GetQueryArray:多成功与否返回值
r.GET("/user/save", func(ctx *gin.Context) { address, ok := ctx.GetQueryArray("address") fmt.Println(ok) ctx.JSON(200, address) })
-
ShouldBindQuery
r.GET("/user/save", func(ctx *gin.Context) { var user User err := ctx.ShouldBindQuery(&user) fmt.Println(err) ctx.JSON(200, user) })
但是这样的话我们的user的address要求就是个数组
type User struct { Id int64 `form:"id"` Name string `form:"name"` Address []string `form:"address" binding:"required"` }
成功返回
{ "Id": 0, "Name": "", "Address": [ "Beijing", "shanghai" ] }
map参数
请求url:http://localhost:8080/user/save?addressMap[home]=Beijing&addressMap[company]=shanghai
-
QueryMap:组装成map
r.GET("/user/save", func(ctx *gin.Context) { addressMap := ctx.QueryMap("addressMap") ctx.JSON(200, addressMap) })
-
GetQueryMap:多成功与否返回值
r.GET("/user/save", func(ctx *gin.Context) { addressMap, _ := ctx.GetQueryMap("addressMap") ctx.JSON(200, addressMap) })
返回值
{ "company": "shanghai", "home": "Beijing" }
POST请求参数
post请求一般是表单参数和json参数
表单参数
r.POST("/user/save", func(ctx *gin.Context) {
id := ctx.PostForm("id")
name := ctx.PostForm("name")
address := ctx.PostFormArray("address")
addressMap := ctx.PostFormMap("addressMap")
ctx.JSON(200, gin.H{
"id": id,
"name": name,
"address": address,
"addressMap": addressMap,
})
})
- PostForm:从表单中对应的字段
- PostFormArray:从表单找对应的数组
- PostFormMap:从表单找对应的Map
r.POST("/user/save", func(ctx *gin.Context) {
var user User
err := ctx.ShouldBind(&user)
addressMap, _ := ctx.GetPostFormMap("addressMap")
user.AddressMap = addressMap
fmt.Println(err)
ctx.JSON(200, user)
})
- GetPostFormMap:从表单找对应的Map
JSON参数
json参数如下
{
"id":1111,
"name":"zhangsan",
"address": [
"beijing",
"shanghai"
],
"addressMap":{
"home":"beijing"
}
}
r.POST("/user/save", func(ctx *gin.Context) {
var user User
err := ctx.ShouldBindJSON(&user)
fmt.Println(err)
ctx.JSON(200, user)
})
对应字段进行匹配
其他类型参数注入xml,yaml等和json道理一样
路径参数
请求url:http://localhost:8080/user/save/111
r.POST("/user/save/:id", func(ctx *gin.Context) {
ctx.JSON(200, ctx.Param("id"))
})
-
:id
表示 占位符,可以匹配 任意路径中的值。 -
ctx.Param("id")
用于 获取路径参数id
的值。
文件参数
r.POST("/user/save", func(ctx *gin.Context) {
form, err := ctx.MultipartForm()
if err != nil {
log.Println(err)
}
files := form.File
for _, fileArray := range files {
for _, v := range fileArray {
ctx.SaveUploadedFile(v, "./"+v.Filename)
}
}
ctx.JSON(200, form.Value)
})
在form表单中请求file类型
这样就能在本地看到了
响应
字符串方式
r.GET("/user/save", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "this is a %s", "ms string response")
})
JSON方式
r.GET("/user/save", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"success": true,
})
})
XML方式
r.GET("/user/save", func(ctx *gin.Context) {
u := XmlUser{
Id: 11,
Name: "zhangsan",
}
ctx.XML(http.StatusOK, u)
})
文件方式
r.GET("/user/save", func(ctx *gin.Context) {
//ctx.File("./1.png")
ctx.FileAttachment("./1.png", "2.png")
})
设置http响应头
r.GET("/user/save", func(ctx *gin.Context) {
ctx.Header("test", "headertest")
})
重定向
r.GET("/user/save", func(ctx *gin.Context) {
ctx.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
})
YAML方式
r.GET("/user/save", func(ctx *gin.Context) {
ctx.YAML(200, gin.H{"name": "ms", "age": 19})
})