@@ -1,58 +0,0 @@ 02.gin框架学习入门 | 凤凰涅槃进阶之路

02.gin框架学习入门

Abel sun2023年1月6日约 4372 字大约 15 分钟

02.gin框架学习入门

Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上Gin

1,Gin框架介绍

Go世界里最流行的Web框架,Githubopen in new window上有32K+star。 基于httprouteropen in new window开发的Web框架。 中文文档open in new window齐全,简单易用的轻量级框架。

2,Gin框架安装与使用

1,安装

下载并安装Gin:

go get -u github.com/gin-gonic/gin

2,第一个Gin示例

package main

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

func main() {
 // 创建一个默认的路由引擎
 r := gin.Default()
 // GET:请求方式;/hello:请求的路径
 // 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
 r.GET("/hello", func(c *gin.Context) {
  // c.JSON:返回JSON格式的数据
  c.JSON(200, gin.H{
   "message": "Hello world!",
  })
 })
 // 启动HTTP服务,默认在0.0.0.0:8080启动服务
 r.Run()
}

将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello就能看到一串JSON字符串。

3,RESTful API

REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

推荐阅读阮一峰 理解RESTful架构open in new window

简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。

只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

请求方法URL含义
GET/book查询书籍信息
POST/create_book创建书籍记录
POST/update_book更新书籍信息
POST/delete_book删除书籍信息

同样的需求我们按照RESTful API设计如下:

请求方法URL含义
GET/book查询书籍信息
POST/book创建书籍记录
PUT/book更新书籍信息
DELETE/book删除书籍信息

Gin框架支持开发RESTful API的开发。

func main() {
 r := gin.Default()
 r.GET("/book", func(c *gin.Context) {
  c.JSON(200, gin.H{
   "message": "GET",
  })
 })

 r.POST("/book", func(c *gin.Context) {
  c.JSON(200, gin.H{
   "message": "POST",
  })
 })

 r.PUT("/book", func(c *gin.Context) {
  c.JSON(200, gin.H{
   "message": "PUT",
  })
 })

 r.DELETE("/book", func(c *gin.Context) {
  c.JSON(200, gin.H{
   "message": "DELETE",
  })
 })
}

开发RESTful API的时候我们通常使用Postmanopen in new window来作为客户端的测试工具。

4,gin渲染

1,返回json

package main

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

func main() {
 r := gin.Default()
 // 方法一:通过map返回
 //data := map[string]interface{}{
 // "name": "eryajf",
 // "age":  20,
 //}
 // gin提供了一个提前定义好了的map H,方便我们直接调用
 data := gin.H{"name": "eryajf", "age": 20} // type H map[string]interface{}
 r.GET("/json", func(c *gin.Context) {
  c.JSON(http.StatusOK, data)
 })

 // 方法二:通过结构体 注意,结构体当中字段名称必须大写,因为下边c.JSON需要对结构体序列化,如果小写,则读不到数据
 // 如果仍旧想返回小写的字段,可以通过tag来定义
 type msg struct {
  Name    string `json:"name"`
  Message string `json:"message"`
  Age     int    `json:"age"`
 }
 data1 := msg{
  Name:    "二丫讲梵",
  Message: "hello golang",
  Age:     22,
 }
 r.GET("/otherjson", func(c *gin.Context) {
  c.JSON(http.StatusOK, data1)
 })

 r.Run(":9090")

}

5,获取参数

1,获取querystring参数

querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

package main

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

func main() {
 r := gin.Default()
 // GET请求URL ?后面的是query string参数
 // key=value格式,多个用 & 连接

 r.GET("web", func(c *gin.Context) {
  // 获取浏览器那边发请求携带的 query string 参数
  //方式一   http://127.0.0.1:9090/web?query=eryajf&age=20
  name := c.Query("query") // 通过 query获取请求中携带的query string
  age := c.Query("age")
  //方式二
  //name := c.DefaultQuery("query", "somequery") //取不到就用指定的默认值
  //方式三
  //name, ok := c.GetQuery("query") //取不到query就返回false
  //if !ok {
  // name = "somequery"
  //}
  c.JSON(http.StatusOK, gin.H{
   "name": name,
   "age":  age,
  })
 })
 r.Run(":9090")
}

