路由分组 #

一、路由分组概述 #

路由分组是组织和管理路由的重要方式,可以统一设置前缀和中间件。

1.1 分组的作用 #

text
┌─────────────────────────────────────────────────────────┐
│                    路由分组的作用                        │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 统一路径前缀                                         │
│     /api/v1/users                                       │
│     /api/v1/posts                                       │
│                                                         │
│  2. 统一中间件                                           │
│     所有 /api 路由都需要认证                             │
│                                                         │
│  3. 版本控制                                             │
│     /api/v1/...                                         │
│     /api/v2/...                                         │
│                                                         │
│  4. 模块化组织                                           │
│     按功能模块分组路由                                   │
│                                                         │
└─────────────────────────────────────────────────────────┘

二、基本分组 #

2.1 创建分组 #

go
e := echo.New()

api := e.Group("/api")

api.GET("/users", getUsers)
api.GET("/posts", getPosts)

等价于:

go
e.GET("/api/users", getUsers)
e.GET("/api/posts", getPosts)

2.2 分组路由方法 #

分组支持所有HTTP方法:

go
api := e.Group("/api")

api.GET("/users", getUsers)
api.POST("/users", createUser)
api.PUT("/users/:id", updateUser)
api.DELETE("/users/:id", deleteUser)

2.3 分组与直接注册对比 #

go
// 方式一:直接注册
e.GET("/api/v1/users", getUsers)
e.GET("/api/v1/posts", getPosts)
e.GET("/api/v1/comments", getComments)

// 方式二:使用分组
api := e.Group("/api/v1")
api.GET("/users", getUsers)
api.GET("/posts", getPosts)
api.GET("/comments", getComments)

三、嵌套分组 #

3.1 多层嵌套 #

go
e := echo.New()

api := e.Group("/api")
v1 := api.Group("/v1")
users := v1.Group("/users")

users.GET("", listUsers)
users.GET("/:id", getUser)
users.POST("", createUser)

生成的路由:

text
/api/v1/users
/api/v1/users/:id
/api/v1/users

3.2 版本控制 #

go
e := echo.New()

api := e.Group("/api")

v1 := api.Group("/v1")
v1.GET("/users", getUsersV1)
v1.GET("/posts", getPostsV1)

v2 := api.Group("/v2")
v2.GET("/users", getUsersV2)
v2.GET("/posts", getPostsV2)

3.3 模块分组 #

go
e := echo.New()

api := e.Group("/api/v1")

users := api.Group("/users")
{
    users.GET("", listUsers)
    users.GET("/:id", getUser)
    users.POST("", createUser)
    users.PUT("/:id", updateUser)
    users.DELETE("/:id", deleteUser)
}

posts := api.Group("/posts")
{
    posts.GET("", listPosts)
    posts.GET("/:id", getPost)
    posts.POST("", createPost)
}

comments := api.Group("/comments")
{
    comments.GET("", listComments)
    comments.POST("", createComment)
}

四、分组中间件 #

4.1 分组级别中间件 #

go
e := echo.New()

api := e.Group("/api", middleware.Logger(), middleware.Recover())

api.GET("/users", getUsers)
api.GET("/posts", getPosts)

4.2 嵌套分组中间件 #

go
e := echo.New()

api := e.Group("/api")
api.Use(middleware.Logger())

v1 := api.Group("/v1")
v1.Use(middleware.RateLimiter())

users := v1.Group("/users")
users.Use(middleware.JWT([]byte("secret")))

users.GET("/profile", getProfile)

中间件执行顺序:

text
Logger → RateLimiter → JWT → Handler

4.3 认证分组 #

go
e := echo.New()

public := e.Group("/api/v1")
{
    public.POST("/login", login)
    public.POST("/register", register)
    public.GET("/health", health)
}

protected := e.Group("/api/v1")
protected.Use(middleware.JWT([]byte("secret")))
{
    protected.GET("/profile", getProfile)
    protected.PUT("/profile", updateProfile)
    protected.GET("/users", listUsers)
}

4.4 管理员分组 #

go
e := echo.New()

api := e.Group("/api/v1")

admin := api.Group("/admin")
admin.Use(middleware.JWT([]byte("secret")))
admin.Use(adminOnlyMiddleware)
{
    admin.GET("/users", listAllUsers)
    admin.DELETE("/users/:id", deleteUser)
    admin.GET("/stats", getStats)
}

func adminOnlyMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        user := c.Get("user").(*jwt.Token)
        claims := user.Claims.(jwt.MapClaims)
        
        if claims["role"] != "admin" {
            return echo.NewHTTPError(http.StatusForbidden, "需要管理员权限")
        }
        
        return next(c)
    }
}

五、路由组织最佳实践 #

5.1 按功能模块分组 #

go
func setupRoutes(e *echo.Echo) {
    api := e.Group("/api/v1")
    
    setupUserRoutes(api)
    setupPostRoutes(api)
    setupAuthRoutes(api)
    setupAdminRoutes(api)
}

func setupUserRoutes(g *echo.Group) {
    users := g.Group("/users")
    users.GET("", listUsers)
    users.GET("/:id", getUser)
    users.POST("", createUser)
    users.PUT("/:id", updateUser)
    users.DELETE("/:id", deleteUser)
}

func setupPostRoutes(g *echo.Group) {
    posts := g.Group("/posts")
    posts.GET("", listPosts)
    posts.GET("/:id", getPost)
    posts.POST("", createPost)
    posts.PUT("/:id", updatePost)
    posts.DELETE("/:id", deletePost)
    
    posts.GET("/:id/comments", getComments)
    posts.POST("/:id/comments", createComment)
}

func setupAuthRoutes(g *echo.Group) {
    auth := g.Group("/auth")
    auth.POST("/login", login)
    auth.POST("/register", register)
    auth.POST("/logout", logout)
    auth.POST("/refresh", refreshToken)
}

func setupAdminRoutes(g *echo.Group) {
    admin := g.Group("/admin")
    admin.Use(middleware.JWT([]byte("secret")))
    admin.Use(adminOnlyMiddleware)
    
    admin.GET("/users", listAllUsers)
    admin.GET("/stats", getStats)
    admin.GET("/logs", getLogs)
}

5.2 分离到独立文件 #

routes/user.go

go
package routes

import (
    "github.com/labstack/echo/v4"
)

func SetupUserRoutes(g *echo.Group) {
    users := g.Group("/users")
    users.GET("", listUsers)
    users.GET("/:id", getUser)
    users.POST("", createUser)
    users.PUT("/:id", updateUser)
    users.DELETE("/:id", deleteUser)
}

func listUsers(c echo.Context) error {
    return c.JSON(http.StatusOK, []string{"user1", "user2"})
}

func getUser(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{"id": c.Param("id")})
}

func createUser(c echo.Context) error {
    return c.JSON(http.StatusCreated, map[string]string{"status": "created"})
}

func updateUser(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{"id": c.Param("id")})
}

func deleteUser(c echo.Context) error {
    return c.NoContent(http.StatusNoContent)
}

routes/auth.go

go
package routes

import (
    "github.com/labstack/echo/v4"
)

func SetupAuthRoutes(g *echo.Group) {
    auth := g.Group("/auth")
    auth.POST("/login", login)
    auth.POST("/register", register)
    auth.POST("/logout", logout)
}

main.go

go
package main

import (
    "myapp/routes"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    e := echo.New()
    
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    
    api := e.Group("/api/v1")
    
    routes.SetupUserRoutes(api)
    routes.SetupAuthRoutes(api)
    
    e.Start(":8080")
}

六、版本控制 #

6.1 URL路径版本 #

go
e := echo.New()

v1 := e.Group("/api/v1")
v1.GET("/users", getUsersV1)

v2 := e.Group("/api/v2")
v2.GET("/users", getUsersV2)

6.2 请求头版本 #

go
e := echo.New()

e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        version := c.Request().Header.Get("API-Version")
        c.Set("version", version)
        return next(c)
    }
})

e.GET("/users", func(c echo.Context) error {
    version := c.Get("version").(string)
    
    if version == "v2" {
        return getUsersV2(c)
    }
    
    return getUsersV1(c)
})

6.3 版本废弃处理 #

go
e := echo.New()

v1 := e.Group("/api/v1")
v1.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        c.Response().Header().Set("X-API-Deprecated", "true")
        c.Response().Header().Set("X-API-Sunset", "2024-12-31")
        return next(c)
    }
})
v1.GET("/users", getUsersV1)

v2 := e.Group("/api/v2")
v2.GET("/users", getUsersV2)

七、分组路由信息 #

7.1 获取分组路由 #

go
e := echo.New()

api := e.Group("/api")
api.GET("/users", getUsers)
api.GET("/posts", getPosts)

for _, r := range e.Routes() {
    fmt.Printf("%s %s\n", r.Method, r.Path)
}

7.2 命名分组路由 #

go
e := echo.New()

