文章目录

  • Gin是什么
  • Gin代码解析
  • 路由(Router)
  • 中间件(Middleware)
  • 自定义一个全局中间件
  • 自定义一个局部中间件


Gin是什么

Gin 是使用 Go/golang 语言实现的 HTTP Web 框架。接口简洁,性能极高。

Gin 特性

  • 快速:路由不使用反射,基于Radix树,内存占用少。
  • 中间件:HTTP请求,可先经过一系列中间件处理,例如:Logger,Authorization,GZIP等。这个特性和 NodeJs 的 Koa 框架很像。中间件机制也极大地提高了框架的可扩展性。
  • 异常处理:服务始终可用,不会宕机。Gin 可以捕获 panic,并恢复。而且有极为便利的机制处理HTTP请求过程中发生的错误。
  • JSON:Gin可以解析并验证请求的JSON。这个特性对Restful API的开发尤其有用。
  • 路由分组:例如将需要授权和不需要授权的API分组,不同版本的API分组。而且分组可嵌套,且性能不受影响。
  • 渲染内置:原生支持JSON,XML和HTML的渲染。

Gin代码解析

package main

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

func main() {
        r := gin.Default()
        r.GET("/", func(c *gin.Context) {
                c.String(200, "Hello, World")
        })
        r.Run() // listen and serve on 0.0.0.0:8080
}
  1. 首先,我们使用了gin.Default()生成了一个实例,这个实例即 WSGI 应用程序。
  2. 接下来,我们使用r.Get("/", …)声明了一个路由,告诉 Gin 什么样的URL 能触发传入的函数,这个函数返回我们想要显示在用户浏览器中的信息。
  3. 最后用 r.Run()函数来让应用运行在本地服务器上,默认监听端口是 8080,可以传入参数设置端口,例如r.Run(":9999")即运行在 _9999_端口。

路由(Router)

路由是一个非常重要的概念,所有的接口都要有路由来进行管理。Gin 的路由支持 GET , POST , PUT , DELETE , PATCH , HEAD , OPTIONS 请求,同时还有一个 Any 函数,可以同时支持以上的所有请求。

路由需要传入两个参数,一个为路径,另一个为路由执行的方法,我们叫做它处理器 Handler ,而且,该参数是可变长参数。也就是说,可以传入多个 handler,形成一条 handler chain 。同时对 handler 该函数有着一些要求,该函数需要传入一个 Gin.Context 指针,同时要通过该指针进行值得处理。

Golang微内核架构 golang 架构_中间件


  • 解析路径参数
    有时候我们需要动态的路由,如 /user/:name,通过调用不同的 url 来传入不同的 name。/user/:name/role, 代表可选。
r.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name") //获取路径里name的值
        c.String(http.StatusOK, "Hello %s", name)
})

比如:

$ curl http://localhost:9999/user/saber
Hello saber

  • 获取Query参数
// 匹配users?name=xxx&role=xxx,role可选
r.GET("/users", func(c *gin.Context) {
        name := c.Query("name")
        role := c.DefaultQuery("role", "teacher")
        c.String(http.StatusOK, "%s is a %s", name, role)
})

  • 获取Body参数
r.POST("/form", func(c *gin.Context) {
        username := c.PostForm("username")
        password := c.DefaultPostForm("password", "000000") // 可设置默认值

        c.JSON(http.StatusOK, gin.H{
                "username": username,
                "password": password,
        })
})

也可以使用struct模型绑定来接住:

type UserModel struct {
         Email         string `form:"email"`
         Password      string `form:"password"`
         PasswordAgain string `form:"password-again"`
}



func UserRegister(context *gin.Context) {
   var user model.UserModel
   if err := context.ShouldBind(&user); err != nil {
           println("err ->", err.Error())
           return
   }
   println("email", user.Email, "password", user.Password, "password again", user.PasswordAgain)
}

