第50课 - 路由库:Chi/Gin
学习目标
通过本课的学习,你将能够:
- 理解 Go 中路由库的作用和必要性
- 掌握 Chi 和 Gin 两个流行路由库的基本使用方法
- 学会使用路由分组(Route Groups)组织代码
- 掌握在路由中添加中间件(Middleware)的技巧
- 能够根据项目需求选择合适的路由库
核心概念
为什么需要路由库?
Go 标准库的 net/http 提供了基础的 HTTP 路由功能,但在实际项目中会显得力不从心。路由库就像交通指挥中心,帮助你:
- 清晰地定义 URL 路径和处理函数的映射
- 支持 RESTful 风格的 URL 设计
- 提供中间件支持(如日志、认证、限流等)
- 组织路由代码,保持结构清晰
Chi 路由库
Chi 是一个轻量级、可组合的路由库,强调简洁和性能。它的设计理念是"小而美",适合喜欢灵活组合的开发者。
// Chi 的路由定义风格
r := chi.NewRouter()
r.Get("/users/{id}", getUserHandler)
r.Post("/users", createUserHandler)
Gin 路由库
Gin 是一个更"全栈"的框架,除了路由功能外,还内置了日志、恢复、验证等功能。它性能出色,被广泛使用。
// Gin 的路由定义风格
r := gin.Default()
r.GET("/users/:id", getUserHandler)
r.POST("/users", createUserHandler)
代码示例
1. Chi 路由库基础使用
package main
import (
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
// User 模拟用户结构
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// 模拟用户数据
var users = map[int]User{
1: {ID: 1, Name: "张三", Email: "[email protected]"},
2: {ID: 2, Name: "李四", Email: "[email protected]"},
}
// 获取单个用户
func getUser(w http.ResponseWriter, r *http.Request) {
// 从 URL 中获取参数
userID := chi.URLParam(r, "id")
// 转换为整数(这里简化处理,实际项目中需要错误处理)
id := 1 // 模拟转换
if user, exists := users[id]; exists {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
} else {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("用户不存在"))
}
}
// 获取所有用户
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
userList := make([]User, 0, len(users))
for _, user := range users {
userList = append(userList, user)
}
json.NewEncoder(w).Encode(userList)
}
func main() {
// 创建路由实例
r := chi.NewRouter()
// 使用内置中间件
r.Use(middleware.Logger) // 日志记录
r.Use(middleware.Recoverer) // 异常恢复
// 注册路由
r.Get("/users", getUsers) // GET /users
r.Get("/users/{id}", getUser) // GET /users/{id}
// 启动服务器
http.ListenAndServe(":8080", r)
}
2. Gin 路由库基础使用
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// User 结构相同
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// 模拟数据相同
var users = map[int]User{
1: {ID: 1, Name: "张三", Email: "[email protected]"},
2: {ID: 2, Name: "李四", Email: "[email protected]"},
}
// 获取单个用户
func getUser(c *gin.Context) {
// 从 URL 中获取参数
userID := c.Param("id")
// 转换为整数(这里简化处理)
id := 1
if user, exists := users[id]; exists {
c.JSON(http.StatusOK, user)
} else {
c.JSON(http.StatusNotFound, gin.H{
"error": "用户不存在",
})
}
}
// 获取所有用户
func getUsers(c *gin.Context) {
userList := make([]User, 0, len(users))
for _, user := range users {
userList = append(userList, user)
}
c.JSON(http.StatusOK, userList)
}
func main() {
// 创建 Gin 默认引擎(包含日志和恢复中间件)
r := gin.Default()
// 注册路由
r.GET("/users", getUsers)
r.GET("/users/:id", getUser)
// 启动服务器
r.Run(":8080")
}
3. 路由分组示例
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
// API v1 路由组
r.Route("/api/v1", func(r chi.Router) {
r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("API v1: 用户列表"))
})
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "id")
w.Write([]byte("API v1: 用户 " + userID))
})
})
// API v2 路由组
r.Route("/api/v2", func(r chi.Router) {
r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("API v2: 用户列表"))
})
// 嵌套路由组 - 管理员路由
r.Route("/admin", func(r chi.Router) {
r.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("API v2 管理员仪表盘"))
})
})
})
http.ListenAndServe(":8080", r)
}
实践练习
练习 1:基础路由实现(简单)
使用 Gin 或 Chi 实现以下 RESTful API:
GET /articles- 获取所有文章列表GET /articles/:id- 获取指定 ID 的文章POST /articles- 创建新文章
预期输出:
$ curl localhost:8080/articles
[{"id":1,"title":"Go入门","content":"..."}]
$ curl localhost:8080/articles/1
{"id":1,"title":"Go入门","content":"..."}
练习 2:路由分组与中间件(中等)
创建一个包含以下结构的 API:
- 公开路由:
/public/*(无需认证) - 需要认证的路由:
/private/*(需要检查 API Key)
实现一个简单的认证中间件,检查请求头中的 X-API-Key。
预期输出:
$ curl localhost:8080/public/health
{"status":"ok"}
$ curl localhost:8080/private/data -H "X-API-Key: secret123"
{"data":"敏感数据"}
练习 3:完整博客 API(困难)
实现一个简单的博客系统 API,包含:
- 用户管理(CRUD)
- 文章管理(CRUD)
- 评论管理(创建、删除)
要求使用路由分组组织代码,并添加日志中间件。
常见错误
1. 参数获取错误
// 错误:Chi 和 Gin 获取 URL 参数的方法不同
// Chi: chi.URLParam(r, "id")
// Gin: c.Param("id")
// 错误示例(在 Gin 中使用 Chi 的方法)
func getUser(c *gin.Context) {
// 错误!这是 Chi 的方法
userID := chi.URLParam(r, "id") // r 未定义
}
2. 路由顺序问题
// 错误:过于宽泛的路由会匹配所有路径
r.Get("/users/*", allUsersHandler) // 会匹配 /users/123
r.Get("/users/{id}", getUserHandler) // 永远不会被匹配!
// 正确:先注册具体路由,再注册通配符
r.Get("/users/{id}", getUserHandler)
r.Get("/users/*", allUsersHandler)
3. 中间件顺序问题
// 错误:中间件顺序影响执行顺序
r.Use(authMiddleware) // 先执行认证
r.Use(loggingMiddleware) // 后执行日志
// 如果认证失败,日志不会记录这个请求
// 通常应该先记录日志,再做认证
r.Use(loggingMiddleware)
r.Use(authMiddleware)
4. 忘记添加错误处理
// 错误:没有错误处理会导致程序崩溃
func getUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id")) // 忽略错误
// 如果转换失败,id 为 0,可能导致意外行为
}
小结
在本课中,我们学习了:
- 路由库的价值:提供了比标准库更强大的路由功能、中间件支持和代码组织能力
- Chi 与 Gin 的区别:
- Chi:轻量级、可组合、风格简洁
- Gin:功能更全面、内置常用功能、性能出色
- 核心功能:
- 路由定义:GET、POST、PUT、DELETE 等方法
- 参数获取:URL 参数、查询参数
- 路由分组:组织代码结构,便于版本管理和权限控制
- 中间件:请求处理前后的通用逻辑
- 最佳实践:
- 先定义具体路由,再定义通配符路由
- 合理安排中间件顺序
- 始终添加错误处理
- 根据项目规模选择合适的库(小项目用 Chi,大项目用 Gin)
掌握了这些路由库,你就能构建结构清晰、易于维护的 Web API 了。下一课我们将学习如何与数据库交互。
练习编辑器
go
Loading...