gin学习记录 6 - 中间件和路由 | 青训营;

82 阅读4分钟

6. gin 中间件和路由

把请求的处理函数叫做中间件,且形参必须是*gin.Context 一个请求可以有多个中间件
通过Use()函数来调用中间件

6.1 单个路由和中间件

  • Next() 会将当前中间件前后分离,前面的为请求中间件,后面的为响应中间件
    执行完前面遇到Next()就执行下一个中间件,把响应中间件压入栈中(大概效果是这样)。

  • Abort() 会拦截执行后面的中间件,但当前中间件后面的代码仍会执行完

func m1(c *gin.Context) {  
    fmt.Println("m1 in")  
    c.Next()  
    c.Abort() // 此时Abort在Next后面,m1的剩余部分是最后一个被执行的,之后没有其他中间件  
    fmt.Println("m1 out")  
}  
  
func m2(c *gin.Context) {  
    fmt.Println("m2 in")  
    c.Next()  
    fmt.Println("m2 out")  
}  
  
func m3(c *gin.Context) {  
    fmt.Println("m3 in")  
    c.Next()  
    fmt.Println("m3 out")  
}  
  
func m4(c *gin.Context) {  
    fmt.Println("m4 in")  
    c.Next()  
    fmt.Println("m4 out")  
}  
  
func m5(c *gin.Context) {  
    fmt.Println("m5 in")  
    c.Next()  
    fmt.Println("m5 out")  
}  
  
  
func main() {  
    router := gin.Default()  
 
    // 单个中间件  
    router.GET("/single", m1, m2, m3)  
    /* 
    结果:
    m1 in  
    m2 in  
    m3 in  
    m3 out  
    m2 out  
    m1 out  
    */
    
    // 使用全局中间件 不需要匹配路径,直接用  
    router.Use(m4, m5)  

    err := router.Run(":80")  
        if err != nil {  
        panic(err)  
    }  
}  
  • 通过中间件传递数据并接收 key-value
type User struct {  
    Name string  
    Age int  
}  
  
func _set(c *gin.Context) {  
    // 设置kv  
    c.Set("note", "这是一条kv设置")  

    // 设置结构体数据  
    c.Set("user", User{  
        Name: "kjasn",  
        Age: 11,  
    })  
}  
  
func main() {  
    router := gin.Default()  

    // 使用全局中间件 不需要匹配路径,直接用  
    router.Use(_set)  

    router.GET("/setVal", func(c *gin.Context) {  
        // 用Get()接收 形参是key 返回any类型的val和一个bool值表示是否存在key  
        // 接收单独设置的kv  
        note, ok := c.Get("note")  
        if ok {  
            fmt.Println(note)  
        }  

    ////////////////////////////////////////////////////////////
        // 接收结构体 kv  
        user, ok := c.Get("user")  
        if ok {  
            // 直接打印整个user  
            fmt.Println(user)  
            c.JSON(http.StatusOK, gin.H{"data": user})  

            // 单独打印 需要断言为User类型才能访问User类型的属性  
            fmt.Println("断言")  
            _user, ok := user.(User) // 断言成功 _user 接收到user的类型,若失败则为空  
            if ok {  
                fmt.Println("姓名:", _user.Name, "年龄:", _user.Age)  
                c.JSON(http.StatusOK, gin.H{"name": _user.Name, "age": _user.Age})  
            } else {  
                fmt.Println("断言失败,不是User类型")  
            }  
        }  
    })  
    
    err := router.Run(":80")  
        if err != nil {  
        panic(err)  
    }  
}  

6.2 路由分组

将一系列的路由放到一个组下,统一管理,为分组路由定义全局中间件同单个路由定义全局中间件一样,用Use()函数

  • 定义一些需要的结构体
  
// 封装响应格式  
type Response struct {  
    Code int `json:"code"`  
    Date any `json:"date"`  
    Message string `json:"msg"`  
}  
  
type UserInfo struct {  
    Name string `json:"name"`  
    Age int `json:"age"`  
}  
  
type ArticleInfo struct {  
    Title string `json:"title"`  
    Content string `json:"content"`  
}  
  • 定义中间件
// 此处直接内定一些简单的数据
func DisplayUserList(c *gin.Context) {  
    list := []UserInfo{  
        {"张三", 23},  
        {"李四", 45},  
    }  

    c.JSON(http.StatusOK, Response{Code: 1, Date: list, Message: "请求成功"})  
}  
  
func DisplayArticle(c *gin.Context) {  
    list := []ArticleInfo {  
        {"c语言入门到入土", "本书是c语言入门教程,面向入土"},  
        {"数据库从删库到跑路", "本书教你如何从删库到跑路"},  
    }  

    c.JSON(http.StatusOK, Response{Code: 1, Date: list, Message: "请求成功"})  
}  
  
func showTest(c *gin.Context) {  
    fmt.Println("showTest in ")  
    c.JSON(http.StatusOK, gin.H{"msg": "这是一个用来测试的中间件"})  
}  
  • 拆分出来,这样也方便单独成包
func UserRouterInit(api *gin.RouterGroup) {  
    // 用户管理分组 将userManagement作为api下的一个组 同一个组内的请求一般放在一个大括号里  
    // 发出请求 http://127.0.0.1/api/user-management/user  
    userManagement := api.Group("user-management") {  
        // 以下是userManagement分组下的一些请求  
        userManagement.GET("/user", DisplayUserList)  
        // ......
    }  
}  

func ArticleRouterInit(api *gin.RouterGroup) {  
    // 文章管理分组 将article-management作为api下的一个组 与userManagement并列  
    // 发出请求 http://127.0.0.1/api/article-management/article  
    article := api.Group("article-management") {
        // 以下是article-management分组下的一些请求,就像之前写的一样 此处简写  
        article.GET("/article", DisplayArticle)  
        // ..........
    }  
}  

func Test(api *gin.RouterGroup) {  
    // 为test组注册showTest全局中间件,该组下的子组都会用到这个中间件  
    test := api.Group("test").Use(showTest) { 
        test.GET("show1", func(c *gin.Context) {  
            fmt.Println("in show1")  
        })  
        test.GET("show2", func(c *gin.Context) {  
            fmt.Println("in show2")  
        })  
    }  
}  
  • 在main()中分组然后直接调用拆分出来的包即可
    分组之后,请求当前组下的其他分组需要加上父组的路径作为前缀
// 分组 
api := router.Group("api") // 返回一个组  
  
UserRouterInit(api)  
ArticleRouterInit(api)  
Test(api)

6.3 讲讲gin.Default()和gin.New()

  • gin.New() 是创建一个新的空白引擎,没有添加任何中间件。而**gin.Default()中调用了gin.New()并添加两个中间件 gin.Logger(), gin.Recovery(),这是二者的主要区别。 **

  • gin.Logger() : 记录请求日志:在每次请求到达服务器时,记录请求的信息,包括请求的方法(GET、POST 等)、请求的路径、请求的 IP 地址等。

  • gin.Recovery() : 恢复从错误中恢复:在处理请求时,捕获潜在的异常,比如代码出现了意外的错误或崩溃。它会确保应用不会因为异常而完全崩溃,而是尽量将异常信息记录下来,并返回一个友好的错误响应给客户端 。

// 以下就等同于用gin.Default()  
router := gin.New()  
router.Use(gin.Logger(), gin.Recovery())