GO系列
1、GO學習之Hello World
2、GO學習之入門語法
3、GO學習之切片操作
4、GO學習之 Map 操作
5、GO學習之 結構體 操作
6、GO學習之 通道(Channel)
7、GO學習之 多線程(goroutine)
8、GO學習之 函數(Function)
9、GO學習之 接口(Interface)
10、GO學習之 網絡通信(Net/Http)
11、GO學習之 微框架(Gin)
文章目錄
- GO系列
- 前言
- 一、Gin 簡介
- 二、Gin 框架搭建
- 2.1 安裝 Gin
- 2.2 簡單示例
- 三、路由和處理函數
- 3.1 什么是路由?
- 3.2 Gin 框架中的路由及案例
- 3.3 實際開發中的應用及優缺點
- 四、請求和響應處理
- 4.1 接受參數
- 4.2 處理請求
- 4.3 生成響應
- 五、中間件
- 5.1 什么是中間件(Middleware)?
- 5.2 中間件的作用及案例
- 5.2.1 全局中間件
- 5.2.2 局部中間件
- 5.3 中間件的優缺點
- 六、錯誤處理
- 6.1 上下文存儲異常信息
- 6.2 獲取異常信息
- 七、總結
前言
按照公司目前的任務,go 學習是必經之路了,雖然行業卷,不過技多不壓身,依舊努力!!!
說起微服務框架,首當其沖的就是 JAVA 技術棧的 Spring Cloud 全家桶,吃過的人都說好,比較 Spring Cloud 已經可以說是完全能滿足互聯網后端開發需求,用 Spring Cloud 來搭建一個三高(高并發、高可用、高性能)的后端架構并非難事。
Spring Cloud:Zuul(路由)、Gateway(路由)、Eureka(注冊中心)、Hystrix(熔斷限流)、Config(配置中心)、Bus(事件,消息總線)、Sleuth(日志收集,鏈路追蹤)、Ribbon(負載均衡)、OpenFeign(API 調用) 等。
Spring Cloud Alibaba:Nacos(注冊配置中心)、Sentinel(限流降級)、Seata(分布式事務)、Stream(分布式消息)等。
那 Go 語言自然也會包容微服務的思想,Gin 就是 Go 語言中的一個微服務框架,是一個輕量級的、高性能的 Web 框架,為專門構建快速的 Web 應用和 API 而設計。
一、Gin 簡介
- Gin 是一個輕量級的、高性能的 Web 框架,專門為構建快速的 Web 應用和 API 而設計。它是基于 Go 語言的標準庫,提供了簡單易用的 API 和許多有用的功能,開發者可以快速地構建和部署 Web 服務。
- 強大的路由和中間件支持,Gin 提供了靈活的路由定義和中間件機制,可以方便的處理各種負責的邏輯。
- 適用于構建 RESTful API,Gin 提供了 JSON 構建和解析響應功能,非常適合構建 RESTful 風格 API。
當然也有不足之處:
- 功能相對簡化,相比一些成熟的 Web 框架,Gin 在一些方面較為簡化,如模板渲染等。
- 社區相對較小,雖然 Gin 在國內擁有一定的用戶群體,但是相對 Spring 等其他 Web 框架,社區還是相對較小的。
二、Gin 框架搭建
在使用 Gin 框架之前,首先需要安裝 Gin 包,就類似 JAVA 中如果要用到 web 模塊就需要映入 starter-web 模塊。
2.1 安裝 Gin
通過如下命令來安裝:
go get github.com/gin-gonic/gin
2.2 簡單示例
下面是一個基于 Gin 寫的一個 簡單的 API 示例。
示例中,首先導入 “github.com/gin-gonic/gin” 包,并使用 gin.Default() 來創建一個默認的 Gin 引擎 router。然后使用 router 來定義了一個 路由,使用匿名函數來處理響應,返回了一個 Hello Gin 的相應字符串。最后使用 router.Run() 啟動這個 HTTP 服務。
package mainimport ("net/http""github.com/gin-gonic/gin"
)func main() {// 創建一個 Gin 引擎router := gin.Default()// 定義路由和處理函數,這里使用匿名函數router.GET("/", func(ctx *gin.Context) {ctx.String(http.StatusOK, "Hello Gin!!!")})// 啟動 HTTP 服務器router.Run("127.0.0.1:8080")
}
我們使用 go run hello.go 來運行,瀏覽器訪問測試:
三、路由和處理函數
3.1 什么是路由?
路由是指根據客戶端請求的 URL 路徑,將請求映射到相應的處理函數上,這里對比 SpringMVC 框架的 DispatcherServlet 控制中心。在 Web 應用中,當用戶訪問不同的 URL 路徑時,應用程序就需要調用相對于的處理函數來處理用戶的請求,每個請求的處理函數都有不同的業務邏輯,
路由就會幫你將不同的請求分配到正確的處理函數上
。
在 Gin 框架中,你可以使用不同的 HTTP 方法(GET, POST, PUT, DELETE等)來定義不同的路由,并且為每一個路由指定一個處理函數。
3.2 Gin 框架中的路由及案例
Gin 框架中采用的路由是基于 httprouter 做的。
下面來看一個小小的示例:
下面示例中,通過 Gin 框架定義了 /hello GET 接口和 /add POST 接口,并且從請求體中獲取 name 參數。
我來來看,一個包中,我們可以定義不同路由已完成不同的請求操作。
package mainimport ("fmt""net/http""github.com/gin-gonic/gin"
)func main() {// 創建一個 Gin 引擎router := gin.Default()// 定義路由和處理函數,這里使用匿名函數router.GET("/hello", func(ctx *gin.Context) {// 從請求體中查詢 name 參數name := ctx.Query("name")fmt.Printf("接收到參數:%s", name)ctx.String(http.StatusOK, "Hello Gin! %s", name)})router.POST("/add", func(ctx *gin.Context) {var requestData struct {Name string "json:name binding:required"}// 使用 ShouldBindJSON 方法將請求體中的 JSON 數據綁定到結構體中if err := ctx.ShouldBindJSON(&requestData); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"error:": err.Error()})return}// 從結構體中獲取參數name := requestData.Nameif len(name) == 0 {fmt.Println("獲取name參數失敗!")ctx.String(http.StatusBadRequest, "add fail!")} else {fmt.Printf("添加操作:%+v 完成\n", name)ctx.String(http.StatusOK, "%s add successful!", name)}})// 啟動 HTTP 服務器router.Run("127.0.0.1:8080")
}
我們 Postman 來請求測試:
/hello GET 接口:
/add POST 請求:
3.3 實際開發中的應用及優缺點
在實際開發中,路由和函數處理運用非常廣泛,以下是一些例子:
- 頁面訪問:給頁面提供不同的 API 接口以供頁面獲取數據渲染,當用戶訪問不同的頁面或資源時,通過 URL 統一資源定位符 來調用不同的路由獲取資源。
- API 路由:除了前端頁面,其他客戶端比如:小程序、APP、給第三方系統提供接口等。
優點:
- 組織結構清晰:路由可以將不同的請求和處理函數組織起來,以便代碼更加清晰。
- 模塊化開發:可以實現模塊化開發,每個處理函數對應每個路由處理不同的業務請求。
- 靈活性:路由可以通過不同的 URL 路徑調用處理不同的處理函數,實現靈活處理請求。
缺點:
- 路由管理:如果規模龐大,路由管理可能變的復雜,需要合理地組織維護。
- 路由沖突:在定義路由時,如果定義了相同的路由,可能會導致路由沖突。
- 可讀性:如果路由過多或者命名不清晰,可能會降低代碼的可讀性。
四、請求和響應處理
在 Gin 框架中,請求和響應處理是開發 Web 應用的核心部分,也是程序猿日常做的最多的事情。
每開發一個API,逃不過這幾步驟:
4.1 接受參數
- 獲取 Request Header 參數
token := ctx.GetHeader("token")
- 獲取 Request Path 參數
id := ctx.Param("id")
- 獲取 Request Param 參數
name := ctx.Query("name")
- 獲取 Request Body 參數
在獲取 RequestBody 參數是,我們需要封裝結構體,然后綁定請求體到結構體中。
var requestData struct {Name string "json:name binding:required"
}
// 使用 ShouldBindJSON 方法將請求體中的 JSON 數據綁定到結構體中
if err := ctx.ShouldBindJSON(&requestData); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"error:": err.Error()})return
}
// 從結構體中獲取參數
name := requestData.Name
4.2 處理請求
在路由處理函數中,你可以執行你想要的處理邏輯。例如,你可以查詢數據庫、調用其他函數、處理數據等等。處理請求的邏輯在路由處理函數中實現,聲明處理函數或者使用匿名函數來處理。
4.3 生成響應
目前大多數的 Web 應用開發或者是系統之間相互調用、傳參、接受數據等大多都是 JSON 格式數據,那在 SpringMVC 框架中有做封裝,那在 Gin 框架中也是如此。
下面的案例中,我們通過
ctx.JSON()
方法向前端以 json 格式把數據返回,你可以將響應的數據封裝成一個gin.H(類似 map)
對象,然后使用ctx.JSON
方法將其作為響應的主體返回給客戶端。
注意
: 這里碰到一個小坑,忘記了 Go 語法中,小寫是沒有導出權限的,所以導致 postman 訪問,data 中總是 [] 的,詢問 大佬(chatGPT)才知道其中之奧妙,需大寫。
package mainimport ("net/http""github.com/gin-gonic/gin"
)func main() {// 創建一個 Gin 引擎router := gin.Default()// 創建一個 Get 請求router.GET("/test/:id", func(ctx *gin.Context) {id := ctx.Param("id")name := ctx.Query("name")data := struct {// 注意,這里定義字段一定要大寫,要不然則不能導出,返回空Id interface{} `json:"id"`Name string `json:"name"`}{Id: id,Name: name,}ctx.JSON(http.StatusOK, gin.H{"code": 200, "message": "請求成功", "data": data})})// 啟動服務router.Run(":8080")
}
在 postma 中測試
調用:http://127.0.01:8080/test/10?name=phen
響應:
{"code": 200,"data": {"id": "10","name": "phen"},"message": "請求成功"
}
當然,如果需要返回其他類型的響應,比如 HTML頁面、純文本等,可以使用
ctx.HTML
、ctx.String
等方法,根據需要,設置適當的狀態碼和響應頭。
五、中間件
5.1 什么是中間件(Middleware)?
- 中間件(
Middleware
)是一種常見的軟件設計模式,在 Web 開發中用于請求和響應之間添加自定義邏輯。 - 中間件可以在請求到達處理函數之前或者在響應客戶端之前,對請求和響應做處理。
- 在 Gin 框架中,中間件是一種非常重要的概念,用于處理一些通用的操作。
- 如果知道
SpringMVC
框架的話,就會知道Filter 過濾器
,Filter 有 Pre-proces 和 Post-process 操作,Gin 框架的中間件和 Filter 差不多。
5.2 中間件的作用及案例
中間件的作用對于請求和響應的進行預處理、后處理和記錄,以便實現如下功能(不限于):
- 身份驗證和授權:中間件中進行用戶身份驗證和授權,確保用戶能夠訪問特定資源。
- 日志記錄:可以在中間件中記錄 PV、用戶操作記錄等便于監控和排錯。
- 請求參數驗證:在中間件中可以進行參數驗證、XSS 攻擊過濾等,確保用戶提交的訂單數據合法。
- 緩存和性能優化:可以在中間件中進行熱數據的緩存,減少后端數據庫的壓力。
- 防爬機制:可以在中間件中對請求參數等進行校驗,以防別人通過接口爬數據。
- 請求耗時統計:中間件中記錄請求耗時,以便性能分析。
如何使用中間件呢:
5.2.1 全局中間件
在上面的案例中加入了中間件的使用,在下面的案例中,定義了一個
Logger()
函數來記錄請求日志,在 Logger() 函數中,首先記錄請求請求進入時間,調用Next()
函數 繼續處理請求 最后記錄結束記錄時間和耗時。
package mainimport ("fmt""log""net/http""time""github.com/gin-gonic/gin"
)// 定義中間件處理函數,用戶記錄日志
func Logger() gin.HandlerFunc {// 使用自定義函數來處理return func(ctx *gin.Context) {fmt.Println("Middleware Logger Handler...")start := time.Now()// 調用 Next 繼續處理請求ctx.Next()end := time.Now()latency := end.Sub(start)log.Printf("[%s] 請求方法:%s 請求路徑:%s 耗時:%v", end.Format("2006-01-02 15:04:05"), ctx.Request.Method, ctx.Request.URL.Path, latency)}
}func main() {// 創建一個 Gin 引擎router := gin.Default()// 添加自定義的 Logger 中間件router.Use(Logger())// 定義路由和處理函數,這里使用匿名函數router.GET("/hello", func(ctx *gin.Context) {// 從請求體中查詢 name 參數name := ctx.Query("name")fmt.Printf("請求處理...接收到參數:%s \n", name)ctx.String(http.StatusOK, "Hello Gin! %s", name)})router.POST("/add", func(ctx *gin.Context) {var requestData struct {Name string "json:name binding:required"}// 使用 ShouldBindJSON 方法將請求體中的 JSON 數據綁定到結構體中if err := ctx.ShouldBindJSON(&requestData); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"error:": err.Error()})return}// 從結構體中獲取參數name := requestData.Nameif len(name) == 0 {fmt.Println("獲取name參數失敗!")ctx.String(http.StatusBadRequest, "add fail!")} else {fmt.Printf("添加操作:%+v 完成\n", name)ctx.String(http.StatusOK, "%s add successful!", name)}})// 啟動 HTTP 服務器router.Run("127.0.0.1:8080")
}
通過 Postman 請求測試:
從上面的截圖中可以看處,每次請求都調用到了中間件 Logger()。
5.2.2 局部中間件
上面是全局中間件,所有請求都會進入中間件,但是有時候并非所有請求都需要中間件處理,這時候就需要某幾個特殊的 API 接口實現中間件即可。
package mainimport ("errors""fmt""log""net/http""github.com/gin-gonic/gin"
)// 定義局部中間件函數,檢查用戶權限
func checkAuth() gin.HandlerFunc {return func(ctx *gin.Context) {token := ctx.Query("token")if len(token) == 0 {ctx.Error(errors.New("無權訪問"))ctx.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "無權訪問"})} else {log.Printf("請求方法:%s 請求路徑:%s 正常訪問", ctx.Request.Method, ctx.Request.URL.Path)}}
}func main() {// 創建一個 Gin 引擎router := gin.Default()router.GET("/list", checkAuth(), func(ctx *gin.Context) {errors := ctx.Errorsif errors != nil {ctx.Errors = nilreturn}data := struct {// 注意,這里定義字段一定要大寫,要不然則不能導出,返回空Id interface{} `json:"id"`Name string `json:"name"`}{Id: 1,Name: "phen",}fmt.Println("data: ", data)ctx.JSON(http.StatusOK, gin.H{"code": 200, "message": "請求成功", "data": data})})// 啟動 HTTP 服務器router.Run("127.0.0.1:8080")
}
通過瀏覽器訪問測試:
訪問成功:
無權訪問:
從兩張截圖中可以看出,有 token 則訪問成功,沒 token 則人訪問失敗!
注意、注意、注意:
這里遇到一個問題,第一次測試是我的代碼中沒有 ctx.Errors = nil
這句代碼,就發現如下情況:
- 第一次訪問不帶 token 參數,自然無權訪問
- 第二次訪問帶上 token, 沒有無權訪問,但是也沒有訪問成功結果
- 為啥呢?chatGPT 告訴我
因為 Gin 框架上下文在一個請求中是共享的,所以在同一個請求的不同處理函數中,上下文中的數據和異常信息是可以共享的。
這種設計有助于在請求的不同處理階段中傳遞數據和異常信息,但也需要小心處理,以確保不會導致意外的結果。如果你想要在每次請求中使用一個干凈的上下文,可以在每個請求處理函數的開頭重新初始化上下文,或者在處理函數之間隔離上下文。
如果你不希望在不同請求之間共享異常信息,你可以在每次請求開始時,將上下文清空或初始化,以確保上下文不會受到之前請求的影響。
所以我加入了這句代碼 ctx.Errors = nil
,每次訪問過之后清楚調異常信息,則每次訪問的時候,帶 token 則訪問成功,不帶則無權訪問,這樣的處理對于新手的我來說應該不是很合理,后面找大佬咨詢,或者哪位大佬看到了,評論區指導一下
。
5.3 中間件的優缺點
有點:
- 代碼復用:中間件可以通過將公共的代碼邏輯提取出來,進行封裝,實現代碼的復用,避免相似邏輯和重復代碼。
- 邏輯解耦:中間件可以將請求和處理邏輯解耦,是的代碼更加清晰、模塊化和易于維護。
- 靈活性:中間件可以在不同的路由器上使用,在上面的案例中,/hello GET 和 /add POST 路由都使用了 Logger() 中間件。
缺點:
- 復雜性:通過中間件處理,可能會增加業務的復雜性,不當使用會導致業務代碼難以理解。
- 性能影響:每次請求都需要經過中間件,這樣中間件中邏輯如果過于復雜,可能影響請求API性能。
六、錯誤處理
Gin 框架中提供了一種簡便的方式來處理函數中發生的異常,確保錯誤能夠捕獲并且正確的返回給前端。
6.1 上下文存儲異常信息
在下面的案例中,定義了一個 failFunc() 函數,此函數的作用就是返回一個異常,當在 Get() 方法中獲取到的 err 時,則用
ctx.Error(err)
將錯誤信息存儲在 Gin 上下文中。這樣,Gin 框架會在請求處理完畢后,檢查是否有存儲的異常信息,如果有則把錯誤信息返回給前端。
package mainimport ("errors""net/http""github.com/gin-gonic/gin"
)func failFunc() error {return errors.New("運行異常")
}func main() {// 創建一個 Gin 引擎router := gin.Default()// 創建一個 Get 請求router.GET("/test/:id", func(ctx *gin.Context) {err := failFunc()if err != nil {// 將錯誤信息存儲在 Gin 上下文中ctx.Error(err)ctx.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "服務器異常"})return}ctx.JSON(http.StatusOK, gin.H{"code": 200, "message": "請求成功"})})// 啟動服務router.Run("127.0.0.1:8080")
}
通過 postman 調用測試:
請求:http://127.0.01:8080/test/10?name=phen
響應:
{"code": 500,"message": "服務器異常"
}
不過我發現,如果在出現異常是,也就是 err 不為空是,里面的 return
不要,則后面所有的 ctx.JSON、ctx.String等,都會成功放回,所以請求結果會如下:
請求:http://127.0.01:8080/test/10?name=phen
響應:
{"code": 500,"message": "服務器異常"
}{"code": 200,"message": "請求成功"
}請求結束
這一點不會像 Spring框架中,response 過之后,如果再 response 則會報錯。
總之,Gin 框架通過在 Gin 上下文中存儲錯誤信息的方式,提供了方法處理錯誤的機制,使得處理函數中的錯誤被捕獲并且能夠返回給前端。
這樣有助于用戶體驗和調試信息。
6.2 獲取異常信息
而也可以通過
ctx.Errors
獲取到所有運行中的異常信息
fmt.Println("運行時的所有異常:", ctx.Errors)
只要在 return 前通過 Errors() 方法獲取存儲在 Gin 中的異常信息,就可以拿到所有的運行過程中的異常,以便業務邏輯的判斷和處理。
七、總結
什么是微框架(Gin),Gin 框架是一個輕量級的,高性能的 Web 框架,專門為構架快速的 Web 應用和 API 而設計。它是基于 Go 語言的標準庫開發,提供簡單易用的 API 和其他更多的功能,開發者可以快速的構建出 Web 應用并且部署。
通過 go get github.com/gin-gonic/gin 來安裝 Gin 框架到開發者環境,通過 框架提供的 路由和處理函數來快速開發 API,接受參數通過函數處理業務邏輯,然后通過 Gin 框架提供的 JSON、String、HTML等方法返回給前端不同類型數據。
通過中間件來處理公共的業務邏輯,比如認證授權、日志記錄等。
也有異常處理機制,Gin 通過上下文存儲異常信息,然后把異常信息返回給前端來實現異常的處理機制,提高用戶體驗和調試信息。
最后,Gin 框架總算是有些入門了,后面繼續學習框架其他內容,然后博客分享。
現階段還是對 Go 語言的學習階段,想必有一些地方考慮的不全面,本文示例全部是親自手敲代碼并且執行通過。
如有問題,還請指教。
評論去告訴我哦!!!一起學習一起進步!!!