【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 mainimport "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 Usererr := 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 Usererr := 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 Usererr := 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 Usererr := ctx.ShouldBind(&user)addressMap, _ := ctx.GetPostFormMap("addressMap")user.AddressMap = addressMapfmt.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 Usererr := 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.Filefor _, 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})
})