本章內容,會介紹一下gin的運用,以及gin框架底層的內容,話不多說,開始進入今天的主題吧!
一.基本使用
gin框架支持前后端不分離的形式,也就是直接使用模板的形式。
模板是什么?
這里可能有同學不太了解,其實就是指的html,直接調用html,在后續的介紹中會有所了解。
1.1 模板搭建
首先簡單看一下引用模板的樣子吧
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//解析模板r.LoadHTMLFiles("templates/hello.html")r.GET("/hello", func(c *gin.Context) {//Http請求c.HTML(http.StatusOK, "hello.html", gin.H{ //模板渲染"title": "Hello World",})})r.Run() //啟動server
}
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body>{{ .title }}<h1>haha</h1></body>
</html>
對應的文件就是hello.html,然后運行go代碼,去默認端口查看即可。
上述對應的函數會在后續介紹,接下來看看靜態文件(css,js)的引入
1.2 靜態文件的引入
- 靜態文件就是指的是css,js那一類的文件
r.Static("/static", "./static")
就是如果以static開頭的文件,會去static下面去找
r.Static("/xxx", "./static")
也就是xxx開頭的文件去static下面去找
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//靜態文件渲染r.Static("/static", "./static")//解析模板r.LoadHTMLFiles("templates/hello.html")r.GET("/hello", func(c *gin.Context) {//Http請求c.HTML(http.StatusOK, "hello.html", gin.H{ //模板渲染"title": "Hello World",})})r.Run() //啟動server
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="/static/css/hello.css"><!-- 這里其實就是去找的static開頭的文件都去 /static下面取找把static改為xxx也是可以實現的 -->
</head>
<body>{{ .title }}<h1>haha</h1><script src="/static/js/hello.js"></script>
</body>
</html>
alert(123);
body{background-color: hotpink;
}
1.3 前后端分離的搭建
主要還是前后端分離的搭建,說一下流程
- 首先設置開發模式
- 創建實例
- 設置中間件
- 加載路由
- 最后啟動
func InitRouter(cfg *config.Configuration) {gin.SetMode(gin.DebugMode)//r := gin.New() //獲取一個gin的引擎實例r := gin.Default() //這個是創建了一個默認的gin實例對象r.Use(cor.Cors()) // 跨域中間件repo := repositories.NewUrlMapRepoUse()service := service2.NewUrlService(repo)UrlHandler := shortURL.NewURLHandler(service)r.GET("/:code", UrlHandler.RedirectURL)apiv1 := r.Group("/api/v1"){apiv1.POST("/shorturl", UrlHandler.CreateURL)}r.Run(":8080")
}
這個例子無法直接運行,他是我的一個案例
二.具體函數和步驟介紹
目前來看,大部分項目都是前后端分離的項目,所以我們采用的也是前后端分離的架構,下面會按照之前介紹的步驟,一一介紹對應的函數。
2.1 開發模式
gin.SetMode(gin.DebugMode)
在gin包下,它里面有默認的常量表示開發模式,就像上面的gin.DebugMode
也可以改為"debug"也是可以的,兩者等價
除了上面的debug模式還有release模式
debug模式會展示更多細節內容,但是release模式就是一個運行,而不是測試
具體介紹一下:
- DebugMode:這是默認模式,適用于開發和調試階段。在這種模式下,Gin 會輸出詳細的調試信息,幫助開發者快速定位問題。例如: gin.SetMode(gin.DebugMode)
- ReleaseMode:適用于生產環境。在這種模式下,Gin 會減少日志輸出,提升性能和安全性。例如: gin.SetMode(gin.ReleaseMode)
- TestMode:主要用于單元測試,Gin 自身的測試會使用這種模式。對于一般開發者來說,這種模式并不常用。例如: gin.SetMode(gin.TestMode)
2.2 實例對象
r := gin.Default() //這個是創建了一個默認的gin實例對象或者r:= gin.New()
兩者的主要區別就是
gin.default默認提供了兩個中間件
但是gin.New()獲得實例沒有任何中間件
r.Use(gin.Logger()) // 日志中間件
r.Use(gin.Recovery()) //
gin.Recovery()中間件
gin.Recovery()
是一個內置的Gin中間件,它的主要目的是捕獲和處理在請求處理過程中發生的任何panic。當一個panic發生時,如果沒有適當的恢復機制,程序將會崩潰并終止運行。gin.Recovery()
中間件提供了一個安全網,它能夠捕獲這些panic,防止程序崩潰,并且返回一個500內部服務器錯誤的響應給客戶端。
2.3 中間件的加入
r.Use(cor.Cors()) // 跨域中間件
主要就是通過r.Use來添加中間件,除了自帶的中間件,我們也可以自己編寫中間件,會在后續聊到中間件的時候具體介紹一下。
2.4 路由的加入
r.GET("/:code", UrlHandler.RedirectURL)apiv1 := r.Group("/api/v1"){apiv1.POST("/shorturl", UrlHandler.CreateURL)}
所謂的路由就是url對應的處理器,處理前端傳送過來的請求,上述的寫法是路由組的形式,很容易看出來,前綴都是api/v1
2.5 啟動gin引擎
r.Run(":8080")
就是在8080端口啟動這個程序
三.請求和響應
所謂的請求和響應就是客戶端和服務器之間的一個信息溝通,他們之間交流的信息格式有很多,比如json,xml等,一般來說都是json比較多。
json就是一個前端和后端交互的一個媒介,換句話說就是一種數據格式。
3.0 前置補充
如果大家有學過http協議,應該對請求方法有所了解,比如Get,Post等等方法,在gin框架中大家不難發現它已經為這些不同的方法做好了封裝處理。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func Sayhello(c *gin.Context) {c.JSON(200, gin.H{"message": "hello world",})
}func main() {//創建一個默認的路由引擎r := gin.Default() //返回一個默認的路由引擎//指定用戶使用GET請求訪問r.GET("/ping", Sayhello)r.GET("/books", func(c *gin.Context) {c.JSON(200, gin.H{"message": "Get",})})r.POST("/books", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "post",})})r.PUT("/books", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "Put",})})r.DELETE("/books", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "DELETE",})})//啟動服務器r.Run() // 監聽并在 0.0.0.0:8080 上啟動服務
}
其中的func(c *gin.Context) { 函數體 }就是http里面的處理器函數
在這個案例中,也可以直接引用處理器函數,要求和http的處理器是一樣的,必須是func(c *gin.Context)類型,這樣寫讓路由更加簡單,也可以更好的實現分層。
//指定用戶使用GET請求訪問
r.GET("/ping", Sayhello)
c.JSON(http.StatusOK, gin.H{ (json格式 ) })
說一下JSON這個函數,它的作用就是處理器處理完成之后,給前端的一個響應,第一個參數就是狀態碼,如果有同學不了解的,可以自行上網搜索一下。
第二個參數就是后續要將的響應數據,采用的是json格式。
3.1 響應
響應,其實就是服務器給客戶端發送數據的操作,在gin框架中,對響應的渲染主要有兩種方式:
一個是map,另一個就是結構體。
3.1.1 gin.H
map方式 (也就是gin.H),為了處理不同類型的值,一般都會采用接口作為value
但是定義map[]interface比較麻煩,所以gin框架就對其做了一個封裝,改為了gin.H。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("json", func(c *gin.Context) {//方法一使用map//data := map[string]interface{}{// "name": "小王子",// "age": 18,// "message": "hello world",// }// c.JSON(http.StatusOK, data)//})//簡化的方式//但是gin的作者考慮到了這個問題,如果一直寫map麻煩,就有了gin.Hdata := gin.H{"name": "小王子","age": 18,"message": "hello world",}c.JSON(http.StatusOK, data)})r.Run(":9090") //啟動server
}
3.1.2 *結構體方式(這一塊的內容可以了解)
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("json", func(c *gin.Context) {type msg struct {Name stringAge intMessage string}m := msg{"小王子",18,"hello world",}c.JSON(http.StatusOK, m)})r.Run(":9090") //啟動server
}
這里注意結構體的訪問問題,大小寫是否可以訪問呢??
如果就想他是小寫的name呢??查看內容即可,我做一個簡單的案例。
通過tag(標簽)來實現一個這樣的效果
type msg struct {Name string `json:"name"`Age int `json:"age"`Message string `json:"message"`}
3.2 請求
類比http包,如果大家對http有了解的話,一個知道,http可以對請求做處理,比如路徑參數,表格參數等等。
接下來就是介紹一下gin框架中是如何處理這些內容的。
3.2.1 querystring參數
這里還是介紹一下query函數的作用:就是獲取參數的作用
name := c.Query("query") //通過query獲取請求中的query string參數
這里獲取的是url上的參數,是在路由之后/web?參數
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("/web", func(c *gin.Context) {//獲取瀏覽器那邊發請求攜帶的querystring參數name := c.Query("query") //通過query獲取請求中的querystring參數c.JSON(http.StatusOK, gin.H{"name": name,})})r.Run(":9090") //啟動server
}
http://localhost:9090/web?query=程瀟,去瀏覽器搜索這個路徑
除了Quety函數,還有DefaultQuery和GetQuery兩個函數
DefaultQuery()則是如果沒用該參數,返回一個設置的默認值
GetQuery()則會返回一個布爾值,來判斷是否返回了一個值
除此之外還可以傳遞多個參數,不是只有一個參數哦
多個key-value使用&來連接
3.2.2.form參數
這個一般是使用在獲取信息的界面。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.LoadHTMLFiles("templates/hello.html", "templates/index.html")r.GET("/hello", func(c *gin.Context) {c.HTML(http.StatusOK, "hello.html", nil)})//接受請求r.POST("/hello", func(c *gin.Context) {user := c.PostForm("username")mm := c.PostForm("password")c.HTML(http.StatusOK, "index.html", gin.H{"Name": user,"Password": mm,})})r.Run(":9090") //啟動server
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="/static/css/hello.css"><!-- 這里其實就是去找的static開頭的文件都去 /static下面取找把static改為xxx也是可以實現的 -->
</head>
<body><form action="/hello" method="post"><div><label for="username">username:</label><input type="text" name="username" id="username"></div><div><label for="password">password:</label><input type="text" name="password" id="password"></div><div><input type="submit" value="登錄"></div></form><script src="/static/js/hello.js"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h1>hello,{{ .Name}}</h1><h1>你的密碼是{{.Password}}</h1>
</body>
</html>
這里我們可以發現,就是一個/hello可以對于不同的請求的界面,一個是get,一個是post
都是可以實現的
還要該清楚這里的邏輯是怎么實現的
他的邏輯就是你進入get的hello網址(不過一般都是/開頭的),然后在登入的html里面把內容通過form表單在傳遞給他參數action規定的處理器上,然后看看他是什么方法,由于是post方法,所以會傳給post方法對應的處理器上面,從而進行不同的操作啦。
還存在第二種方法
他和上面的query string差不多,他就是類似一個,如果沒有的話就返回一個默認值,你應該是傳入input標簽里面的name,但是沒有傳入,就會取默認值。
還有第三種,和上面的大差不差
3.2.3.path參數的獲取
path參數也被稱為路徑參數,主要通過/:key 來表示,獲取這個key的值
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/:name/:age", func(c *gin.Context) {name := c.Param("name")age := c.Param("age")c.JSON(200, gin.H{"name": name,"age": age,})})r.Run(":9090")
}
看他的URL上,就可以將他傳給我們的get,去處理
package mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/:name/:age", func(c *gin.Context) {name := c.Param("name")age := c.Param("age")c.JSON(200, gin.H{"name": name,"age": age,})})r.GET("/blog/:year/:month", func(c *gin.Context) {year := c.Param("year")month := c.Param("month")c.JSON(200, gin.H{"year": year,"month": month,})})r.Run(":9090")
}
可以通過加前綴的方式改變這個問題
3.2.4.參數的綁定
c.ShouldBind(&參數)
我們可以使用這個函數直接提取上述三種方式傳入的數據,并且綁定到對應的結構體里面去。
非常的好用哦
比如我們的登入注冊功能就可以不在使用頁面做了,直接使用api來進行測試也是非常的快的。
接下展示一下json的例子,由于是前后端分離的形式,所以要認真學習者一塊的內容
json發送和form選的是不一樣的啦
package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)type User struct {Username string `json:"username" form:"username"`Password string `json:"password" form:"password"`
}func main() {r := gin.Default()r.LoadHTMLFiles("templates/hello.html")r.GET("/", func(c *gin.Context) {//username := c.Query("username")//password := c.Query("password")//u1 := User{// Username: username,// Password: password,//}var u1 Usererr := c.ShouldBind(&u1) //這里為什么要傳&u1引用,因為你要去改變他的值/*這里就會還有一個問題,你怎么判斷傳入的字段和你的結構體字段符合呢?在結構體體后面加上form,就是tag*/if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})} else {fmt.Println(u1)c.JSON(http.StatusOK, gin.H{"status": "ok",})}})r.GET("/hello", func(c *gin.Context) {c.HTML(http.StatusOK, "hello.html", nil)})r.POST("/form", func(c *gin.Context) {var u1 Usererr := c.ShouldBind(&u1)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})} else {fmt.Println(u1)c.JSON(http.StatusOK, gin.H{"status": "ok",})}})r.POST("/json", func(c *gin.Context) {var u1 Usererr := c.ShouldBind(&u1)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error(),})} else {fmt.Println(u1)c.JSON(http.StatusOK, gin.H{"status": "ok",})}})r.Run(":9090")
}
通過觀察,我們會發現不管是什么方式去獲取,他們的大致代碼都是差不多喲,你應該可以發現吧。
除了請求和URL的不同,其他大致都可以,
由此可見ShouldBind(&參數)的厲害,不管你是什么格式,都可以調用
其實這里一般只用來處理表單傳入的數據,一般前端form表單的數據轉化為json格式或者其他格式傳給后端來解析。
除了ShouldBind,還有ShouldBindJSON專門用來解析josn數據。
3.2.5.獲取Host
直接通過c.Host就可以獲取路徑的主機名
這些函數其實本質上都是對http的一個封裝,簡單看一下源碼,大家應該就知道如何使用了。
四.文件的傳輸
4.1 介紹
4.2 操作案例
f, err := c.FormFile("f1") 首先獲取文件
dst := fmt.Sprintf("%d", f.Filename)
dst := path.Join("./", f.Filename)
上述兩個方法都是將個獲取當前目錄的方法
c.SaveUploadedFile(f, dst)
然后這里就是將這文件保存到指定目錄下面
package mainimport ("github.com/gin-gonic/gin""net/http""path"
)func main() {r := gin.Default()r.LoadHTMLFiles("templates/index.html")r.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", nil)})r.POST("/upload", func(c *gin.Context) {//從請求中讀取文件f, err := c.FormFile("f1")if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})} else {// 將讀取的文件保存在本地// dst := fmt.Sprintf("%d", f.Filename)dst := path.Join("./", f.Filename)c.SaveUploadedFile(f, dst)c.JSON(http.StatusOK, gin.H{"message": "ok"})}})r.Run(":9090")
}
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="/upload" method="post" enctype="multipart/form-data"><!-- 插入文件或者照片必須帶上enctype="multipart/form-data"--><input type="file" name="f1"><input type="submit" value="上傳"></form></body>
</html>
上面是單個文件進行的一個讀取操作
下面介紹一下多個文件的讀取操作
c.MultipartForm 獲取多個文件
然后存入到這個數組里面去
再用一個for range循環上傳到指定的文件去即可
五.請求重定向
5.1 概念介紹
關于重定向,在http的內容里面已經涉及過了,就不在過多介紹了。
5.2 案例
5.2.1 http 重定向
下面就是一個重定向,到百度的網址
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("/", func(c *gin.Context) {//c.JSON(http.StatusOK, gin.H{// "status": "ok",//})c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")})r.Run(":9090")
}
5.2.2 路由重定向
也就是你雖然進入的是a的路由,但是他最后卻是指向b的路由,這就是所謂的路由重定向
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()r.GET("/a", func(c *gin.Context) {//跳轉到bc.Request.URL.Path = "/b" //把請求的URL修改r.HandleContext(c)//繼續后續的處理,跳轉到b路由})r.GET("/b", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"massage": "ok",})})r.Run(":9090")
}
六.路由和路由組
6.1 基礎介紹
any請求里面包含所有的請求哦,可以用switch來設置
NoRoute
處理函數允許開發者定義當請求沒有匹配到任何路由時應該執行的操作。默認情況下,Gin框架會為NoRoute
情況返回一個404狀態碼
6.2 案例
6.2.1普通路由
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()//訪問/index的r.GET("/test", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": "GET",})})r.POST("/test", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": "POST",})})r.DELETE("/test", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": "DELETE",})})r.PUT("/test", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": "PUT",})})//這個會處理所以的請求,懶得話就可以寫這個anyr.Any("/user", func(c *gin.Context) {switch c.Request.Method {case "GET":c.JSON(http.StatusOK, gin.H{"method": "GET"})case http.MethodPost:c.JSON(http.StatusOK, gin.H{"method": "POST"})}c.JSON(http.StatusOK, gin.H{"method": "ANY",})})r.Run(":9090")
}
但是一般來說,我們設置的URL是有限制的,但是用戶可能會隨便輸入各種各樣的路徑,為了解決如果是不存在的路徑,我們就設置一個頁面進行跳轉
我們可以使用NoRoute來實現這個功能
r.NoRoute(func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"信息": "404,查找無果",})})
6.2.2路由組
如果當我們去寫項目的時候,發現有很多的來自同一個URL下的多個不同的頁面,如果一個一個寫r.GET或者什么,寫起來是相當麻煩的。
比如這樣:
我們該如何去修改呢?
這個時候我們就可以使用路由組去解決這個問題
這樣就可以了
6.3路由組的嵌套
七.中間件
7.1 概念介紹
除此之外,還有跨域中間件,在gin框架中提供的有跨域的庫cor。
7.2 案例
要明白Next函數和Abort函數
下面展示一個案例
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func indexhandler(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "index",})
}// 定義一個中間件:統計耗時
func m1(c *gin.Context) {fmt.Println("m1進入")//計時start := time.Now()c.Next() //調用后續的處理函數//c.Abort() //阻止調用后續的處理函數cost := time.Since(start)fmt.Printf("m1 cost %v\n", cost)
}func main() {r := gin.Default()r.GET("/test", m1, indexhandler)//這里請求進來之后先走m1,之后才走index,這里的m1就相當于是一個中中間件r.Run(":9090")
}
如果存在一個情況就是多個請求都需要使用m1這個中間件,有沒有什么快捷的方式呢?
當然有了:
就是Use這個函數
加上之后,后續的函數都會有這個m1中間件
如果存在多個中間件:
要明白他在執行的順序:他不是一個一個調用,而是類似一個嵌套的那種方式
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func indexhandler(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "test",})
}// 定義一個中間件:統計耗時
func m1(c *gin.Context) {fmt.Println("m1進入")//計時start := time.Now()c.Next() //調用后續的處理函數,就是相當于是調用后面的indexhandler//c.Abort() //阻止調用后續的處理函數cost := time.Since(start)fmt.Printf("m1 cost %v\n", cost)
}func m2(c *gin.Context) {fmt.Println("m2 in")c.Next() //調用后續的處理函數fmt.Println("m2 out")
}func main() {r := gin.Default()r.Use(m1, m2)r.GET("/test", indexhandler)//這里請求進來之后先走m1,之后才走index,這里的m1就相當于是一個中中間件r.Run(":9090")
}
如果把m2里面的Next改為Abort,這樣就會斷掉,這樣網頁就會沒有內容
如果想后面的m2 out 都沒有
直接return即可
7.3 真正的使用案例
一般情況下是和閉包一起使用的
為路由準備中間件啦
7.4 如何實現傳遞數據(m2->indexhandler)
也就是使用函數GET和SET
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func indexhandler(c *gin.Context) {name, ok := c.Get("name")if !ok {name = "匿名用戶"}c.JSON(http.StatusOK, gin.H{"message": name,})
}// 定義一個中間件:統計耗時
func m1(c *gin.Context) {fmt.Println("m1進入")//計時start := time.Now()c.Next() //調用后續的處理函數,就是相當于是調用后面的indexhandler//c.Abort() //阻止調用后續的處理函數cost := time.Since(start)fmt.Printf("m1 cost %v\n", cost)
}func m2(c *gin.Context) {fmt.Println("m2 in")c.Set("name", "qimi")c.Next() //調用后續的處理函數//c.Abort() //阻止調用后續的處理函數fmt.Println("m2 out")
}func main() {r := gin.Default()r.Use(m1, m2)r.GET("/test", indexhandler)//這里請求進來之后先走m1,之后才走index,這里的m1就相當于是一個中中間件r.Run(":9090")
}