# 第 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/*` 分为两组,给后者单独加一个日志中间件。