3.6 KiB
3.6 KiB
第 4 章 Gin 路由与分组
学完本章你将:熟练定义路由、处理路径/查询参数、组织分组、设计 RESTful API。
4.1 路由基础
Gin 支持所有 HTTP 方法:
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) // 匹配任意方法
本项目示例:
r.GET("/health", health.Check) // 健康检查
v1 := r.Group("/api/v1")
v1.GET("/ping", pingHandler.Ping) // 分组路由
4.2 路径参数
// 定义
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 查询参数与表单
// 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 分组路由
分组用于版本、权限、模块划分:
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 | 删除 |
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 完整模式:
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
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 动手练习
- 新增
GET /api/v1/users/:id,返回{"id": "xxx"}。 - 实现
GET /search支持q与page查询参数,page默认 1。 - 将
/health与/api/v1/*分为两组,给后者单独加一个日志中间件。