从本章节开始的2个章节基本都是gin的核心知识点,如果您是初学者,建议只看 基础用法 ,先学会使用是最重要的。
当使用熟练后,再回头看 进阶学习 ,你会彻底明白其中原理,进阶学习都是gin源码解析,对初学者来说难度比较大。

基础用法

1.创建一个标准的路由与路由组

  1. // 使用 gin 路由包提供的默认路由创建一个路有引擎
  2. router := gin.Default()
  3. // 创建一个路由组,并基于路由组创建具体路由
  4. backend:=router.Group("/api/v1")
  5. // 使用已有路由组可以继续分组
  6. users:=backend.Group("/users")
  7. // 这里的花括号,只是为了让代码块紧凑,没有其他任何含义,和 c、c++ 语言的语法块完全一样.
  8. {
  9. // 对于初学者,这里比较难理解的就是第二个参数。
  10. // 这里您首先知道它是从容器获取了一个表单参数验证器,校验客户端请求的参数就行
  11. // 具体细节在表单参数验证器做专门介绍
  12. users.GET("list", validatorFactory.Create(consts.ValidatorPrefix+"UsersList"))
  13. users.POST("create", validatorFactory.Create(consts.ValidatorPrefix+"UsersCreate"))
  14. users.POST("edit", validatorFactory.Create(consts.ValidatorPrefix+"UsersEdit"))
  15. users.POST("destroy", validatorFactory.Create(consts.ValidatorPrefix+"UsersDestroy"))
  16. }

2.带中间件的路由(组)

实际项目开发中,有一部分接口是开放的,但是有部分是需要鉴权认证的,那么针对需要鉴权认证的所有路由可以统一添加中间件进行鉴权认证,一般是token认证。

  1. // 针对以上用户组路由添加token中间件鉴权认证
  2. // 创建一个理由组,并基于路由组创建具体路由
  3. backend:=router.Group("/api/v1")
  4. // authorization.CheckTokenAuth() 是 ginskeleton 默认集成校验token的中加减,直接使用即可
  5. // backend 加载了中间件,表示 /api/v1 开头的路由全部需要 token 校验
  6. backend.Use(authorization.CheckTokenAuth()) //A
  7. users:=backend.Group("/users")
  8. // users 加载了中间件,表示 /api/v1/users 开头的路由全部需要 token 校验
  9. users.Use(authorization.CheckTokenAuth()) // B, 注意: B 和 A 存在重复校验token,实际使用时不要重复验证,如果在 A 处加载,那么就取消 B 处的加载中间件的逻辑
  10. {
  11. // 接下来定义具体路由(路径),对于初学者,这里比较难理解的就是第二个参数。
  12. // 首先告诉各位这里是从容器获取了一个表单参数验证器,校验客户端请求的参数
  13. // 具体细节在表单参数验证器做专门介绍
  14. users.GET("list", validatorFactory.Create(consts.ValidatorPrefix+"UsersList"))
  15. users.POST("create", validatorFactory.Create(consts.ValidatorPrefix+"UsersCreate"))
  16. users.POST("edit", validatorFactory.Create(consts.ValidatorPrefix+"UsersEdit"))
  17. users.POST("destroy", validatorFactory.Create(consts.ValidatorPrefix+"UsersDestroy"))
  18. }

3.不带中间件、不带参数验校验器的路由

验证码路由,该路由无需专门校验参数,所以可以直接调用控制器

  1. // 创建一个验证码路由
  2. verifyCode := router.Group("captcha")
  3. {
  4. // 获取验证码ID,后面可以直接是控制器的函数名称,控制器的函数必须满足:func(context *gin.Context) 形式就行。
  5. verifyCode.GET("/", (&captcha.Captcha{}).GenerateId)
  6. // 获取图像地址
  7. verifyCode.GET("/:captcha_id", (&captcha.Captcha{}).GetImg)
  8. // 校验验证码
  9. verifyCode.GET("/:captcha_id/:captcha_value", (&captcha.Captcha{}).CheckCode)
  10. }

进阶学习

4.路由、中间件相关的 gin 源码学习

