Gin 是一個基于 Go 語言的高性能 Web 框架
gin下載
在已有的go項目直接終端輸入
go get -u github.com/gin-gonic/gin
hello world快速上手
package mainimport ("github.com/gin-gonic/gin"
)func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {c.JSON(200, gin.H{"message": "Hello world!",})})router.Run()
}
運行
go run main.go
瀏覽器輸入http://localhost:8080/
可以看到瀏覽器顯示
到這里簡單的gin上手完成了
參數獲取
路徑參數獲取
這里可以類比Java springboot 的@RequestParam
即獲取路徑url?后邊的參數數據
func main() {r := gin.Default()r.GET("/user/phone", func(c *gin.Context) {//設置默認的數值 假設路徑參數phone沒有傳值則給一個默認值phone := c.DefaultQuery("phone", "333")//直接獲取路徑參數username := c.Query("username")Json格式返回參數c.JSON(http.StatusOK, gin.H{"message": "ok","username": username,"phone": phone,})})r.Run()
}
瀏覽器輸入user/phone?phone=333&username=用戶?
瀏覽器輸入user/phone?username=用戶
路徑參數沒有給phone的參數
這里給出了默認的返回333
這里的返回是由于gin程序里 phone := c.DefaultQuery("phone", "333")實現的
form參數獲取
func main() {//Default返回一個默認的路由引擎r := gin.Default()r.POST("/user/phone", func(c *gin.Context) {// DefaultPostForm取不到值時會返回指定的默認值username := c.DefaultPostForm("username", "用戶")// username := c.PostForm("username")phone := c.PostForm("phone")c.JSON(http.StatusOK, gin.H{"message": "ok","username": username,"phone": phone,})})r.Run(":8080")
}
postman調試(參數都有傳值)
返回
postman調試(username參數沒有傳值)
返回默認值 “用戶”
這里實現和獲取默認路徑差不多??? ??? ?username := c.DefaultPostForm("username", "用戶")
調用DefaultPostForm實現的
json參數獲取
這里可以類比Java的@RequestBody
如下程序實現了把給定的json參數原樣返回
func main() {r := gin.Default()r.POST("/json", func(c *gin.Context) {b, err := c.GetRawData() // 從c.Request.Body讀取請求數據if err != nil {log.Fatal(err)}var m map[string]interface{}// 反序列化err1 := json.Unmarshal(b, &m)if err1 != nil {log.Fatal(err1)}c.JSON(http.StatusOK, m)})r.Run(":8080")
}
postman調用
返回
path參數獲取
可以類比成Java的@PathVariable
func main() {r := gin.Default()r.GET("/user/phone/:username/:phone", func(c *gin.Context) {username := c.Param("username")phone := c.Param("phone")//輸出json結果給調用方c.JSON(http.StatusOK, gin.H{"message": "ok","username": username,"phone": phone,})})r.Run(":8080")
}
postman調用
傳格式如下
/user/phone/測試用戶參數/10190010933
返回
參數綁定
即利用反射機制,通過請求的Content-Type以及ShouldBind函數自動判斷請求的格式來解析出來再綁定到返回的數據中,這里就和springboot的@RequestParam等注解自動解析差不多
type Login struct {User string `form:"user" json:"user" binding:"required"`Password string `form:"password" json:"password" binding:"required"`
}func main() {router := gin.Default()// 綁定JSON的示例 ({"user": "q1mi", "password": "123456"})router.POST("/loginJSON", func(c *gin.Context) {var login Loginif err := c.ShouldBind(&login); err == nil {fmt.Printf("login info:%#v\n", login)c.JSON(http.StatusOK, gin.H{"user": login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// 綁定form表單示例 (user=q1mi&password=123456)router.POST("/loginForm", func(c *gin.Context) {var login Login// ShouldBind()會根據請求的Content-Type自行選擇綁定器if err := c.ShouldBind(&login); err == nil {c.JSON(http.StatusOK, gin.H{"user": login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// 綁定QueryString示例 (/loginQuery?user=q1mi&password=123456)router.GET("/loginQuery", func(c *gin.Context) {var login Login// ShouldBind()會根據請求的Content-Type自行選擇綁定器if err := c.ShouldBind(&login); err == nil {c.JSON(http.StatusOK, gin.H{"user": login.User,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// Listen and serve on 0.0.0.0:8080router.Run(":8080")
}
json格式
返回
form格式
返回
query格式
返回
文件上傳
單文件上傳
func main() {router := gin.Default()// 處理multipart forms提交文件時默認的內存限制是32 MiB// 可以通過下面的方式修改// router.MaxMultipartMemory = 8 << 20 // 8 MiBrouter.POST("/upload", func(c *gin.Context) {// 單個文件file, err := c.FormFile("f1")if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error(),})return}log.Println(file.Filename)dst := fmt.Sprintf("/Users/kanyu/Desktop/project/kanyu_server/ginlearn/templates/user/%s", file.Filename)// 上傳文件到指定的目錄c.SaveUploadedFile(file, dst)c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("'%s' uploaded!", file.Filename),})})router.Run()
}
原項目結構
postman上傳文件
結果
可以看到文件上傳成功 目錄下多了個3.zip文件
多文件上傳
func main() {router := gin.Default()// 處理multipart forms提交文件時默認的內存限制是32 MiB// 可以通過下面的方式修改// router.MaxMultipartMemory = 8 << 20 // 8 MiBrouter.POST("/upload", func(c *gin.Context) {// Multipart formform, _ := c.MultipartForm()//取key=file的參數文件files := form.File["file"]for index, file := range files {log.Println(file.Filename)dst := fmt.Sprintf("/Users/kanyu/Desktop/project/kanyu_server/ginlearn/templates/user/_%d_%s", index, file.Filename)// 上傳文件到指定的目錄c.SaveUploadedFile(file, dst)}c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%d files uploaded!", len(files)),})})router.Run()
}
原項目結構
postman調用多文件上傳
返回
項目結構
路由
常規路由
router.GET("/", func(c *gin.Context) {}router.POST("/", func(c *gin.Context) {}router.GET("/", func(c *gin.Context) {}
這些路由在之前參數獲取的時候講過了
和java中差不多 根據請求類型和路徑的不同獲取請求的參數進行處理
Any
func main() {router := gin.Default()router.Any("/", func(c *gin.Context) {c.JSON(200, gin.H{"message": "Hello world!",})})router.Run()
}
get請求
post請求
可以看到無論請求類型如何 都會返回程序中定義的"Hello world!"
NoRoute
即無路由請求傳任何路徑的返回處理
func main() {router := gin.Default()router.NoRoute(func(c *gin.Context) {c.JSON(200, gin.H{"message": "404 Not Found",})})router.Run()
}
給定任意路徑
路由組
相當于Java springboot的如下結構 利用@RestController和@RequestMapping("/chat")
都擁有共同的前綴/chat
所以擁有共同的前綴url劃分為路由組
func main() {r := gin.Default()userGroup := r.Group("/user"){userGroup.GET("/index", func(c *gin.Context) {c.JSON(200, gin.H{"message": "user index",})})userGroup.GET("/login", func(c *gin.Context) {c.JSON(200, gin.H{"message": "user login GET",})})userGroup.POST("/login", func(c *gin.Context) {c.JSON(200, gin.H{"message": "user login POST",})})}shopGroup := r.Group("/shop"){shopGroup.GET("/index", func(c *gin.Context) {c.JSON(200, gin.H{"message": "shop login",})})shopGroup.GET("/cart", func(c *gin.Context) {c.JSON(200, gin.H{"message": "shop cart",})})shopGroup.POST("/checkout", func(c *gin.Context) {c.JSON(200, gin.H{"message": "shop checkout",})})// 嵌套路由組shouIndexGroup := shopGroup.Group("/index")shouIndexGroup.GET("/router", func(c *gin.Context) {c.JSON(200, gin.H{"message": "shop index router",})})}r.Run()
}
請求/user/index
請求GET? /user/login
請求POST??/user/login
請求/shop/index
請求/shop/cart
請求/shop/checkout
嵌套路由?請求/shop/index/router
中間件
專注于HTTP請求處理流程的攔截與擴展,如日志記錄、權限驗證、異常恢復等。其核心是通過鏈式調用模式對單個請求生命周期進行功能增強,屬于輕量級、高并發的Web開發組件,
比如Gin默認中間件gin.Logger()
用于請求日志記錄,gin.Recovery()
用于panic恢復
gin框架的中間件類比Java
如Spring Interceptor的preHandle()
和postHandle()
方法,與Gin中間件的請求前后邏輯對應,適合權限檢查、參數預處理等場景
如Java?Servlet Filter均通過鏈式調用(Chain of Responsibility模式)攔截HTTP請求,可在請求處理前、后插入邏輯
定義中間件和注冊
func StatCost() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()c.Set("name", "小王子") // 可以通過c.Set在請求上下文中設置值,后續的處理函數能夠取到該值// 調用該請求的剩余處理程序c.Next()// 不調用該請求的剩余處理程序// c.Abort()// 計算耗時cost := time.Since(start)log.Println(cost)}
}// 全局注冊路由
func main() {// 新建一個沒有任何默認中間件的路由r := gin.New()// 注冊一個全局中間件r.Use(StatCost())r.GET("/test", func(c *gin.Context) {name := c.MustGet("name").(string) // 從上下文取值log.Println(name)c.JSON(http.StatusOK, gin.H{"message": "Hello " + name,})})r.Run()
}
調用/test
可以看到返回了中間件前置處理塞進去的值 "小王子"
這里利用c.MustGet("name").(string)取到了上下文數值,底層利用了Context的能力,這個后續再記錄下Context的核心實現和原理
單個路由注冊中間件
func StatCost() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()c.Set("name", "小王子") // 可以通過c.Set在請求上下文中設置值,后續的處理函數能夠取到該值// 調用該請求的剩余處理程序c.Next()// 不調用該請求的剩余處理程序// c.Abort()// 計算耗時cost := time.Since(start)log.Println(cost)}
}//記錄響應體中間件type bodyLogWriter struct {gin.ResponseWriter // 嵌入gin框架ResponseWriterbody *bytes.Buffer // 我們記錄用的response
}// Write 寫入響應體數據
func (w bodyLogWriter) Write(b []byte) (int, error) {w.body.Write(b) // 我們記錄一份return w.ResponseWriter.Write(b) // 真正寫入響應
}// ginBodyLogMiddleware 一個記錄返回給客戶端響應體的中間件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware() gin.HandlerFunc {return func(c *gin.Context) {blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}c.Writer = blw // 使用我們自定義的類型替換默認的c.Next() // 執行業務邏輯fmt.Println("Response body: " + blw.body.String()) // 事后按需記錄返回的響應}
}func main() {// 新建一個沒有任何默認中間件的路由r := gin.New()// 注冊兩個中間件 一個記錄耗時 一個記錄響應體 這里可以注冊多個中間件r.GET("/test", StatCost(), ginBodyLogMiddleware(), func(c *gin.Context) {name := c.MustGet("name").(string) // 從上下文取值log.Println(name)c.JSON(http.StatusOK, gin.H{"message": "Hello " + name,})})r.Run()
}
請求
后臺打印日志
可以看到后臺打印出了相應體?Response body: {"message":"Hello 小王子"}
以及耗時209.369
參考:
李文周go語言教程