此时请求 http://127.0.0.1:9090/web?query=eryajf&age=20

2,获取form表单

请求的数据通过form表单来提交,例如向/user/search发送一个POST请求,获取请求数据的方式如下,此处通过两个html文件简单模拟一下登陆页面。

创建一个登陆页面:login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>

<form action="/login" method="post">
    <div>
        <label for="username">username:</label>
        <input type="text" name="username" id="username">
    </div>
    <div>
        <label for="password">password:</label>
        <input type="text" name="password" id="password">
    </div>
    <div>
    <input type="submit" value="登陆">
    </div>
</form>
</body>
</html>

创建一个index.html用于返回用户登陆成功之后的页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>

<h1>hello {{ .Name }}</h1>
<h1>hello {{ .Pass }}</h1>

</body>
</html>

主程序内容如下:

package main

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

func main() {
 r := gin.Default()

 r.LoadHTMLFiles("./login.html", "./index.html")

 r.GET("/login", func(c *gin.Context) {
  c.HTML(http.StatusOK, "login.html", nil)
 })
 // /login post
 r.POST("login", func(c *gin.Context) {
  // 方法一
  //username := c.PostForm("username")
  //password := c.PostForm("password")
  // 方法二
  //username := c.DefaultPostForm("username", "somev")
  //password := c.DefaultPostForm("password", "***")
  // 方法三
  username, ok := c.GetPostForm("username")
  if !ok {
   username = "somev"
  }
  password, ok := c.GetPostForm("password")
  if !ok {
   password = "***"
  }
  c.HTML(http.StatusOK, "index.html", gin.H{
   "Name": username,
   "Pass": password,
  })
 })

 r.Run(":9090")
}

3,获取path参数

请求的参数通过URL路径传递,例如:/user/search/小王子/沙河。 获取请求URL路径中的参数的方式如下。

package main

// 获取请求的path (URI) 返回的都是字符串类型
// 注意URL的匹配不要冲突了
import (
 "github.com/gin-gonic/gin"
 "net/http"
)

func main() {
 r := gin.Default()
 r.GET("/user/:name/:age", func(c *gin.Context) { // eg: curl 127.0.0.1:9090/user/eryajf/20
  name := c.Param("name")
  age := c.Param("age") //string类型
  c.JSON(http.StatusOK, gin.H{
   "name": name,
   "age":  age,
  })
 })
 // 模拟查询博客某年某月的文章列表
 r.GET("blog/:year/:month", func(c *gin.Context) { // eg: curl 127.0.0.1:9090/blog/2019/10
  year := c.Param("year")
  month := c.Param("month")
  c.JSON(http.StatusOK, gin.H{
   "year":  year,
   "month": month,
  })
 })

 r.Run(":9090")
}

4,参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSONform表单QueryString类型的数据,并把值绑定到指定的结构体对象。

package main

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

type userInfo struct {
 Username string `form:"username" json:"user"`
 Password string `form:"password" json:"pwd"`
}

func main() {
 r := gin.Default()
 r.GET("/user", func(c *gin.Context) {
  username := c.Query("username")
  password := c.Query("password")
  u := userInfo{
   Username: username,
   Password: password,
  }
  c.JSON(http.StatusOK, gin.H{
   "user": u.Username,
   "pass": u.Password,
  })
 })
 // 利用 绑定参数方式可以方便的获取到浏览器请求参数 querystring
 r.GET("/bindquery", func(c *gin.Context) {
  var u userInfo
  if err := c.ShouldBind(&u); err != nil {
   c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
  } else {
   c.JSON(http.StatusOK, gin.H{
    "user": u.Username,
    "pass": u.Password,
   })
  }
 })
 // 利用 绑定参数方式可以方便的获取到浏览器表单参数 提交普通的参数
 r.POST("/bindform", func(c *gin.Context) {
  var u userInfo
  if err := c.ShouldBind(&u); err != nil {
   c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
  } else {
   c.JSON(http.StatusOK, gin.H{
    "user": u.Username,
    "pass": u.Password,
   })
  }
 })
 // 利用 绑定参数方式可以方便的获取到浏览器表单参数 提交json内容
 r.POST("/bindjson", func(c *gin.Context) {
  var u userInfo
  if err := c.ShouldBind(&u); err != nil {
   c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
  } else {
   c.JSON(http.StatusOK, gin.H{
    "user": u.Username,
    "pass": u.Password,
   })
  }
 })

 r.Run(":9090")
}

