50·Web 开发进阶

路由库:Chi/Gin

chiginroutergroup

第50课 - 路由库:Chi/Gin

学习目标

通过本课的学习,你将能够:

  1. 理解 Go 中路由库的作用和必要性
  2. 掌握 Chi 和 Gin 两个流行路由库的基本使用方法
  3. 学会使用路由分组(Route Groups)组织代码
  4. 掌握在路由中添加中间件(Middleware)的技巧
  5. 能够根据项目需求选择合适的路由库

核心概念

为什么需要路由库?

Go 标准库的 net/http 提供了基础的 HTTP 路由功能,但在实际项目中会显得力不从心。路由库就像交通指挥中心,帮助你:

  • 清晰地定义 URL 路径和处理函数的映射
  • 支持 RESTful 风格的 URL 设计
  • 提供中间件支持(如日志、认证、限流等)
  • 组织路由代码,保持结构清晰

Chi 路由库

Chi 是一个轻量级、可组合的路由库,强调简洁和性能。它的设计理念是"小而美",适合喜欢灵活组合的开发者。

// Chi 的路由定义风格
r := chi.NewRouter()
r.Get("/users/{id}", getUserHandler)
r.Post("/users", createUserHandler)

Gin 路由库

Gin 是一个更"全栈"的框架,除了路由功能外,还内置了日志、恢复、验证等功能。它性能出色,被广泛使用。

// Gin 的路由定义风格
r := gin.Default()
r.GET("/users/:id", getUserHandler)
r.POST("/users", createUserHandler)

代码示例

1. Chi 路由库基础使用

package main

import (
    "encoding/json"
    "net/http"
    
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

// User 模拟用户结构
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// 模拟用户数据
var users = map[int]User{
    1: {ID: 1, Name: "张三", Email: "[email protected]"},
    2: {ID: 2, Name: "李四", Email: "[email protected]"},
}

// 获取单个用户
func getUser(w http.ResponseWriter, r *http.Request) {
    // 从 URL 中获取参数
    userID := chi.URLParam(r, "id")
    
    // 转换为整数(这里简化处理,实际项目中需要错误处理)
    id := 1 // 模拟转换
    
    if user, exists := users[id]; exists {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(user)
    } else {
        w.WriteHeader(http.StatusNotFound)
        w.Write([]byte("用户不存在"))
    }
}

// 获取所有用户
func getUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    
    userList := make([]User, 0, len(users))
    for _, user := range users {
        userList = append(userList, user)
    }
    
    json.NewEncoder(w).Encode(userList)
}

func main() {
    // 创建路由实例
    r := chi.NewRouter()
    
    // 使用内置中间件
    r.Use(middleware.Logger)    // 日志记录
    r.Use(middleware.Recoverer) // 异常恢复
    
    // 注册路由
    r.Get("/users", getUsers)        // GET /users
    r.Get("/users/{id}", getUser)    // GET /users/{id}
    
    // 启动服务器
    http.ListenAndServe(":8080", r)
}

2. Gin 路由库基础使用

package main

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

// User 结构相同
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// 模拟数据相同
var users = map[int]User{
    1: {ID: 1, Name: "张三", Email: "[email protected]"},
    2: {ID: 2, Name: "李四", Email: "[email protected]"},
}

// 获取单个用户
func getUser(c *gin.Context) {
    // 从 URL 中获取参数
    userID := c.Param("id")
    
    // 转换为整数(这里简化处理)
    id := 1
    
    if user, exists := users[id]; exists {
        c.JSON(http.StatusOK, user)
    } else {
        c.JSON(http.StatusNotFound, gin.H{
            "error": "用户不存在",
        })
    }
}

// 获取所有用户
func getUsers(c *gin.Context) {
    userList := make([]User, 0, len(users))
    for _, user := range users {
        userList = append(userList, user)
    }
    
    c.JSON(http.StatusOK, userList)
}

func main() {
    // 创建 Gin 默认引擎(包含日志和恢复中间件)
    r := gin.Default()
    
    // 注册路由
    r.GET("/users", getUsers)
    r.GET("/users/:id", getUser)
    
    // 启动服务器
    r.Run(":8080")
}

3. 路由分组示例

package main

import (
    "net/http"
    
    "github.com/go-chi/chi/v5"
)