api := e.Group("/api")
api.GET("/users", getUsers).Name = "users.list"
api.GET("/users/:id", getUser).Name = "users.get"

url := e.Reverse("users.get", "123")
fmt.Println(url)

八、完整示例 #

go
package main

import (
    "net/http"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    e := echo.New()
    
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    e.Use(middleware.CORS())
    
    e.GET("/health", health)
    
    api := e.Group("/api/v1")
    {
        setupAuthRoutes(api)
        setupUserRoutes(api)
        setupPostRoutes(api)
        setupAdminRoutes(api)
    }
    
    e.Logger.Fatal(e.Start(":8080"))
}

func health(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{
        "status": "healthy",
    })
}

func setupAuthRoutes(g *echo.Group) {
    auth := g.Group("/auth")
    auth.POST("/login", login)
    auth.POST("/register", register)
    auth.POST("/logout", logout)
}

func setupUserRoutes(g *echo.Group) {
    users := g.Group("/users")
    users.GET("", listUsers)
    users.GET("/:id", getUser)
    users.POST("", createUser)
    users.PUT("/:id", updateUser)
    users.DELETE("/:id", deleteUser)
    
    users.GET("/:id/posts", getUserPosts)
}

func setupPostRoutes(g *echo.Group) {
    posts := g.Group("/posts")
    posts.GET("", listPosts)
    posts.GET("/:id", getPost)
    posts.POST("", createPost)
    posts.PUT("/:id", updatePost)
    posts.DELETE("/:id", deletePost)
    
    posts.GET("/:id/comments", getComments)
    posts.POST("/:id/comments", createComment)
}

func setupAdminRoutes(g *echo.Group) {
    admin := g.Group("/admin")
    admin.Use(middleware.JWT([]byte("secret")))
    admin.Use(adminOnlyMiddleware)
    
    admin.GET("/users", listAllUsers)
    admin.DELETE("/users/:id", deleteUser)
    admin.GET("/stats", getStats)
    admin.GET("/logs", getLogs)
}

func adminOnlyMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        return next(c)
    }
}

func login(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{"token": "jwt-token"})
}

func register(c echo.Context) error {
    return c.JSON(http.StatusCreated, map[string]string{"status": "created"})
}

func logout(c echo.Context) error {
    return c.NoContent(http.StatusNoContent)
}

func listUsers(c echo.Context) error {
    return c.JSON(http.StatusOK, []map[string]string{
        {"id": "1", "name": "张三"},
        {"id": "2", "name": "李四"},
    })
}

func getUser(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{"id": c.Param("id")})
}

func createUser(c echo.Context) error {
    return c.JSON(http.StatusCreated, map[string]string{"status": "created"})
}

func updateUser(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{"id": c.Param("id")})
}

func deleteUser(c echo.Context) error {
    return c.NoContent(http.StatusNoContent)
}

func getUserPosts(c echo.Context) error {
    return c.JSON(http.StatusOK, []map[string]string{
        {"id": "1", "title": "文章1"},
    })
}

func listPosts(c echo.Context) error {
    return c.JSON(http.StatusOK, []map[string]string{})
}

func getPost(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{"id": c.Param("id")})
}

func createPost(c echo.Context) error {
    return c.JSON(http.StatusCreated, map[string]string{"status": "created"})
}

func updatePost(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{"id": c.Param("id")})
}

func deletePost(c echo.Context) error {
    return c.NoContent(http.StatusNoContent)
}

func getComments(c echo.Context) error {
    return c.JSON(http.StatusOK, []map[string]string{})
}

func createComment(c echo.Context) error {
    return c.JSON(http.StatusCreated, map[string]string{"status": "created"})
}

func listAllUsers(c echo.Context) error {
    return c.JSON(http.StatusOK, []map[string]string{})
}

func getStats(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]int{"users": 100})
}

func getLogs(c echo.Context) error {
    return c.JSON(http.StatusOK, []string{})
}

九、总结 #

路由分组要点:

功能 方法 示例
创建分组 Group() g := e.Group("/api")
分组路由 GET/POST/... g.GET("/users", handler)
分组中间件 Use() g.Use(middleware)
嵌套分组 Group() v1 := g.Group("/v1")

最佳实践:

  1. 按功能模块分组:users、posts、auth等
  2. 版本控制:/api/v1、/api/v2
  3. 中间件分层:公共中间件、认证中间件
  4. 分离路由文件:便于维护

准备好学习中间件了吗?让我们进入下一章!

最后更新:2026-03-28