看到这里我相信您已经学会了路由、路由组、中间件的创建与使用,那么更深入一步。
我们一起研究一下 users.GET("list", 参数2) 背后究竟发生了什么

  1. // 当我们使用 goland ,ctrl+鼠标左键 点开 Users.GET(路径参数,参数二,...)
  2. // gin的源码如下:
  3. // 参数一: 路由路径
  4. // 参数二:处理函数1
  5. // 参数三:处理函数2
  6. // ...
  7. // 参数
  8. func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
  9. return group.handle(http.MethodGet, relativePath, handlers)
  10. }
  11. // 继续追踪业务处理函数 HandlerFunc 的源码(第二个参数)
  12. // 看到这里终于明白,业务处理函数必须符合 func(*Context) , 不管以什么形式传递进去都可以
  13. type HandlerFunc func(*Context)
  14. // 额外补充一点,中间件的回调函数形式也是 func(*Context)
  15. // 下一章节我们继续深度学习,这里您只需要知道中间件的回调函数和路由的回调函数本质是一回事就行
  16. backend.Use(authorization.CheckTokenAuth())
  17. // 其实,在gin里面,所有的回调函数本质都是 func(*Context) 形式
  18. // 当你明白这一切,那么后面的学习就非常容易了, gin 使用了全局统一的上下文 *gin.Context
  19. // 一次请求开始以后,所有的数据都被绑定了上下文,绝大部分的场景只需要不停地传递 *conetgin.Context 就能获取所有主线数据,去处理业务问题。
  20. //此外 *gin.Context 是一个指针,任何位置通过传递使用,效率也是极高的。

当我们明白回调函数的本质以后,那么下面的代码理解起来,就是非常简单的一件事情了

  1. // 示例1:默认根路由, 客户端访问后端ip:端口
  2. router := gin.Default()
  3. router.GET("/", func (c *gin.Context){
  4. // 处理客户端请求 / 路径时的逻辑 ...
  5. })
  6. // 示例2:验证码相关的一个路由与回调函数示例
  7. // 第二个参数调用了控制器的函数
  8. // 不管从哪里获取,都必须满足 gin 最原始的 func(*Context) 格式
  9. verifyCode := router.Group("captcha")
  10. {
  11. verifyCode.GET("/:captcha_id", (&captcha.Captcha{}).GetImg)
  12. }
  13. // (&captcha.Captcha{}).GetImg 相关的的代码段如下
  14. type Captcha struct {
  15. Id string `json:"id"`
  16. ImgUrl string `json:"img_url"`
  17. Refresh string `json:"refresh"`
  18. Verify string `json:"verify"`
  19. }
  20. func (c *Captcha) GetImg(context *gin.Context) {
  21. // 省略业务代码
  22. }
  23. // 示例3 : 本项目骨架中的路由语法
  24. // 看到这里我想你已经明白了,该路由的第二个参数看起来样子奇怪
  25. // 但是它的本质绝对是 func(*Context) 格式
  26. // 好了,到这里我们就进入下一个环节去继续学习了
  27. users.GET("list", validatorFactory.Create(consts.ValidatorPrefix+"UsersList"))

以上过程总体上就是在定义路由组、路由路径、然后给路由路径编写各种回调函数,一个路由路径可以对应很多个回调函数,具体数量我们将在下一章节继续分析源码。

5.路由组、中间键、路由 各自的特点

5.1 路由组相关的gin源码

  1. // 初始化一个gin默认的路由引擎
  2. router = gin.Default()
  3. // 创建一个分组
  4. api := router.Group("/api/")
  5. // 1.在 goland 中使用 ctrl+鼠标左键点击 router.Group,查看源码
  6. // 首先初始化一个路由组结构体指针
  7. func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
  8. return &RouterGroup{
  9. Handlers: group.combineHandlers(handlers),
  10. basePath: group.calculateAbsolutePath(relativePath),
  11. engine: group.engine,
  12. }
  13. }
  14. // 路由组结构体指针上面绑定的常用函数清单
  15. //2.基于一个路由组可以继续创建更多路由组,
  16. // 注意:该函数返回的是一个路由组指针
  17. func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup
  18. {
  19. // 省略过程代码
  20. }
  21. //3.路由组可以直接使用 Use 函数加载中间件
  22. // 但是由于加载中间件以后,返回值是一个所有路由接口,就不能继续创建路由组
  23. // IRoutes 是一个所有路由的接口,暂时大家先知道它主要负责定义具体的路由,此外还支持加载中间件回调函数就行,后续我们会继续追踪源代码
  24. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
  25. group.Handlers = append(group.Handlers, middleware...)
  26. return group.returnObj()
  27. }
  28. // 4.基于路由组开发者还可以继续自定义请求方式、请求相对路径,以及对应的回调函数
  29. // 注意:这里返回的也是 IRoutes ,即所有路由的统一接口
  30. func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
  31. if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
  32. panic("http method " + httpMethod + " is not valid")
  33. }
  34. return group.handle(httpMethod, relativePath, handlers)
  35. }
  36. //5. 路由组其他函数主要是定义具体的路由。例如:POST 请求
  37. func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
  38. return group.handle(http.MethodPost, relativePath, handlers)
  39. }
  40. //6.GET 请求
  41. func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
  42. return group.handle(http.MethodGet, relativePath, handlers)
  43. }
  44. // 后续还有更多请求方式,请自行查看,格式和 GET 、POST 都是一致的

