解决 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
:channelID
与 redirect
冲突,并且两个路由的层级是不一样的,第一个有两级,第二个有三级,如何解决?
先将上面的路由转化下:
- 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 规范),尽可能的避免路由冲突问题。由于是重构项目,为了保持接口路由不变,才这么处理的。