做后端开发的人都明白一个道理:永远不要相信前端传过来的数据。所有的数据在进过后端时,务必要进行数据的校验。
在模型中可用 binding 来对数据进行校验。Gin 对于数据校验使用的是 validator.v8 库,该库提供多种校验方法。通过 binding:"" 方式来进行对数据的校验。

type UserModel struct {
        Email         string `form:"email" binding:"email"`
        Password      string `form:"password"`
        PasswordAgain string `form:"password-again" binding:"eqfield=Password"`
}

  • 重定向
r.GET("/redirect", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "/index")
})

r.GET("/goindex", func(c *gin.Context) {
        c.Request.URL.Path = "/"
        r.HandleContext(c)
})

  • 分组路由
    如果有一组路由,前缀都是/api/v1开头,是否每个路由都需要加上/api/v1这个前缀呢?答案是不需要,分组路由可以解决这个问题。利用分组路由还可以更好地实现权限控制,例如将需要登录鉴权的路由放到同一分组中去,简化权限控制。
// group routes 分组路由
defaultHandler := func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
                "path": c.FullPath(),
        })
}


// group: v1
v1 := r.Group("/v1")
{
        v1.GET("/posts", defaultHandler)
        v1.GET("/series", defaultHandler)
}
// group: v2
v2 := r.Group("/v2")
{
        v2.GET("/posts", defaultHandler)
        v2.GET("/series", defaultHandler)
}
$ curl http://localhost:9999/v1/posts
{"path":"/v1/posts"}
$ curl http://localhost:9999/v2/posts
{"path":"/v2/posts"}

中间件(Middleware)

中间件 middleware 在 golang 中是一个很重要的概念,与 java 中拦截器很相似。我们在这里通过 gin 源代码来看看中间件代码使用。

回到main()方法:

router := gin.Default()
这个gin.Default()做了什么?
func Default() *Engine {
        debugPrintWARNINGDefault()
        engine := New()
        engine.Use(Logger(), Recovery())
        return engine
}

源码中,首先是 New 了一个 engine ,紧接着通过 Use 方法,传入了 Logger() 和 Recovery() ,而 Logger和 Recovery 就是两个中间件。
其中 Logger 是对日志进行记录,而 Recovery 是对有 painc时, 进行 500 的错误处理

查看了源码之后,那么我们也就知道如何使用中间件了。

自定义一个全局中间件

中间件需要返回 gin.HandlerFunc 函数,所以定义返回函数。
而且 中间件有个 Next 函数,在我们定义的众多中间件,会形成一条中间件链,而通过 Next 函数来对后面的中间件进行执行。

我们来自己定义一个 Logger 日志,新建一个 middleware 文件夹,里面新建 Logger.go,进行我们自定义的日志展示。

package middleware

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

func Logger() gin.HandlerFunc {
        return func(context *gin.Context) {
                host := context.Request.Host
                url := context.Request.URL
                method := context.Request.Method
                fmt.Printf("%s::%s \t %s \t %s ", time.Now().Format("2006-01-02 15:04:05"), host, url, method)
                context.Next()
                fmt.Println(context.Writer.Status())
        }
}

修改 initRouter 中的 SetupRouter 方法,将我们自定义的中间件进行添加。

router := gin.New()
// 添加自定义的 logger 中间件
router.Use(middleware.Logger(), gin.Recovery())

此时重新启动我们的项目,访问localhost:8080端口,控制台上就会打印出我们新的格式日志。

2019-07-23 18:17:58::localhost:8080          /          GET 200

自定义一个局部中间件

package middleware

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

func Auth() gin.HandlerFunc {
        return func(context *gin.Context) {
                println("已经授权")
                context.Next()
        }
}
userRouter.GET("/profile/", middleware.Auth(), handler.UserProfile)
userRouter.POST("/update", middleware.Auth(), handler.UpdateUserProfile)

再次运行项目,当我们对用户详情页进行访问的时候,控制台上会打印出 已经授权 的日志,而访问其他页面则不会出现,说明我们的中间件使用成功。