针对以上源码总结一下:
api := router.Group("/api/") 只有使用 Group 函数创建出来的路由组可以继续创建更多路由组,一旦基于路由组调用了其他函数获取的结果,就只能去定义具体路由、加载中间件等,相关示例:

  1. 场景1:
  2. api := router.Group("/api/")
  3. v1:=api.Group("/v1")
  4. v1.Group("/vx")
  5. // ... Group 可以无限嵌套创建组,最本质就是 Group 返回是 *RouterGroup ,该指针上面绑定了 Group 函数。
  6. 场景2:
  7. api := router.Group("/api/")
  8. api.use(中间件) // 注意这里 我们没有接收结果,如果这里接收结果,结果类型就是IRoutes,就无法创建更多路由组了
  9. v1:=api.Group("/v1") // 基于api继续可以创建路由组
  10. ...
  11. 场景3
  12. api := router.Group("/api/").use(加载一个中间件) // 加载中间件之后的返回结果是:IRoutes,该结构体上没有绑定 Group 函数,因此无法继续创建更多路由
  13. api.Get()
  14. api.use(中间件).Get()

5.2中间件源码定义

  1. // 初始化一个gin默认的路由引擎
  2. router = gin.Default()
  3. // 创建一个分组
  4. api := router.Group("/api/")
  5. // authorization.CheckTokenAuth() 是 ginskeleton 自带的一个 token 鉴权中间件
  6. api.Use(authorization.CheckTokenAuth())
  7. // 在 goland 中使用 ctrl+鼠标左键点击 api.Use 查看源码
  8. // 中间件的源码非常简单,主要负责回调函数注册,返回的是一个所有路由的接口
  9. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
  10. group.Handlers = append(group.Handlers, middleware...)
  11. return group.returnObj()
  12. }
  13. // IRoutes 的源码定义
  14. // 主要的功能:凡是返回值是IRoutes 类型,那么都可以继续加载(注册)中间件,也可以定义具体路由以及对应的回调函数
  15. // IRoutes defines all router handle interface.
  16. type IRoutes interface {
  17. Use(...HandlerFunc) IRoutes
  18. Handle(string, string, ...HandlerFunc) IRoutes
  19. Any(string, ...HandlerFunc) IRoutes
  20. GET(string, ...HandlerFunc) IRoutes
  21. POST(string, ...HandlerFunc) IRoutes
  22. DELETE(string, ...HandlerFunc) IRoutes
  23. PATCH(string, ...HandlerFunc) IRoutes
  24. PUT(string, ...HandlerFunc) IRoutes
  25. OPTIONS(string, ...HandlerFunc) IRoutes
  26. HEAD(string, ...HandlerFunc) IRoutes
  27. StaticFile(string, string) IRoutes
  28. Static(string, string) IRoutes
  29. StaticFS(string, http.FileSystem) IRoutes
  30. }

5.3 路由背后的源码

  1. users.GET("index", func (c *gin.Context){
  2. // 省略业务逻辑
  3. })
  4. // 在 goland 中使用 ctrl+鼠标左键点击 users.GET 查看源码
  5. // 这里实现的主要功能依然是按照 key => value 形式注册 请求方式+路由对应的回调函数
  6. // 注意:该函数的返回值 IRoutes 与中间件处的返回值一样,都是所有路由的接口类型,具体功能参上部分代码段
  7. func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
  8. return group.handle(http.MethodGet, relativePath, handlers)
  9. }
  10. // POST 以及其他请求方式源代码都是统一的格式, 这里省略

学习到这里,再给大家看一段如下代码,我想大家瞬间就懂了。
对于初学者,关于第二个参数我们会在后续继续介绍,这里只要知道他本质上也是一个 func (c *gin.Conetxt) { }即可。

  1. // 示例 1:
  2. users := backend.Group("users/")
  3. {
  4. // 查询
  5. users.GET("list", validatorFactory.Create(consts.ValidatorPrefix+"UserList"))
  6. // 新增
  7. users.POST("create", validatorFactory.Create(consts.ValidatorPrefix+"UserCreate"))
  8. // 更新
  9. users.POST("edit", validatorFactory.Create(consts.ValidatorPrefix+"UserEdit"))
  10. // 删除
  11. users.POST("destroy", validatorFactory.Create(consts.ValidatorPrefix+"UserDestroy"))
  12. }
  13. // 示例2:
  14. users := backend.Group("users/")
  15. {
  16. // 链式定义语法,本质上就是利用了路由的返回类型,继续去注册更多的路由以及回调函数
  17. users.GET("index", validatorFactory.Create(consts.ValidatorPrefix+"UsersShow")).
  18. POST("create", validatorFactory.Create(consts.ValidatorPrefix+"UsersStore")).
  19. POST("edit",validatorFactory.Create(consts.ValidatorPrefix+"UsersUpdate")).
  20. POST("delete",validatorFactory.Create(consts.ValidatorPrefix+"UsersDestroy"))
  21. }