其中发起post请求的时候,上边两种方法有所不同,请求 /bindform,方法如下:

请求 /bindjson,方法如下:

6,路由重定向

package main

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

func main() {
 r := gin.Default()

 r.GET("index", func(c *gin.Context) {
  //c.JSON(http.StatusOK, gin.H{
  // "msg": "ok",
  //})
  // 将 /index 请求跳转到 其他域名
  c.Redirect(http.StatusMovedPermanently, "http://eryajf.net")
 })

 r.GET("/a", func(c *gin.Context) {
  // 将 /a 跳转到 /b
  c.Request.URL.Path = "/b" //把请求的URL修改
  r.HandleContext(c)        //继续后续的处理
 })
 r.GET("/b", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
   "msg": "b",
  })
 })

 r.Run(":9090")
}

7,路由组

package main

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

func main() {
 r := gin.Default()
 r.GET("index", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
   "method": "GET",
  })
 })
 r.POST("index", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
   "method": "POST",
  })
 })
 r.PUT("index", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
   "method": "PUT",
  })
 })
 r.DELETE("index", func(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{
   "method": "DELETE",
  })
 })

 // Any 可以接收任意方法
 r.Any("user", func(c *gin.Context) {
  switch c.Request.Method {
  case "GET":
   c.JSON(http.StatusOK, gin.H{"method": "GET"})
  case http.MethodPost:
   c.JSON(http.StatusOK, gin.H{"method": "POST"})
  }
 })

 // NoRoute 可以接收任意未定义的路由请求
 r.NoRoute(func(c *gin.Context) {
  c.JSON(http.StatusNotFound, gin.H{"msg": "来到了荒漠"})//也可以指向固定的404.html页面
 })

 // 定义一个视频首页以及详情
 //r.GET("vedio/indxe", func(c *gin.Context) {
 // c.JSON(http.StatusOK, gin.H{"msg": "/vedio/index"})
 //})
 //r.GET("vedio/xx", func(c *gin.Context) {
 // c.JSON(http.StatusOK, gin.H{"msg": "/vedio/xx"})
 //})
 //r.GET("vedio/oo", func(c *gin.Context) {
 // c.JSON(http.StatusOK, gin.H{"msg": "/vedio/oo"})
 //}) 可以用如下路由组方式定义
 vedioGroup := r.Group("vedio")
 { //通常用一个大括号作为代码块包起来
  vedioGroup.GET("index", func(c *gin.Context) {
   c.JSON(http.StatusOK, gin.H{"msg": "/vedio/index"})
  })
  vedioGroup.GET("xx", func(c *gin.Context) {
   c.JSON(http.StatusOK, gin.H{"msg": "/vedio/xx"})
  })
  vedioGroup.GET("oo", func(c *gin.Context) {
   c.JSON(http.StatusOK, gin.H{"msg": "/vedio/oo"})
  })
 }

 r.Run(":9090")
}

路由嵌套:

shopGroup := r.Group("/shop")
 {
  shopGroup.GET("/index", func(c *gin.Context) {...})
  shopGroup.GET("/cart", func(c *gin.Context) {...})
  shopGroup.POST("/checkout", func(c *gin.Context) {...})
  // 嵌套路由组
  xx := shopGroup.Group("xx")
  xx.GET("/oo", func(c *gin.Context) {...})
 }

8,中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

1,定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。

// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
 fmt.Println("m1 in...")
 start := time.Now()
 c.Next() //调用后续的处理函数
 //c.Abort() //阻止调用后续的处理函数
 cost := time.Since(start)
 fmt.Printf("cost:%v\n", cost)
 fmt.Println("m1 out...")
}

2,注册中间件

在gin框架中,我们可以为每个路由添加任意数量的中间件。