func main() {
    r := chi.NewRouter()
    
    // API v1 路由组
    r.Route("/api/v1", func(r chi.Router) {
        r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("API v1: 用户列表"))
        })
        
        r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
            userID := chi.URLParam(r, "id")
            w.Write([]byte("API v1: 用户 " + userID))
        })
    })
    
    // API v2 路由组
    r.Route("/api/v2", func(r chi.Router) {
        r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("API v2: 用户列表"))
        })
        
        // 嵌套路由组 - 管理员路由
        r.Route("/admin", func(r chi.Router) {
            r.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("API v2 管理员仪表盘"))
            })
        })
    })
    
    http.ListenAndServe(":8080", r)
}

实践练习

练习 1:基础路由实现(简单)

使用 Gin 或 Chi 实现以下 RESTful API:

  • GET /articles - 获取所有文章列表
  • GET /articles/:id - 获取指定 ID 的文章
  • POST /articles - 创建新文章

预期输出:

$ curl localhost:8080/articles
[{"id":1,"title":"Go入门","content":"..."}]

$ curl localhost:8080/articles/1
{"id":1,"title":"Go入门","content":"..."}

练习 2:路由分组与中间件(中等)

创建一个包含以下结构的 API:

  1. 公开路由:/public/*(无需认证)
  2. 需要认证的路由:/private/*(需要检查 API Key)

实现一个简单的认证中间件,检查请求头中的 X-API-Key

预期输出:

$ curl localhost:8080/public/health
{"status":"ok"}

$ curl localhost:8080/private/data -H "X-API-Key: secret123"
{"data":"敏感数据"}

练习 3:完整博客 API(困难)

实现一个简单的博客系统 API,包含:

  • 用户管理(CRUD)
  • 文章管理(CRUD)
  • 评论管理(创建、删除)

要求使用路由分组组织代码,并添加日志中间件。

常见错误

1. 参数获取错误

// 错误:Chi 和 Gin 获取 URL 参数的方法不同
// Chi: chi.URLParam(r, "id")
// Gin: c.Param("id")

// 错误示例(在 Gin 中使用 Chi 的方法)
func getUser(c *gin.Context) {
    // 错误!这是 Chi 的方法
    userID := chi.URLParam(r, "id") // r 未定义
}

2. 路由顺序问题

// 错误:过于宽泛的路由会匹配所有路径
r.Get("/users/*", allUsersHandler)  // 会匹配 /users/123
r.Get("/users/{id}", getUserHandler) // 永远不会被匹配!

// 正确:先注册具体路由,再注册通配符
r.Get("/users/{id}", getUserHandler)
r.Get("/users/*", allUsersHandler)

3. 中间件顺序问题

// 错误:中间件顺序影响执行顺序
r.Use(authMiddleware)      // 先执行认证
r.Use(loggingMiddleware)   // 后执行日志

// 如果认证失败,日志不会记录这个请求
// 通常应该先记录日志,再做认证
r.Use(loggingMiddleware)
r.Use(authMiddleware)

4. 忘记添加错误处理

// 错误:没有错误处理会导致程序崩溃
func getUser(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id")) // 忽略错误
    // 如果转换失败,id 为 0,可能导致意外行为
}

小结

在本课中,我们学习了:

  1. 路由库的价值:提供了比标准库更强大的路由功能、中间件支持和代码组织能力
  2. Chi 与 Gin 的区别
    • Chi:轻量级、可组合、风格简洁
    • Gin:功能更全面、内置常用功能、性能出色
  3. 核心功能
    • 路由定义:GET、POST、PUT、DELETE 等方法
    • 参数获取:URL 参数、查询参数
    • 路由分组:组织代码结构,便于版本管理和权限控制
    • 中间件:请求处理前后的通用逻辑
  4. 最佳实践
    • 先定义具体路由,再定义通配符路由
    • 合理安排中间件顺序
    • 始终添加错误处理
    • 根据项目规模选择合适的库(小项目用 Chi,大项目用 Gin)

掌握了这些路由库,你就能构建结构清晰、易于维护的 Web API 了。下一课我们将学习如何与数据库交互。

练习编辑器

go
Loading...

继续学习

完成本课后,建议继续学习下一课「database/sql 基础」 以巩固所学知识。