使用 gin 搭建 api 後臺系統之框架搭建

gin 是 golang 中比較流行的框架,很多系統都是在該框架下開發的,這個框架給我的感覺像是 Flask 在 python 中的位置,基礎的功能都有,但是如果想要很好的使用,還需要開發很多自己的功能與中間件,在看過不少的教程以後,想要記錄一下學習過程中遇到的各種問題。

本系列簡單的實現了一些做後臺服務的能用方法,如獲取參數,數據庫查詢等操作,項目的整體還談不上架構,頂多算是個 quick start, 目錄結構也不那麼講究,因爲初學,所以肯定會有很多問題。以後再一點點地實踐一點點地修改吧。

下載 gin

第一篇,簡單一些,先把框架搭起來,寫個 hello world 出來。

項目基於 go module 方式,在一個空目錄中運行 go mod init gintest  初始化項目,go 會自動生成一個 go.mod 文件

在 go.mod 文件中添加 gin

module gintest

go 1.15

require (
 github.com/gin-gonic/gin v1.7.4
)

之後使用go mod download 命令下載。

或者在項目目錄中直接運行 go get github.com/gin-gonic/gin@v1.7.4 命令,也會下載 gin 包,並且會自動添加到 go.mod 文件中。

構建 http 服務

新建一個 server.go 文件,寫入以下代碼

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "Msg":"Hello World",
        })
    })
    r.Run(":8080")
}

代碼很簡單,運行go run server.go 則會在 8080 端口啓動一個 http 服務,使用瀏覽器訪問 http://127.0.0.1:8080/ 則會返回 json 的數據

{
  Msg: "Hello world"
}

上面代碼中,使用gin.Default() 創建一個默認的 gin 應用,這個應用底層初始化了很多設置,這裏就先不深入探索了。

這裏就註冊了一個路由,/,它的處理由一個匿名函數來處理, gin 中的 GET 方法的定義爲

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
 return group.handle(http.MethodGet, relativePath, handlers)
}

handler 爲處理方法,該方法的定義爲

type HandlerFunc func(*Context)

所以這裏需要一個參數爲 (*Context) 的函數,上面的代碼中使用

func(c *gin.Context) {
  c.JSON(200, gin.H{
    "Msg":"Hello World",
  })
}

這個匿名函數來處理 /  的 GET 請求。

這裏還可以定義 POST、DELETE、PUT 等方法,這樣可以很方便的編寫 restful 風格的 API,

r.POST("/", func(c *gin.Context) {
  c.JSON(200, gin.H{
    "Msg":"Hello world POST",
  })
})

404 配置

默認的 404 處理,會打印 404 not found,更多的時候,我們想自定義 404 頁面的處理,這時可以通過設置r.NoRoute 方法來實現

r.NoRoute(func(c *gin.Context) {
  c.JSON(404, gin.H{
    "Msg""The page not found",
  })
})

這時在訪問一個不存在的路由時會展示自定義的內容。

路由組

很多時候,我們是有這樣的需求,訪問的路由前面都有統一的前綴,如/api/v1 , 或者/user ,當然可以在定義路由的時候都統一寫上前綴,但是這種方式,如果後期修改了前綴將要改好多地方。這裏可以使用路由組的概念。

func main() {
 r := gin.Default()
 r.GET("/", func(c *gin.Context) {
  c.JSON(200, gin.H{
   "Msg""Hello world",
  })
 })
 apiv1 := r.Group("/api/v1")
 {
  apiv1.GET("/", func(c *gin.Context) {
   c.JSON(200, gin.H{"msg""hello api v1"})
  })
 }
 r.Run(":8080")
}

以上代碼,定義了一個路由組,/api/v1  , 之後又會在該路由組下定義相應的處理方法,這裏區分 http://127.0.0.1:8080/ 這個 url 返回 "Msg": "Hello world",  /api/v1/ 這個路由返回 "msg": "hello api v1"

處理類的封裝

上面的路由的處理類都是使用匿名函數,處理的邏輯比較簡單的話,可以這麼寫,但是還是不建議寫個匿名函數來處理,之後隨着項目越來越大,這裏的路由定義會越來越多,如果還有大量的匿名函數的話,看着就會比較亂。

這裏我們可以將處理 handler 進行封裝。

先創建一個 handlers 的文件夾,寫入以下代碼

package handlers

import "github.com/gin-gonic/gin"

type ApiV1 struct {
 
}

func (ApiV1) Get(c *gin.Context)  {
 c.JSON(200, gin.H{"msg""handlers get request!"})
}

這裏定義了一個 ApiV1 的結構體, 之後定義了一個 Get 方法,然後在入口 server.go 文件中,修改原來的代碼,

func main() {
 r := gin.Default()
 apiv1_h := handlers.ApiV1{}
 r.GET("/", func(c *gin.Context) {
  c.JSON(200, gin.H{
   "Msg""Hello world",
  })
 })
 apiv1 := r.Group("/api/v1")
 {
  apiv1.GET("/", apiv1_h.Get)
 }
 r.Run(":8080")
}

使用apiv1_h := handlers.ApiV1{} 初始化一個 ApiV1 的結構體對象,在之後路由定義的時候,就可以直接使用該對象的 Get 方法,最好將相同前綴的放入一個括號中{}, 方法查看

{
  apiv1.GET("/", apiv1_h.Get)
}

這裏注意,處理 handler 爲 apiv1_h.Get , 而不是 apiv1_h.Get(c, *gin.Context)

使用 http.Server 啓動服務

上面的代碼都是以r.run(":8080") 的方式啓動服務,但是有時候我們希望做一些個性化的設置,如一些讀寫超時,當然也可以調用gin.New() 方法中得到的對象中進行設置,也可以直接使用http.Server 中定義。

func main() {
 r := gin.Default()
 apiv1_h := handlers.ApiV1{}
 apiv1 := r.Group("/api/v1")
 {
  apiv1.GET("/", apiv1_h.Get)
 }
  s := &http.Server{
  Addr: ":8080",
  Handler: r,
  ReadTimeout: 10*time.Second,
  WriteTimeout: 10*time.Second,
 }
  err := s.ListenAndServe()
 if err != nil {
  return 
 }
}

網頁跳轉

上面的代碼在處理請求的時候,都是返回 json 數據,由於現在主流開發使用前後端分離的模式,所以這裏不太使用 html 模板的方式返回數據了。

當使用第三方登錄的時候,全牽扯到網頁跳轉,網頁跳轉也非常簡單, 修改 apiv1.go 文件

package handlers

import "github.com/gin-gonic/gin"

type ApiV1 struct {
 
}

func (*ApiV1) Get(c *gin.Context)  {
 c.Redirect(301, "https://www.baidu.com")
}

這樣在方法的對應的路由的時候,就會跳轉到百度了。

這裏要注意,狀態碼必須處理 300-308 之間,否則會報錯

func (r Redirect) Render(w http.ResponseWriter) error {
 if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {
  panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
 }
 http.Redirect(w, r.Request, r.Location, r.Code)
 return nil
}

gin 框架的基本搭建先記錄到這裏,之後會介紹各種請求參數的獲取與校驗。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/fqNvwrjK08nU2oO7qwQesA