路由设计
问题
Go Web 框架的路由是如何实现的?如何设计 RESTful API 的路由?
答案
路由实现原理
Radix Tree(Gin / Echo)
压缩前缀树,相同前缀的路径共享节点:
注册:/api/users、/api/users/:id、/api/articles
树结构:
root
└── /api/
├── users
│ └── /:id
└── articles
查找过程:请求 /api/users/123 → 从根开始匹配 /api/ → 匹配 users → 匹配 /:id(参数节点)→ 提取 id=123。
标准库 ServeMux(Go 1.22+)
Go 1.22 增强了 http.ServeMux,支持方法和参数路由:
mux := http.NewServeMux()
// Go 1.22 新语法:METHOD /path
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "User: %s", id)
})
mux.HandleFunc("POST /users", createUser)
mux.HandleFunc("GET /files/{path...}", serveFile) // 通配符
RESTful 路由设计
r := gin.Default()
// 资源路由约定
users := r.Group("/api/v1/users")
{
users.GET("", listUsers) // GET /api/v1/users
users.POST("", createUser) // POST /api/v1/users
users.GET("/:id", getUser) // GET /api/v1/users/:id
users.PUT("/:id", updateUser) // PUT /api/v1/users/:id
users.DELETE("/:id", deleteUser) // DELETE /api/v1/users/:id
// 子资源
users.GET("/:id/posts", getUserPosts) // GET /api/v1/users/:id/posts
users.POST("/:id/posts", createUserPost) // POST /api/v1/users/:id/posts
}
路由冲突规则
// Gin 路由优先级:精确路径 > 参数路由 > 通配符
r.GET("/users/me", getCurrentUser) // 精确匹配
r.GET("/users/:id", getUser) // 参数匹配
r.GET("/static/*filepath", serveStatic) // 通配符
// ⚠️ 以下会 panic:路由冲突
r.GET("/users/:id", getUser)
r.GET("/users/:name", getUserByName) // 同一位置不能有两个参数
API 版本控制
v1 := r.Group("/api/v1")
{
v1.GET("/users", listUsersV1)
}
v2 := r.Group("/api/v2")
{
v2.GET("/users", listUsersV2) // 新版 API
}
// 或通过 Header 版本控制
r.GET("/api/users", func(c *gin.Context) {
version := c.GetHeader("API-Version")
switch version {
case "2":
listUsersV2(c)
default:
listUsersV1(c)
}
})
常见面试问题
Q1: Radix Tree 比 HashMap 路由好在哪?
答案:
| 维度 | HashMap | Radix Tree |
|---|---|---|
| 精确匹配 | O(1) | O(k)(k=路径长度) |
| 参数路由 | 不支持 | 支持 :id |
| 通配符 | 不支持 | 支持 *path |
| 内存 | 每个路由一条记录 | 相同前缀共享 |
HashMap 只能做精确匹配,不支持 /users/:id 这种参数路由。Radix Tree 在支持动态路由的同时保持高性能。
Q2: :id 参数路由和 {id} 有什么区别?
答案:只是不同框架的语法:
- Gin / Echo:
:id - net/http (Go 1.22+):
{id} - Chi:
{id}
功能相同,都是匹配路径段并提取参数。