StreamBox/docs/04-gin-路由与分组.md

155 lines
3.6 KiB
Markdown

# 第 4 章 Gin 路由与分组
> 学完本章你将:熟练定义路由、处理路径/查询参数、组织分组、设计 RESTful API。
## 4.1 路由基础
Gin 支持所有 HTTP 方法:
```go
r.GET("/ping", handler)
r.POST("/users", handler)
r.PUT("/users/:id", handler)
r.DELETE("/users/:id", handler)
r.PATCH("/users/:id", handler)
r.OPTIONS("/*any", handler)
r.Any("/any", handler) // 匹配任意方法
```
本项目示例:
```go
r.GET("/health", health.Check) // 健康检查
v1 := r.Group("/api/v1")
v1.GET("/ping", pingHandler.Ping) // 分组路由
```
## 4.2 路径参数
```go
// 定义
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id") // "/users/42" => "42"
})
// 多参数
r.GET("/users/:id/books/:bookID", func(c *gin.Context) {
id := c.Param("id")
bookID := c.Param("bookID")
})
// 通配符(匹配剩余路径)
r.GET("/files/*filepath", func(c *gin.Context) {
fp := c.Param("filepath") // "/files/a/b.txt" => "/a/b.txt"
})
```
## 4.3 查询参数与表单
```go
// GET /search?q=gin&page=2
r.GET("/search", func(c *gin.Context) {
q := c.Query("q") // "gin"
page := c.DefaultQuery("page", "1") // 带默认值
arr := c.QueryArray("tag") // ?tag=a&tag=b => [a b]
m := c.QueryMap("filter") // ?filter[name]=a => map[name:a]
})
// POST form
r.POST("/form", func(c *gin.Context) {
name := c.PostForm("name")
file, _ := c.FormFile("upload")
c.SaveUploadedFile(file, "./"+file.Filename)
})
```
## 4.4 分组路由
分组用于版本、权限、模块划分:
```go
v1 := r.Group("/api/v1")
{
v1.GET("/ping", pingHandler.Ping)
v1.GET("/users", listUsers)
v1.POST("/users", createUser)
}
// 嵌套分组
admin := v1.Group("/admin", authMiddleware())
{
admin.GET("/stats", statsHandler)
}
```
本项目 `internal/router/router.go` 即采用分组:`r.GET("/health")` 独立于 `v1.Group("/api/v1")`,便于探活不走鉴权。
## 4.5 RESTful 设计示例
以用户资源为例:
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/v1/users | 列表 |
| POST | /api/v1/users | 创建 |
| GET | /api/v1/users/:id | 详情 |
| PUT | /api/v1/users/:id | 全量更新 |
| PATCH | /api/v1/users/:id | 部分更新 |
| DELETE | /api/v1/users/:id | 删除 |
```go
users := v1.Group("/users")
{
users.GET("", listUsers)
users.POST("", createUser)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
}
```
## 4.6 路由注册实战(对照本项目)
`internal/router/router.go` 完整模式:
```go
func New(cfg *config.Config) *gin.Engine {
gin.SetMode(cfg.Server.Mode)
r := gin.New()
r.Use(middleware.Recovery())
r.Use(middleware.ZapLogger())
r.Use(middleware.Cors(cfg.Cors))
health := handler.NewHealthHandler()
r.GET("/health", health.Check)
pingRepo := repo.NewPingRepo()
pingSvc := service.NewPingService(pingRepo)
pingHandler := handler.NewPingHandler(pingSvc)
v1 := r.Group("/api/v1")
{ v1.GET("/ping", pingHandler.Ping) }
return r
}
```
依赖注入顺序:`repo -> service -> handler -> router`,各层职责清晰,新增模块时复制此模式即可。
## 4.7 重定向与 NoRoute
```go
r.GET("/old", func(c *gin.Context) {
c.Redirect(301, "/new")
})
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"code": 404, "msg": "not found"})
})
```
## 4.8 动手练习
1. 新增 `GET /api/v1/users/:id`,返回 `{"id": "xxx"}`。
2. 实现 `GET /search` 支持 `q` 与 `page` 查询参数,`page` 默认 1。
3. 将 `/health` 与 `/api/v1/*` 分为两组,给后者单独加一个日志中间件。