1,为某个路由单独注册

package main

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

func indexHandler(c *gin.Context) {
 fmt.Println("index...")
 c.JSON(http.StatusOK, gin.H{
  "msg": "index...",
 })
}
func shopHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "shop...",
 })
}
func userHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "user...",
 })
}

// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
 fmt.Println("m1 in...")
 start := time.Now()
 c.Next() //调用后续的处理函数
 //c.Abort() //阻止调用后续的处理函数
 cost := time.Since(start)
 fmt.Printf("cost:%v\n", cost)
 fmt.Println("m1 out...")
}

func main() {
 r := gin.Default()
    
 // GET(relativePath string, handlers ...HandlerFunc)
 r.GET("/index", m1, indexHandler)
 r.GET("/shop", m1, indexHandler)
 r.GET("/user", indexHandler)
    
 r.Run()
}

同一个中间件可以注册给指定的路由,不过如果想要把这个中间件授权给所有路由组,就需要用如下方式了。

2,为全局路由注册

package main

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

func indexHandler(c *gin.Context) {
 fmt.Println("index...")
 c.JSON(http.StatusOK, gin.H{
  "msg": "index...",
 })
}
func shopHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "shop...",
 })
}
func userHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "user...",
 })
}

// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
 fmt.Println("m1 in...")
 start := time.Now()
 c.Next() //调用后续的处理函数
 //c.Abort() //阻止调用后续的处理函数
 cost := time.Since(start)
 fmt.Printf("cost:%v\n", cost)
 fmt.Println("m1 out...")
}

func main() {
 r := gin.Default()

 r.Use(m1)                 //全局注册中间件函数 m1
 r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
 r.GET("/shop", shopHandler)
 r.GET("/user", userHandler)
 r.Run()
}

3,为路由组注册中间件

为路由组注册中间件有以下两种写法。

写法1:

shopGroup := r.Group("/shop", m1)
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

写法2:

shopGroup := r.Group("/shop")
shopGroup.Use(m1)
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

3,中间件执行顺序

1,c.Next()

c.Next():表示执行下一个函数

package main

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

func indexHandler(c *gin.Context) {
 fmt.Println("index...")
 c.JSON(http.StatusOK, gin.H{
  "msg": "index...",
 })
}
func shopHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "shop...",
 })
}
func userHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "user...",
 })
}

// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
 fmt.Println("m1 in...")
 start := time.Now()
 c.Next() //调用后续的处理函数
 cost := time.Since(start)
 fmt.Printf("cost:%v\n", cost)
 fmt.Println("m1 out...")
}

func m2(c *gin.Context) {
 fmt.Println("m2 in...")
 c.Next()
 fmt.Println("m2 out...")
}

func main() {
 r := gin.Default()
 
 //r.Use(m1)                 //全局注册中间件函数 m1
 r.Use(m1, m2)                 //可以注册多个中间件,注意中间件执行的顺序
 r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
 r.GET("/shop", shopHandler)
 r.GET("/user", userHandler)
 
 r.Run()
}

如果请求index,将打印:

m1 in...
m2 in...
index...
m2 out...
cost:232.264µs
m1 out...

执行图示如下:

image-20200308101949467

具体函数流程,如下:

image-20200308102049034

2,c.Abort()

c.Abort:表示取消执行后边的函数

package main

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

func indexHandler(c *gin.Context) {
 fmt.Println("index...")
 c.JSON(http.StatusOK, gin.H{
  "msg": "index...",
 })
}
func shopHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "shop...",
 })
}
func userHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "user...",
 })
}

// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
 fmt.Println("m1 in...")
 start := time.Now()
 c.Next() //调用后续的处理函数
 cost := time.Since(start)
 fmt.Printf("cost:%v\n", cost)
 fmt.Println("m1 out...")
}

func m2(c *gin.Context) {
 fmt.Println("m2 in...")
 //c.Next()
 c.Abort() //阻止调用后续的处理函数
 fmt.Println("m2 out...")
}

