Skip to content

第04章:路由分组与路由组织 ​

本章目标 ​

学完本章后,你将能够:

  1. 使用 r.Group() 创建路由分组
  2. 实现嵌套路由分组(/api/v1/users)
  3. 在分组上应用中间件
  4. 合理拆分路由文件,组织项目结构
  5. 遵循 RESTful 命名规范

前置知识 ​

  • 需要先学习:第03章(路由基础)

4.1 为什么需要路由分组 ​

当你的 API 变多时,没有分组的路由会变成这样:

go
// ❌ 混乱:所有路由平铺,难以管理
r.GET("/api/v1/users", handler)
r.POST("/api/v1/users", handler)
r.GET("/api/v1/users/:id", handler)
r.PUT("/api/v1/users/:id", handler)
r.GET("/api/v1/orders", handler)
r.POST("/api/v1/orders", handler)
// ... 几十个路由,每次都要写 /api/v1/ 前缀

用分组后:

go
// ✅ 清晰:分组管理,前缀只写一次
v1 := r.Group("/api/v1")
{
	v1.GET("/users", handler)
	v1.POST("/users", handler)
	v1.GET("/users/:id", handler)
	v1.PUT("/users/:id", handler)
	
	v1.GET("/orders", handler)
	v1.POST("/orders", handler)
}

4.2 r.Group():创建分组 ​

go
func main() {
	r := gin.Default()

	// 创建分组
	api := r.Group("/api")
	{
		api.GET("/ping", func(c *gin.Context) {
			c.JSON(200, gin.H{"msg": "pong"})
		})
		api.GET("/status", func(c *gin.Context) {
			c.JSON(200, gin.H{"status": "ok"})
		})
	}
	// 实际路由:/api/ping 和 /api/status

	r.Run(":8080")
}

4.3 嵌套分组 ​

go
r := gin.Default()

// 第一层:/api
api := r.Group("/api")
{
	// 第二层:/api/v1
	v1 := api.Group("/v1")
	{
		// 完整路径:/api/v1/users
		v1.GET("/users", listUsers)
		
		// 第三层嵌套
		admin := v1.Group("/admin")
		{
			// 完整路径:/api/v1/admin/users
			admin.GET("/users", adminListUsers)
		}
	}
	
	// 第二层:/api/v2
	v2 := api.Group("/v2")
	{
		// 完整路径:/api/v2/users
		v2.GET("/users", listUsersV2)
	}
}

4.4 分组上应用中间件 ​

中间件可以加在分组上,只对该分组生效:

go
r := gin.Default()

// 需要认证的分组
auth := r.Group("/api")
auth.Use(AuthMiddleware())  // 这个分组下的所有路由都需要认证
{
	auth.GET("/profile", getProfile)
	auth.PUT("/profile", updateProfile)
	auth.GET("/orders", listOrders)
}

// 公开的分组(不需要认证)
public := r.Group("/api")
{
	public.GET("/articles", listArticles)
	public.GET("/articles/:id", getArticle)
}

4.5 多文件路由组织 ​

当路由变多时,应该把路由拆分到不同文件中:

推荐目录结构 ​

gin-project/
├── main.go              # 入口,组装路由
├── go.mod
├── config/
│   └── config.go        # 配置
├── handlers/            # 处理函数(Controller)
│   ├── user.go
│   ├── order.go
│   └── article.go
├── middlewares/         # 中间件
│   ├── auth.go
│   └── logger.go
├── models/              # 数据模型
│   ├── user.go
│   └── order.go
└── routes/              # 路由注册
    └── router.go

routes/router.go ​

go
package routes

import (
	"gin-project/handlers"
	"gin-project/middlewares"
	"github.com/gin-gonic/gin"
)

func SetupRouter() *gin.Engine {
	r := gin.Default()

	// 公开路由
	public := r.Group("/api")
	{
		public.POST("/login", handlers.Login)
		public.POST("/register", handlers.Register)
	}

	// 需要认证的路由
	protected := r.Group("/api")
	protected.Use(middlewares.Auth())
	{
		// 用户
		protected.GET("/users", handlers.ListUsers)
		protected.GET("/users/:id", handlers.GetUser)
		protected.PUT("/users/:id", handlers.UpdateUser)
		protected.DELETE("/users/:id", handlers.DeleteUser)

		// 订单
		protected.GET("/orders", handlers.ListOrders)
		protected.POST("/orders", handlers.CreateOrder)
	}

	return r
}

main.go ​

go
package main

import "gin-project/routes"

func main() {
	r := routes.SetupRouter()
	r.Run(":8080")
}

handlers/user.go ​

go
package handlers

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func ListUsers(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"users": []string{"张三", "李四", "王五"},
	})
}

func GetUser(c *gin.Context) {
	id := c.Param("id")
	c.JSON(http.StatusOK, gin.H{"id": id, "name": "张三"})
}

func UpdateUser(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"msg": "更新成功"})
}

func DeleteUser(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"msg": "删除成功"})
}

4.6 路由命名规范 ​

规范示例说明
资源用复数/users, /orders避免单复数混用
层级表示关系/users/:id/orders用户的订单
版本号前缀/api/v1/users版本控制
小写 + 连字符/user-profiles不用驼峰或下划线
查询参数过滤/users?status=active筛选用参数
避免动词❌ /getUsers动词在 HTTP 方法里

常见错误 ​

错误1:分组路径末尾多加了斜杠 ​

go
// ❌ 分组路径末尾有 /
v1 := r.Group("/api/v1/")

// 注册路由时又加了 /
v1.GET("/users", handler)
// 实际路径:/api/v1//users (双斜杠!)

错误2:所有代码堆在 main.go ​

go
// ❌ main.go 几千行,谁都看不懂
func main() {
	r := gin.Default()
	r.GET("/users", func(c *gin.Context) { ... })  // 100行
	r.POST("/users", func(c *gin.Context) { ... }) // 100行
	// ... 50个路由,5000行代码
}
// 拆分成 routes/ + handlers/ + models/

错误3:忘记分组上的中间件会影响所有子路由 ​

go
// auth 分组的中间件会影响下面所有路由
auth := r.Group("/api")
auth.Use(AuthMiddleware())
{
	auth.GET("/profile", handler)
	auth.POST("/logout", handler)
	// 这两个接口都需要认证
}
// 如果 logout 不需要认证,应该放到别的分组

本章小结 ​

  • r.Group("/prefix") 创建路由分组,避免重复写前缀
  • 嵌套分组实现 /api/v1/users 这样的多层路径
  • group.Use(middleware) 给分组加中间件
  • 推荐目录:routes/ + handlers/ + middlewares/ + models/
  • RESTful 命名:复数、层级关系、版本前缀

练习题 ​

  1. 创建一个项目,包含 /api/v1 和 /api/v2 两个版本的路由分组。
  2. 在 /api/v1/admin 分组上添加一个模拟的认证中间件。
  3. 将路由拆分成 routes/router.go 和 handlers/ 两个包。
  4. 设计一个博客系统的完整路由结构(文章、评论、用户),写出路由注册代码。
  5. (思考题)API 版本控制除了 URL 前缀(/v1/),还有什么方式?各有什么优劣?

把零散知识,整理成可以反复走的路。