跳转到内容

Router Primer

当你已经通过 稳定根 确认改动仍属于默认服务路径,而且问题进一步收窄到服务如何将 URL 映射到 handler 时,就打开这一页:哪条路径胜出、如何提取 params、命名路由如何反解。

router 拥有 HTTP route matching、path parameter 提取、route group 与反向路由。它被有意设计得很窄:它不了解 JSON 响应、auth 决策或应用构造。

app.Get("/api/v1/items", http.HandlerFunc(items.List))
app.Post("/api/v1/items", http.HandlerFunc(items.Create))
grp := app.Group("/api/v1/users")
grp.Get("/:id", http.HandlerFunc(users.Get))
  • 你正在添加、删除或重组 URL pattern
  • 你正在修改 path parameter 提取或 Param 助手
  • 你正在用共享前缀或 middleware 构建 route group
  • 你正在用 WithRouteName 反解命名路由 URL
  • 你正在用 StaticStaticFS 挂载静态文件树
  • 改动关于响应格式或错误格式化 — 从 contract 开始
  • 改动引入了 auth 验证或 security headers — 从 security 开始
  • 工作关于 app 如何组装和启动 — 从 core 开始
  • 改动添加了 tenant 专属路由工厂、feature-flag routing 或前端缓存策略
  1. router/module.yaml
  2. reference/standard-service/internal/app/routes.go
  3. router/cache.go
这些工作适合留在 router一旦变成这些问题就应移出
NewRouterAddRouteGroup — 结构化基础元语隐式路由注册、反射驱动的发现,或服务构造
从匹配路径中提取 Param路由参数验证策略或业务规则守卫
WithRouteName 与命名路由 URL 反解stdlib-shadow handler 别名或框架式 controller dispatch
Static / StaticFS — 小型挂载基础元语前端资产策略、SPA fallback、cache headers 或 ETag 生成

路由通过 core.App(典型路径)或直接在 router.Router 上注册(需要独立路由器时)。

app.Get("/api/users", http.HandlerFunc(handler.ListUsers))
app.Post("/api/users", http.HandlerFunc(handler.CreateUser))
app.Put("/api/users/:id", http.HandlerFunc(handler.UpdateUser))
app.Delete("/api/users/:id", http.HandlerFunc(handler.DeleteUser))
import "github.com/spcent/plumego/router"
func (h UserHandler) Get(w http.ResponseWriter, r *http.Request) {
id := router.Param(r, "id") // 从匹配路径中提取 :id
}
import "github.com/spcent/plumego/router"
r := router.NewRouter()
api := r.Group("/api/v1")
api.AddRoute("GET", "/users", http.HandlerFunc(handler.ListUsers))
api.AddRoute("POST", "/users", http.HandlerFunc(handler.CreateUser))
api.AddRoute("GET", "/users/:id", http.HandlerFunc(handler.GetUser))
api.AddRoute("DELETE", "/users/:id", http.HandlerFunc(handler.DeleteUser))
import "github.com/spcent/plumego/router"
// 带名称注册
app.AddRoute(http.MethodGet, "/api/users/:id", http.HandlerFunc(handler.GetUser),
router.WithRouteName("user-detail"))
// 反解 URL
url := app.URL("user-detail", "id", "42") // → "/api/users/42"
// 匹配 /files/images/logo.png → filepath = "images/logo.png"
app.Get("/files/*filepath", http.HandlerFunc(handler.ServeFile))
func (h FileHandler) Serve(w http.ResponseWriter, r *http.Request) {
path := router.Param(r, "filepath")
}
r := router.NewRouter()
r.Static("/static", "./public") // 从目录提供
r.StaticFS("/assets", http.FS(embeddedFS)) // 从 fs.FS 提供

路由注册是服务将处理的每个请求的入口映射。如果路由难以 grep,读者就无法追踪请求路径。router 强制执行注册显式且线性的规则:一个方法、一条路径、一个 handler。一旦路由学会了 feature flag、tenant ID 或 plugin catalog,这一约束就消失了。