為什么選擇Gin框架?
Gin 是一個基于 Go 語言的高性能 Web 框架,具備以下優勢:
- 輕量高效:底層依賴 net/http,性能接近原生。
- 簡潔優雅:API 設計友好,支持路由分組、中間件鏈、參數綁定等特性。
- 生態豐富:內置 JSON 解析、日志記錄、錯誤恢復等實用功能,社區插件生態完善。
- 無論是構建 RESTful API 還是全棧應用,Gin 都能顯著提升開發效率。
安裝
要安裝Gin軟件包,您需要安裝Go并首先設置Go工作區。
- 首先需要安裝Go(需要1.10+版本),然后可以使用下面的Go命令安裝Gin。
go get -u github.com/gin-gonic/gin
- 將其導入您的代碼中:
import "github.com/gin-gonic/gin"
基礎示例
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {// 1.創建 (實例化gin.Engine結構體對象)r := gin.Default()// 2.綁定路由規則,執行的函數// gin.Context,封裝了request和responser.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "Hello, Gin!")})// 3.監聽端口,默認在8080// Run("里面不指定端口號默認為8080")r.Run(":8080")
}
運行后訪問 http://localhost:8080,即可看到返回的字符串。
Gin工工作作流流程
核心概念
- Engine 容器對象,整個框架的基礎
- Engine.tree 負責存儲路由和handle方法的映射,采用類似于字典樹的結構
- Engine.RouterGroup 其中handlers存儲著所有中間件
- Context 上下文對象,負責處理 請求回應 ,其中handles的存儲處理請求時中間件和處理方法的
請求處理 流程
GIN啟動流程
Gin初始化----> Use 中間件 ----> 注冊Routers路由 ----> RUN()啟動
Gin原原理解析
參考資料:http://v5blog.cn/pages/dd7d5a/
gin.Default()
Default()跟New()幾乎一模一樣, 就是調用了gin內置的Logger(), Recovery()中間件
// Default返回一個已經附加了Logger和Recovery中間件的Engine實例
func Default() *Engine {debugPrintWARNINGDefault()engine := New() "# 默認實例// 注冊中間建,中間件的是一個函數,最終只要返回一個 type HandlerFunc func(*Context) 就可以engine.Use(Logger(), Recovery()) // 默認注冊的兩個中間件return engine
}
engine := New() 初初始化始化
通過調用 gin.New() 方法來實例化 Engine容器
engine.Use() 注注冊冊中間件 中間件
路由與參數處理
無參路由
func HelloWorldhandler(ctx *gin.Context){ctx.JSON(200, gin.H{"message": "Hello World",})
}func main() {// 創建一個默認的路由器router := gin.Default()// gin.Context是一個結構體,包含了請求和響應的細節, 封裝request和responserouter.GET("/",HelloWorldhandler)// 路由重定向router.GET("/re", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")})// 啟動服務器router.Run(":8081")
}
動態路由參數
通過 :param 捕獲 URL 中的變量:
(可以通過Context的Param方法來獲取API參數)
r.GET("/book/:id", func(c *gin.Context) {bookID := c.Param("id")c.String(http.StatusOK, "書籍ID: %s", bookID)
})
查詢參數
使用 Query 或 DefaultQuery 獲取 URL 參數:
http://127.0.0.1:8000/user?name=zhangsan
r.GET("/user", func(c *gin.Context) {name := c.Query("name")role := c.DefaultQuery("role", "guest")c.JSON(200, gin.H{"name": name, "role": role})
})
ShouldBind參數綁定
通過 ShouldBind 自動解析請求體(支持 JSON、Form 等):
我們可以基于請求的 Content-Type 識別請求數據類型并利用反射機制
自動提取請求中 QueryString 、 form表單 、 JSON 、 XML 等參數到結構體中
type LoginForm struct {Username string `form:"username" binding:"required"`Password string `form:"password" binding:"required"`
}r.POST("/login", func(c *gin.Context) {var form LoginFormif err := c.ShouldBind(&form); err != nil {c.JSON(400, gin.H{"error": err.Error()})return}c.String(200, "登錄成功: %s", form.Username)
})
完整代碼案例
package mainimport ("fmt""github.com/gin-gonic/gin""net/http"
)// HelloWorldhandler 無參數路由處理函數
func HelloWorldhandler(ctx *gin.Context){ctx.JSON(200, gin.H{"message": "Hello World",})
}func GetBookDetailHandler(ctx *gin.Context ) {bookId := ctx.Param("id")ctx.String(http.StatusOK, fmt.Sprintf("成功獲取書籍詳情:%s", bookId))}func GetUserDetailHandlers(ctx *gin.Context) {username := ctx.Query("username")ctx.String(http.StatusOK, fmt.Sprintf("成功獲取用戶詳情:%s", username))
}type Login struct {Username string `form:"username" json:"username" binding:"required"`Password string `form:"password" json:"password" binding:"required"`
}func ResponseJsonHandler(c *gin.Context) {type Data struct {Msg string `json:"msg:"`Code int `json:"code"`}d := Data{Msg: "success",Code: 200,}c.JSON(http.StatusOK, d)
}
func Loginhandler(c *gin.Context) {var login Loginif err := c.ShouldBind(&login); err == nil {c.JSON(http.StatusOK, gin.H{"username": login.Username,"password": login.Password,})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}
}func ResponseStringHandle(c *gin.Context) {c.String(http.StatusOK, "Hello World")
}func main() {// 創建一個默認的路由器router := gin.Default()// gin.Context是一個結構體,包含了請求和響應的細節, 封裝request和response// 無參數路由router.GET("/",HelloWorldhandler)// 返回字符串router.GET("/string", ResponseStringHandle)// 返回jsonrouter.GET("/json", ResponseJsonHandler)// 動態路由參數router.GET("/book/:id", GetBookDetailHandler)// 查詢參數 Query 或 DefaultQueryrouter.GET("/user/", GetUserDetailHandlers)// 參數綁定router.POST("/login", Loginhandler)// 路由重定向router.GET("/re", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")})// 啟動服務器router.Run(":8081")
}
路由分發
為什么需要路由分發?
- 我們一個項目有非常多的模塊,如果全部寫在一塊導致代碼結構混亂,不利于后續的擴展
- 按照大的模塊,每個模塊有自己獨立的路由,主路由可以再main.go中進行注冊
項目結構
├── go.mod
├── go.sum
├── main.go
└── routers├── books.go└── users.go
main.go
import (
"days/routers"
"fmt"
"github.com/gin-gonic/gin"
)
func main() {router := gin.Default()// 全局中間件router.Use(MiddleWare())// 加載路由routers.LoadUsers(router)routers.LoadBooks(router)router.Run(":8081")
}
routers/users.go
package routers
import ("fmt""github.com/gin-gonic/gin""net/http""time"
)func LoadUsers(e *gin.Engine) {e.GET("/user",MiddleWareOne(),UserHandler)
}func UserHandler(c *gin.Context) {fmt.Println("我是用戶路由")time.Sleep(time.Second * 5)c.JSON(http.StatusOK,gin.H{"message" : "weclome user",})
}
routers/books.go
package routers
import (
"net/http"
"github.com/gin-gonic/gin"
)
func LoadBooks(e *gin.Engine) {e.GET("/book", GetBookHandler)
}
func GetBookHandler(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "Book Router",})
}
中間件
- Gin框架允許開發者在處理請求的過程中,加入用戶自己的鉤子(Hook)函數。
- 這個鉤子函數就叫中間件,中間件適合處理一些公共的業務邏輯
- 比如登錄認證、權限校驗、數據分頁、記錄日志、耗時統計等
全局中間件
func Logger() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()c.Next()latency := time.Since(start)fmt.Printf("請求耗時: %v\n", latency)}
}func main() {r := gin.Default()r.Use(Logger()) // 全局生效r.GET("/", func(c *gin.Context) { /* ... */ })
}
局部中間件
func AuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {token := c.GetHeader("token")if token != "SECRET_KEY" {c.AbortWithStatusJSON(401, gin.H{"error": "身份驗證失敗"})}c.Next()}
}r.GET("/profile", AuthMiddleware(), func(c *gin.Context) {c.JSON(200, gin.H{"data": "用戶信息"})
})
next()方法
- 在中間件中調用next()方法,會從next()方法調用的地方跳轉到Handler函數
- Handler函數執行完成,若中間件還有部分代碼未執行(中間件中next()之后的代碼),則執行該代碼
package mainimport ("fmt""github.com/gin-gonic/gin""net/http""time"
)func main() {r := gin.Default()r.Use(Log(), RequestID())r.GET("/", func(c *gin.Context) {fmt.Println("app running 1")time.Sleep(time.Second * 5)c.String(http.StatusOK, "hello World!")})r.Run()
}
func Log() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("log start")c.Next()fmt.Println("log end")}
}
func RequestID() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("requestid start")c.Next()fmt.Println("requestid end")}
}
實現token認證
- http://127.0.0.1:8080/index index首頁無需token直接訪問
- http://127.0.0.1:8080/home home家目錄需要對token進行驗證,驗證通過才可訪問
package mainimport ("fmt""github.com/gin-gonic/gin"
)func AuthMiddleWare() func(c *gin.Context) {return func(c *gin.Context){// 客戶端攜帶token 有三種方式 1.放在請求頭 2.放在請求體 3.放在url// token 驗證成功,返回 c.next()才會繼續,否則 c.Abort()token := c.Request.Header.Get("token")fmt.Println("獲取token:",token)if token == "" {c.JSON(200, gin.H{"code": 401,"msg": "身份驗證不通過",})c.Abort()}if token != "123456" {c.JSON(200, gin.H{"code": 401,"msg": "token錯誤",})c.Abort()}}
}func main() {router := gin.Default()// 首頁無需驗證router.GET("/", func(c *gin.Context) {c.JSON(200, gin.H{"message": "首頁",})})// Home頁面需要驗證router.GET("/home", AuthMiddleWare(), func(c *gin.Context) {c.JSON(200, gin.H{"message": "Home頁面",})})router.Run(":8081")
}