go go go 出發咯 - go web開發入門系列(二) Gin 框架實戰指南
往期回顧
- go go go 出發咯 - go web開發入門系列(一) helloworld
前言
前一節我們使用了go語言簡單的通過net/http
搭建了go web服務,但是僅使用 Go 的標準庫 net/http
來構建復雜的 Web 應用可能會有些繁瑣。這時,一個優秀的 Web 框架就顯得至關重要。Gin 就是其中最受歡迎的選擇之一。它是一個用 Go 編寫的高性能 Web 框架,以其極快的速度和豐富的功能集而聞名。
在本篇博客中,我們將帶你從零開始,一步步學習如何使用 Gin 框架搭建你的第一個 Web 應用。
為什么選擇 Gin?
在眾多 Go Web 框架中,Gin 脫穎而出,主要有以下幾個原因:
- 極致性能:Gin 的路由基于 Radix 樹,內存占用極小,性能表現卓越,是 Go 社區中最快的框架之一。
- 中間件支持:Gin 的中間件(Middleware)機制非常強大,可以將一系列可插拔的組件(如日志、授權、Gzip 壓縮)串聯起來,作用于請求處理的生命周期中。
- 錯誤管理:Gin 提供了一種便捷的方式來收集和處理 HTTP 請求期間發生的所有錯誤,讓你的應用更加健壯。
- JSON 驗證:Gin 可以輕松地解析和驗證請求中的 JSON 數據,這對于構建 RESTful API 至關重要。
- 路由組:通過路由組(Route Grouping),你可以更好地組織你的路由,例如將需要相同授權中間件的路由歸為一組。
- 上手簡單:Gin 的 API 設計非常直觀,學習曲線平緩,文檔齊全,對新手非常友好。
準備工作
在開始之前,請確保你已經完成了以下準備:
- 安裝 Go 環境:訪問 Go 官方網站 下載并安裝適合你操作系統的 Go 版本(建議 1.18 或更高版本,我使用的是1.24.4)。
- 配置你的工作區:設置好你的
GOPATH
和GOROOT
環境變量。 - 一個代碼編輯器:推薦使用 VS Code 并安裝 Go 擴展,或者使用 GoLand。
第一個 Gin 應用:“Hello, Gin!”
第一步:創建項目
首先,創建一個新的項目目錄,并使用 Go Modules 初始化項目。
如果是使用vscode的童鞋,參見以下代碼:
# 創建一個項目文件夾
mkdir gin-hello-world
cd gin-hello-world# 初始化 Go 模塊
go mod init gin-hello-world
`go mod init` 命令會創建一個 `go.mod` 文件,用于跟蹤和管理項目的依賴。
第二步:下載Gin 依賴
在當前項目目錄下,鍵入如下命令,安裝Gin依賴
go get -u github.com/gin-gonic/gin
如果你使用的是 Goland
存在拉取 Gin依賴失敗的情況,請配置GoProxy
GOPROXY=https://goproxy.cn,direct.
第二步:編寫main.go
main.go
package mainimport "github.com/gin-gonic/gin"func main() {// 1. 創建一個默認的 Gin 引擎r := gin.Default()// 2. 定義一個路由和處理函數// 當客戶端以 GET 方法請求 /hello 路徑時,執行后面的匿名函數r.GET("/hello", func(c *gin.Context) {// c.JSON 是一個輔助函數,可以序列化給定的結構體為 JSON 并寫入響應體c.JSON(200, gin.H{"message": "Hello, Gin!",})})// 3. 啟動 HTTP 服務器r.Run(":8888")
}
直接運行main.go,訪問localhost:8888/hello
或者使用請求工具
curl http://localhost:8888/hello
可以收到一個json的返回
{"message":"Hello, Gin!"}
Gin 核心概念深入
掌握了基礎之后,讓我們來探索 Gin 的一些核心功能。
1. 路由(Routing)
Gin 提供了非常靈活的路由功能。
路由參數
你可以定義包含動態參數的路由。
r.GET("/users/:name", func(c *gin.Context) {// 使用 c.Param() 獲取路由參數name := c.Param("name")c.String(200, "Hello, %s", name)
})
測試:訪問 http://localhost:8888/users/jerry
,你會看到 Hello, jerry
。
查詢字符串參數
獲取 URL 中的查詢參數(如 ?page=1&size=10
)。
r.GET("/articles", func(c *gin.Context) {// 使用 c.DefaultQuery() 獲取參數,如果不存在則使用默認值page := c.DefaultQuery("page", "1")// 使用 c.Query() 獲取參數size := c.Query("size") // 如果不存在,返回空字符串c.JSON(200, gin.H{"page": page,"size": size,})
})
測試:訪問 http://localhost:8888/articles?page=2&size=20
。
處理 POST 請求和數據綁定
構建 API 不可避免地要處理 POST 請求,通常是 JSON 格式的數據。Gin 的數據綁定功能可以極大地簡化這個過程。
首先,定義一個與 JSON 結構匹配的 Go 結構體:
// 定義一個 Article 結構體
type Article struct {Title string `json:"title" binding:"required"`Content string `json:"content" binding:"required"`
}
binding:"required"
標簽表示這個字段是必需的。
然后,創建一個處理 POST 請求的路由:
r.POST("/articles", func(c *gin.Context) {// 聲明一個 Article 類型的變量var article Article// 使用 ShouldBindJSON 將請求的 JSON body 綁定到 article 變量上// ShouldBindJSON 會在綁定失敗時返回錯誤,但不會中止請求if err := c.ShouldBindJSON(&article); err != nil {// 如果綁定失敗,返回一個 400 錯誤和錯誤信息c.JSON(400, gin.H{"error": err.Error()})return}// 綁定成功,返回一個 200 成功響應和接收到的數據c.JSON(200, gin.H{"status": "received","title": article.Title,"content": article.Content,})
})
使用 curl
或者 postman
來測試這個 POST 端點:
curl -X POST http://localhost:8888/articles \-H "Content-Type: application/json" \-d '{"title":"aaaa", "content":"123456789"}'
你會收到成功的響應。如果嘗試發送不完整的數據(例如缺少 title
),你會收到一個 400 錯誤。
2. 路由組 (Router Grouping)
當應用變大時,路由會變得復雜。路由組可以幫助你更好地組織代碼,并為一組路由共享中間件。
類似于 SpringBoot 中
@RequestMapping("/api/v1")
設置公共請求路徑。
func main() {r := gin.Default()// 創建一個 /api/v1 的路由組v1 := r.Group("/api/v1"){// 在 v1 組下定義路由v1.GET("/users", func(c *gin.Context) {c.JSON(200, gin.H{"users": []string{"Alice", "Bob", "Charlie"}})})v1.GET("/products", func(c *gin.Context) {c.JSON(200, gin.H{"products": []string{"Laptop", "Mouse"}})})}// 創建一個 /api/v2 的路由組v2 := r.Group("/api/v2"){v2.GET("/users", func(c *gin.Context) {c.JSON(200, gin.H{"message": "v2 users endpoint"})})}r.Run()
}
現在,你可以通過 /api/v1/users
和 /api/v2/users
訪問不同版本的 API。
3. 中間件 (Middleware)
中間件是 Gin 的精髓所在。它是一個在請求被處理之前或之后執行的函數。gin.Default()
就默認使用了 Logger 和 Recovery 中間件。
類似于SpringBoot aop切面實現的全局請求攔截器。只不過再go中被叫做中間件。
讓我們來創建一個自定義的日志中間件。
package mainimport ("log""time""github.com/gin-gonic/gin"
)// 自定義日志中間件
func LoggerMiddleware() gin.HandlerFunc {return func(c *gin.Context) {// 請求開始時間startTime := time.Now()// 調用 c.Next() 執行后續的處理函數c.Next()// 請求結束時間endTime := time.Now()// 計算執行時間latencyTime := endTime.Sub(startTime)// 獲取請求方法和路徑reqMethod := c.Request.MethodreqUri := c.Request.RequestURI// 獲取狀態碼statusCode := c.Writer.Status()// 獲取客戶端 IPclientIP := c.ClientIP()// 打印日志log.Printf("| %3d | %13v | %15s | %s | %s |",statusCode,latencyTime,clientIP,reqMethod,reqUri,)}
}func main() {r := gin.New() // 使用 gin.New() 創建一個沒有任何中間件的引擎// 全局使用我們的自定義日志中間件r.Use(LoggerMiddleware())// 使用 Gin 默認的 Recovery 中間件,防止 panic 導致程序崩潰r.Use(gin.Recovery())r.GET("/test-middleware", func(c *gin.Context) {c.JSON(200, gin.H{"message": "Middleware test successful!"})})r.Run()
}
這個中間件會記錄每個請求的狀態碼、耗時、IP、方法和路徑。你可以用它來替換 Gin 默認的 Logger,或者為特定路由組添加認證中間件。
關于使用r := gin.New() 的代碼解釋,
通過使用 gin.New() 創建一個沒有任何中間件的引擎,相比 gin.default()創建的更純凈,因為gin.default()自帶了兩個中間件:
gin.Logger()
: Gin 自帶的日志中間件,會在控制臺打印每條請求的日志。gin.Recovery()
: 一個恢復(Recovery)中間件,它能捕獲任何panic
,防止整個程序因此崩潰,并會返回一個500
錯誤。- 即 gin.default() 等價于 r := gin.New();r.Use(gin.Logger(), gin.Recovery())
所以為了體驗更純粹的gin并設置自定義的日志中間件,使用了gin.new(),當然也可以使用gin.default,只不過日志信息會有重疊
4. HTML 模板渲染
雖然 Gin 常用于構建 API,但它同樣能出色地渲染 HTML 頁面。
第一步:創建模板文件
在你的項目根目錄下創建一個 templates
文件夾,并在其中創建一個 index.html
文件。
templates/index.html
:
類似于java 中的Springboot thymeleaf 模板的方式,在模板之間進行傳遞參數進行渲染
<!DOCTYPE html>
<html>
<head><title>{{ .title }}</title>
</head>
<body><h1>Hello, {{ .name }}!</h1><p>Welcome to our website rendered by Gin.</p>
</body>
</html>
第二步:編寫 Go 代碼
修改 main.go
來加載并渲染這個模板。
package mainimport ("net/http""github.com/gin-gonic/gin"
)func main() {r := gin.Default()// 告訴 Gin 模板文件在哪里r.LoadHTMLGlob("templates/*")r.GET("/index", func(c *gin.Context) {// 使用 c.HTML 渲染模板// 我們傳遞一個 gin.H 對象,模板中可以通過 .key 的方式訪問數據c.HTML(http.StatusOK, "index.html", gin.H{"title": "Gin Template Rendering","name": "Guest",})})r.Run()
}
運行程序并訪問 http://localhost:8888/index
,你將看到一個動態渲染的 HTML 頁面。
5. 文件上傳
處理文件上傳是 Web 應用的常見需求。Gin 讓這個過程變得異常簡單。
第一步:更新 HTML 模板
在 templates
目錄下創建一個 upload.html
。
templates/upload.html
:
<!DOCTYPE html>
<html>
<head><title>File Upload</title>
</head>
<body><h2>Upload a File</h2><form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit" value="Upload"></form>
</body>
</html>
第二步:編寫后端處理邏輯
package mainimport ("fmt""net/http""github.com/gin-gonic/gin"
)func main() {r := gin.Default()r.LoadHTMLGlob("templates/*")// 為上傳文件設置一個較低的內存限制 (默認是 32 MiB)r.MaxMultipartMemory = 8 << 20 // 8 MiB// 提供上傳頁面的路由r.GET("/upload", func(c *gin.Context) {c.HTML(http.StatusOK, "upload.html", nil)})// 處理文件上傳的路由r.POST("/upload", func(c *gin.Context) {// 從表單中獲取文件file, err := c.FormFile("file")if err != nil {c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))return}// 將上傳的文件保存到服務器上的指定位置// 這里我們保存在項目根目錄下的 "uploads/" 文件夾中// 請確保你已經手動創建了 "uploads" 文件夾dst := "./uploads/" + file.Filenameif err := c.SaveUploadedFile(file, dst); err != nil {c.String(http.StatusInternalServerError, fmt.Sprintf("upload file err: %s", err.Error()))return}c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded successfully!", file.Filename))})r.Run()
}
在運行前,請務必在項目根目錄手動創建一個名為 uploads
的文件夾。然后運行程序,訪問 http://localhost:8888/upload
,你就可以選擇并上傳文件了。
6. 重定向 (Redirection)
HTTP 重定向也是一個常用功能。Gin 使用 c.Redirect()
方法來處理。
這個方法接受兩個參數:
- HTTP 狀態碼:常用的有
http.StatusMovedPermanently
(301, 永久重定向) 和http.StatusFound
(302, 臨時重定向)。 - 目標 URL:你想要重定向到的地址,可以是相對路徑或絕對路徑。
package mainimport ("net/http""github.com/gin-gonic/gin"
)func main() {r := gin.Default()// 示例1: 臨時重定向// 訪問 /test-redirect 會被重定向到外部網站r.GET("/test-redirect", func(c *gin.Context) {c.Redirect(http.StatusFound, "https://www.google.com")})// 示例2: 永久重定向內部路由// 訪問 /old-path 會被永久重定向到 /new-pathr.GET("/old-path", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "/new-path")})r.GET("/new-path", func(c *gin.Context) {c.JSON(200, gin.H{"message": "Welcome to the new path!"})})r.Run()
}
當你訪問 http://localhost:8888/test-redirect
時,你的瀏覽器地址欄會變成 https://www.google.com
。當你訪問 http://localhost:8888/old-path
時,瀏覽器會跳轉到 http://localhost:8888/new-path
并顯示 JSON 消息。
總結
通過這篇終極指南,我們系統地學習了如何使用 Gin 框架構建一個功能完善的 Web 應用。我們從基礎的 “Hello, World” 出發,深入探索了路由、數據綁定、路由組、自定義中間件、HTML 模板渲染、文件上傳和重定向等一系列核心功能。
這已經為你打下了堅實的基礎。接下來,你可以繼續探索更高級的主題,例如:
- 與數據庫集成:將你的 Gin 應用連接到 MySQL、PostgreSQL 或 GORM。
- WebSocket 支持:構建實時通信應用。
- 部署:將你的 Gin 應用打包成 Docker 鏡像并部署到服務器。
希望這篇博客能為你打開 Go Web 開發的大門。Gin 是一個強大而有趣的工具,現在就開始用它來構建你的下一個項目吧!
有用的鏈接:
- Gin 官方文檔
- Go 官方網站