func main() {
 r := gin.Default()
 
 //r.Use(m1)                 //全局注册中间件函数 m1
 r.Use(m1, m2)                 //可以注册多个中间件,注意中间件执行的顺序
 r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
 r.GET("/shop", shopHandler)
 r.GET("/user", userHandler)
 
 r.Run()
}

执行如上代码,将会看到如下输出:

m1 in...
m2 in...
m2 out...
cost:232.264µs
m1 out...

进入到m2 in之后不再执行响应请求的函数,而是直接来到 m2 out。图示如下:

image-20200308102512239

3,return

return:随时从某个地方跳出


package main

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

func indexHandler(c *gin.Context) {
 fmt.Println("index...")
 c.JSON(http.StatusOK, gin.H{
  "msg": "index...",
 })
}
func shopHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "shop...",
 })
}
func userHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "user...",
 })
}

// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
 fmt.Println("m1 in...")
 start := time.Now()
 c.Next() //调用后续的处理函数
 cost := time.Since(start)
 fmt.Printf("cost:%v\n", cost)
 fmt.Println("m1 out...")
}

func m2(c *gin.Context) {
 fmt.Println("m2 in...")
 //c.Next()
 c.Abort() //阻止调用后续的处理函数
 return
 fmt.Println("m2 out...")
}

func main() {
 r := gin.Default()
 
 //r.Use(m1)                 //全局注册中间件函数 m1
 r.Use(m1, m2)                 //可以注册多个中间件,注意中间件执行的顺序
 r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
 r.GET("/shop", shopHandler)
 r.GET("/user", userHandler)
 
 r.Run()
}

如上代码执行结果如下:

m1 in...
m2 in...
cost:232.264µs
m1 out...

4,c.Set()和c.Get()

package main

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

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

// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
 fmt.Println("m1 in...")
 start := time.Now()
 c.Next() //调用后续的处理函数
 //c.Abort() //阻止调用后续的处理函数
 cost := time.Since(start)
 fmt.Printf("cost:%v\n", cost)
 fmt.Println("m1 out...")
}

func m2(c *gin.Context) {
 fmt.Println("m2 in...")
 //c.Next()
 c.Set("name", "eryajf")
 fmt.Println("m2 out...")
}

func main() {
 r := gin.Default()
 r.Use(m1, m2)                 //可以注册多个中间件,注意中间件执行的顺序
 r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
 r.Run()
}

这里在m2这个中间件里边传递了一个name给后边的函数,然后indexHandler接收这个变量来使用。

4,登陆中间件实例

通常我们采用如下方式定义一个判断用户是否登陆的一个中间件:

package main

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

func indexHandler(c *gin.Context) {
 fmt.Println("index...")
 c.JSON(http.StatusOK, gin.H{
  "msg": "index...",
 })
}
func shopHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "shop...",
 })
}
func userHandler(c *gin.Context) {
 c.JSON(http.StatusOK, gin.H{
  "msg": "user...",
 })
}

// 定义一个中间件用于统计函数处理请求的耗时
func m1(c *gin.Context) {
 fmt.Println("m1 in...")
 start := time.Now()
 c.Next() //调用后续的处理函数
 //c.Abort() //阻止调用后续的处理函数
 cost := time.Since(start)
 fmt.Printf("cost:%v\n", cost)
 fmt.Println("m1 out...")
}

func m2(c *gin.Context) {
 fmt.Println("m2 in...")
 c.Next()
 fmt.Println("m2 out...")
}

func authMiddleware(doCheck bool) gin.HandlerFunc {
 // 可以连接数据库
 // 或者一些其他的准备工作
 return func(c *gin.Context) {
  if doCheck {
   // 存放具体的逻辑
   // 判断是否登陆
   // if 是登陆用户
   // c.Next()
   // else
   // c.Abort()
  } else {
   c.Next()
  }
 }
}

func main() {
 r := gin.Default()
 
 r.Use(m1, m2)                 //可以注册多个中间件,注意中间件执行的顺序
 r.GET("/index", indexHandler) //这样保持了以下路由原样不变,也新增了中间件进来
 r.GET("/shop", authMiddleware(true), shopHandler)
 r.GET("/user", userHandler)
 r.Run()
}

3,中间件注意事项

2,gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

3,gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.9.1