基於Gin框架的web後端開發(十): Gin框架-中介軟體(定義、使用、通訊與例項)詳解

LiberHome 發表於 2022-06-17
框架 中介軟體

Gin-中介軟體:Gin框架的作者為開發者們提供的一種機制,可以讓開發者自定義請求執行的Hook函式,中介軟體函式(或者叫Hook函式、鉤子函式)適合處理很多重複的操作的場景(比如登入認證,許可權校驗,資料分頁,耗時統計,記錄日誌等) ,如果僅僅是一個頁面的獨有的邏輯直接放到對應的路由下的Handler函式處理即可,


定義中介軟體

Gin的中介軟體必須是gin.HandlerFunc型別(就是包含請求上下文引數*gin.Context的函式)

例如:下面的indexHandler就是一箇中介軟體:

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func indexHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}
func main() {
    r := gin.Default()
    r.GET("/index", indexHandler)
    r.Run(":9090")
}

再比如,我們還可以這樣定義一箇中介軟體:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定義一箇中介軟體m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
}
func main() {
    r := gin.Default()
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", m1, indexHandler)
    r.Run(":9090")
}

c.Next()

但上面這樣寫中介軟體並沒有什麼實際意義,我們可以嘗試寫一個程式計時的功能,這裡介紹一下,c.Next()可以呼叫後續的處理函式:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定義一箇中介軟體m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
    start := time.Now()
    c.Next() //呼叫後續的處理函式
    //c.Abort() //組織呼叫後續的處理函式
    cost := time.Since(start)
    fmt.Printf("cost:  %v\n ", cost)
    fmt.Println("m1 is done")
}
func main() {
    r := gin.Default()
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", m1, indexHandler)
    r.Run(":9090")
}

執行結果如下:
基於Gin框架的web後端開發(十): Gin框架-中介軟體(定義、使用、通訊與例項)詳解


r.Use()

當然,中介軟體的設計初衷是為了更多的複用,比如使用Use(middle_ware...)函式進行全域性註冊,比如,我有幾個路由,想在訪問這幾個路由的時候都呼叫m1計時器的功能:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定義一箇中介軟體m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
    start := time.Now()
    c.Next() //呼叫後續的處理函式
    //c.Abort() //組織呼叫後續的處理函式
    cost := time.Since(start)
    fmt.Printf("cost:  %v\n ", cost)
    fmt.Println("m1 is done")
}
func main() {
    r := gin.Default()
    //全域性註冊中介軟體
    r.Use(m1)
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", indexHandler)
    r.GET("/game", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "shop",
        })
    })
    r.GET("/food", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "food",
        })
    })
    r.GET("/ad", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "ad",
        })
    })
    r.Run(":9090")
}

執行結果如下:
基於Gin框架的web後端開發(十): Gin框架-中介軟體(定義、使用、通訊與例項)詳解

c.Abort()

這裡介紹一下,c.Abort()可以剝奪所有後續的處理函式執行的權利,直接跳過去,例子如下:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定義一箇中介軟體m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
    start := time.Now()
    c.Next() //呼叫後續的處理函式
    //c.Abort() //組織呼叫後續的處理函式
    cost := time.Since(start)
    fmt.Printf("cost:  %v\n ", cost)
    fmt.Println("m1 is done")
}

//定義一箇中介軟體m2
func m2(c *gin.Context) {
    fmt.Println("this is middle-ware m2~")
    c.Abort() //組織呼叫後續的處理函式
    fmt.Println("m2 is done")
}

func main() {
    r := gin.Default()
    //全域性註冊中介軟體
    r.Use(m1, m2)
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", indexHandler)
    r.GET("/game", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "shop",
        })
    })
    r.GET("/food", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "food",
        })
    })
    r.GET("/ad", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "ad",
        })
    })
    r.Run(":9090")
}

執行結果如下:
基於Gin框架的web後端開發(十): Gin框架-中介軟體(定義、使用、通訊與例項)詳解

登入鑑權的中介軟體

再比如,我們想寫一個登入鑑權的中介軟體,虛擬碼就可以寫成:

//定義一個登入中介軟體
func authMiddleware(c *gin.Context) {
    //if 是登入使用者
    //  c.Next()
    //else
    //  c.Abort()
}

不過,為了更方便進行一些準備工作(比如連線資料庫等),通常我們更加推薦將中介軟體寫成閉包的形式:

//定義一個登入中介軟體
func authMiddleware(doChck bool) gin.HandlerFunc {
    //寫成閉包的形式,就可以在這裡進行資料庫的連線,或其他的準備工作
    return func(c *gin.Context) { //這裡存放校驗引數的邏輯
        if doChck{
            //if 是登入使用者
            //  c.Next()
            //else
            //  c.Abort()
        } else {
            c.Next()
        }
    }
}

將這部分程式碼納入整體程式碼如下:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定義一箇中介軟體m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
    start := time.Now()
    c.Next() //呼叫後續的處理函式
    //c.Abort() //組織呼叫後續的處理函式
    cost := time.Since(start)
    fmt.Printf("cost:  %v\n ", cost)
    fmt.Println("m1 is done")
}

//定義一箇中介軟體m2
func m2(c *gin.Context) {
    fmt.Println("this is middle-ware m2~")
    c.Abort() //組織呼叫後續的處理函式
    fmt.Println("m2 is done")
}

//定義一個登入中介軟體
func authMiddleware(doChck bool) gin.HandlerFunc {
    //寫成閉包的形式,就可以在這裡進行資料庫的連線,或其他的準備工作
    return func(c *gin.Context) { //這裡存放校驗引數的邏輯
        if doChck {
            //if 是登入使用者
            //  c.Next()
            //else
            //  c.Abort()
        } else {
            c.Next()
        }
    }
}

func main() {
    r := gin.Default()
    //全域性註冊中介軟體
    r.Use(m1, m2, authMiddleware(true))
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", indexHandler)
    r.GET("/game", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "shop",
        })
    })
    r.GET("/food", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "food",
        })
    })
    r.GET("/ad", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "ad",
        })
    })
    r.Run(":9090")
}

當然,路由組也可以進行全域性註冊,例如:

    billGroup := r.Group("/bill")
    billGroup.Use(authMiddleware(true))
    {
        billGroup.GET("/index1", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "lokaloka1"})
        })
        billGroup.GET("/index2", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "lokaloka2"})
        })
        billGroup.GET("/index3", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "lokaloka3"})
        })
    }

中介軟體之間通訊 c.Set()與c.Get()

比如在中介軟體m2中設定一個鍵值對,想在後面的indexHandler中介軟體中根據key取得value,可以這樣寫:

//定義一箇中介軟體m2
func m2(c *gin.Context) {
    fmt.Println("this is middle-ware m2~")
    //c.Abort() //組織呼叫後續的處理函式
    c.Set("name", "lokays")
    fmt.Println("m2 is done")
}

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    name, ok := c.Get("name")
    if !ok {
        name = "匿名使用者"
    }
    c.JSON(http.StatusOK, gin.H{
        "msg": name,
    })
}

注意

📢 注意:gin.Default()會自動載入Logger中介軟體和Recovery中介軟體:

  • Logger中介軟體會將日誌寫入gin.DefaultWriter,無論是否配置了GIN_Mode
  • Recovery中介軟體會Recover所有的panic,如果有panic會寫入500響應碼

所以,如果不想使用任何中介軟體, 可以用gin.New()新建一個路由。

  • 在中介軟體或者Handler中啟動新的goroutine的時候,不能使用上下文的c *gin.Context,只能使用只讀副本c.Copy()

參考:bilibili