解决 gin 路由冲突问题

Table of Contents

使用 gin 框架时,如果有两个这样的路由:

  • GET /users/:id
  • GET /users/info

gin 会报错:

panic: 'info' in new path '/users/info' conflicts with existing wildcard ':id' in existing prefix '/users/:id'

原因是这两个路由拥有一致的 HTTP 方法(指 GET/POST/PUT/DELETE 等)和请求路径前缀,且在相同的路由位置上,第一个路由是 wildcard(指 :id 这种形式)参数,第二个路由是普通字符串 info,那么就会发生路由冲突。 发生冲突该如何解决?

router.GET("/users/:id", func(ctx *gin.Context) {
        switch c.Param("id") {
        case "info":
            handlers.getUserInfo(ctx)
        default:
            handlers.GetUser(ctx)
        }
    })

思路是 :id 这个通配结果如果是 info,则拦截下来,走 /users/info 对应的 handler 逻辑;:id 通配结果非 info,则走 /users/:id 对应的 handler 逻辑。虽然不太好看,但能解决问题。

稍微复杂的路由冲突可能是这样的:

  • GET /:channelID/:programID
  • GET /redirect/:channelID/:programID

:channelIDredirect 冲突,并且两个路由的层级是不一样的,第一个有两级,第二个有三级,如何解决?

先将上面的路由转化下:

  • GET /:path1/:path2
  • GET /:path1/:path2/:path3

转换后逻辑更清晰,

  • 如果 path1 = redirect, 且 path2 和 path3 都不为空,则匹配到第二个路由,那么 path2 与 path3 分别是 channelID 和 programID

  • 如果 path1 不为空,且 path1 != redirect, 且 path2 不为空,且 path3 为空,则匹配到第一个路由,那么 path1 与 path2 分别是 channelID 和 programID

router.GET("/:path1/:path2", getHandler)       
router.GET("/:path1/:path2/:path3", getHandler)

func getHandler(ctx *gin.Context) {
    if ctx.Param("path1") == "redirect" {
        if ctx.Param("path2") != "" && ctx.Param("path3") != "" {
            ctx.Params = append(ctx.Params, gin.Param{Key: "channelID", Value: ctx.Param("path2")},
                gin.Param{Key: "programID", Value: ctx.Param("path3")})

            handlers.GetRedirectResult(ctx)
        }
    } else if ctx.Param("path1") != "" && ctx.Param("path2") != "" && ctx.Param("path3") == "" {
        ctx.Params = append(ctx.Params, gin.Param{Key: "channelID", Value: ctx.Param("path1")},
            gin.Param{Key: "programID", Value: ctx.Param("path2")})

        handlers.GetResult(ctx)
    }
}

以上的法方法是不优雅的,当遇到路由冲突的时候,可以考虑路由设计是否合理(比如是否遵循 RESTful API 规范),尽可能的避免路由冲突问题。由于是重构项目,为了保持接口路由不变,才这么处理的。