Echo动态路由分三类:静态路径(最高优先级,如/users/new)、参数路径(中优先级,如/users/:id)、通配路径(最低优先级,如/files/*),严格按此顺序匹配,不依赖注册顺序。

动态路由的三种写法必须分清
Echo 的动态路由不是“一种写法”,而是三类匹配模式共存,且有严格优先级。写错顺序或混用符号,请求会进错 handler,甚至 404。
- 静态路径(最高优先级):
/users/new、/health—— 完全字面匹配,不带任何符号 - 参数路径(中优先级):
/users/:id、/posts/:category/:page—— 冒号后跟参数名,匹配任意非空段 - 通配路径(最低优先级):
/files/*、/admin/*—— 星号必须在末尾,匹配该前缀下所有子路径(含查询参数)
比如请求 /users/123,会命中 /users/:id,而不会进 /users/*,哪怕后者注册得更早 —— Echo 不看注册顺序,只看匹配类型层级。
c.Param() 只能取冒号定义的参数,别指望它读通配段
参数提取和路径匹配是两件事。c.Param("id") 只对 :id 这种显式声明的参数有效;/* 匹配到的整段路径(如 /files/2023/report.pdf 中的 2023/report.pdf)不会自动拆成参数,也不会出现在 c.Param 结果里。
- 正确用法:
e.GET("/users/:id", func(c echo.Context) error { id := c.Param("id") }) - 错误假设:
e.GET("/files/*", func(c echo.Context) error { path := c.Param("*") })——*不是参数名,c.Param("*")永远返回空字符串 - 取通配段真实路径:
c.Request().URL.Path,再手动截掉前缀,例如strings.TrimPrefix(c.Request().URL.Path, "/files/")
带问号的 :id? 不是可选参数,是独立静态路由
/user/:id? 看起来像“id 可选”,但实际效果是注册了两条路由:一条是 /user/(空 id),另一条是 /user/xxx(非空 id)。它不会让 /user 和 /user/ 同时生效 —— 前者根本不会被匹配到,因为 /user 是静态路径,而 /user/:id? 属于参数路径,优先级更低。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 想支持
/user和/user/123两种访问方式,得显式注册:e.GET("/user", listUsers)+e.GET("/user/:id", getUser) -
:id?的真实用途是兼容旧 URL 设计,比如从/post/1迁移到/post/1/edit,用/post/:id?/edit可同时覆盖两者(但注意:这种写法在 v4 中已不推荐,易引发歧义)
路径参数名重复或冲突会导致 panic
Echo 路由树在启动时就做校验,如果同一路径下出现重复参数名,比如 /users/:id/posts/:id,echo.New() 会直接 panic 并报错 invalid parameter name "id" —— 不是运行时报错,是服务根本起不来。
- 合法写法:
/users/:userID/posts/:postID、/org/:orgId/member/:memberId - 容易忽略的坑:嵌套路由组里拼接路径时手误,例如
v1 := e.Group("/v1"); v1.GET("/users/:id", h)和v2 := e.Group("/v2"); v2.GET("/users/:id", h)没问题,但若写成v1.GET("/users/:id/:id", h)就会挂 - 调试建议:启动前加
echo.Debug = true,它会在日志里打印所有注册的路由及其参数结构,方便核对
最常被跳过的细节是:Echo 的路径匹配发生在请求进入中间件之前,所以你在中间件里看到的 c.Request().URL.Path 已经是原始路径,不会被重写或标准化 —— 如果你依赖路径做鉴权或日志,得自己处理 trailing slash、大小写、编码等问题。









