6.5. Gin Web 框架

6.5.1. Gin 概述

Gin 是 Go 语言中最流行的 Web 框架,以高性能和简洁 API 著称。

6.5.2. 安装

go get github.com/gin-gonic/gin

6.5.3. 基本用法

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()  // 包含 Logger 和 Recovery 中间件
    
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong"})
    })
    
    r.Run(":8080")
}

6.5.4. 路由

6.5.4.1. HTTP 方法

r.GET("/users", getUsers)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.DELETE("/users/:id", deleteUser)
r.PATCH("/users/:id", patchUser)
r.HEAD("/users", headUsers)
r.OPTIONS("/users", optionsUsers)

// 匹配所有方法
r.Any("/test", handleAny)

6.5.4.2. 路由参数

// 路径参数
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"id": id})
})

// 通配符
r.GET("/files/*filepath", func(c *gin.Context) {
    path := c.Param("filepath")
    c.JSON(200, gin.H{"path": path})
})

// 查询参数
r.GET("/search", func(c *gin.Context) {
    query := c.Query("q")           // ?q=xxx
    page := c.DefaultQuery("page", "1")  // 默认值
    c.JSON(200, gin.H{"query": query, "page": page})
})

6.5.4.3. 路由分组

v1 := r.Group("/api/v1")
{
    v1.GET("/users", getUsers)
    v1.POST("/users", createUser)
}

v2 := r.Group("/api/v2")
{
    v2.GET("/users", getUsersV2)
}

// 带中间件的分组
authorized := r.Group("/admin")
authorized.Use(AuthMiddleware())
{
    authorized.GET("/dashboard", dashboard)
}

6.5.5. 请求处理

6.5.5.1. 绑定请求数据

type CreateUserRequest struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"gte=0,lte=130"`
}

func createUser(c *gin.Context) {
    var req CreateUserRequest
    
    // 绑定 JSON
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 处理请求...
    c.JSON(201, gin.H{"user": req})
}

// 其他绑定方法
c.ShouldBindQuery(&req)       // 查询参数
c.ShouldBindUri(&req)         // 路径参数
c.ShouldBind(&req)            // 自动推断
c.ShouldBindHeader(&req)      // Header

6.5.5.2. 获取请求数据

// Header
auth := c.GetHeader("Authorization")

// Cookie
cookie, _ := c.Cookie("session")

// 表单数据
name := c.PostForm("name")
name := c.DefaultPostForm("name", "default")

// 文件上传
file, _ := c.FormFile("file")
c.SaveUploadedFile(file, "/tmp/"+file.Filename)

// 原始 Body
body, _ := c.GetRawData()

6.5.6. 响应

// JSON
c.JSON(200, gin.H{"status": "ok"})
c.JSON(200, user)  // 结构体

// XML
c.XML(200, gin.H{"status": "ok"})

// YAML
c.YAML(200, gin.H{"status": "ok"})

// String
c.String(200, "Hello %s", name)

// HTML
c.HTML(200, "index.html", gin.H{"title": "Home"})

// 重定向
c.Redirect(301, "/new-path")

// 文件
c.File("/path/to/file")
c.FileAttachment("/path/to/file", "filename.txt")

// 流式响应
c.Stream(func(w io.Writer) bool {
    w.Write([]byte("data"))
    return true  // 继续发送
})

6.5.7. 中间件

6.5.7.1. 内置中间件

// Logger - 日志
r.Use(gin.Logger())

// Recovery - panic 恢复
r.Use(gin.Recovery())

// 静态文件
r.Static("/static", "./public")
r.StaticFile("/favicon.ico", "./public/favicon.ico")

6.5.7.2. 自定义中间件

func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        
        c.Next()  // 处理请求
        
        latency := time.Since(start)
        status := c.Writer.Status()
        
        log.Printf("%s %s %d %v", c.Request.Method, path, status, latency)
    }
}

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
            return
        }
        
        // 验证 token...
        user := validateToken(token)
        c.Set("user", user)  // 设置上下文
        
        c.Next()
    }
}

// 使用
r.Use(LoggerMiddleware())
r.GET("/protected", AuthMiddleware(), protectedHandler)

6.5.7.3. 从上下文获取数据

func protectedHandler(c *gin.Context) {
    user, exists := c.Get("user")
    if !exists {
        c.JSON(500, gin.H{"error": "user not found"})
        return
    }
    c.JSON(200, gin.H{"user": user})
}

6.5.8. ⚠️ 常见陷阱

6.5.8.1. 陷阱 1:goroutine 中使用 Context

// ❌ 在 goroutine 中直接使用 c
func handler(c *gin.Context) {
    go func() {
        time.Sleep(time.Second)
        c.JSON(200, gin.H{})  // 危险!c 可能已失效
    }()
}

// ✅ 复制需要的数据
func handler(c *gin.Context) {
    cCopy := c.Copy()  // 或者只复制需要的值
    go func() {
        time.Sleep(time.Second)
        processAsync(cCopy)
    }()
    c.JSON(200, gin.H{"status": "processing"})
}

6.5.8.2. 陷阱 2:多次写入响应

// ❌ 多次写入
func handler(c *gin.Context) {
    c.JSON(200, gin.H{"first": true})
    c.JSON(200, gin.H{"second": true})  // 无效!
}

// ✅ 只写入一次
func handler(c *gin.Context) {
    c.JSON(200, gin.H{"result": "done"})
    return
}

6.5.8.3. 陷阱 3:Next 之后的代码

// 中间件中 Next 之后的代码在响应后执行
func middleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 请求前
        c.Next()
        // 响应后(可以用于记录日志、清理等)
    }
}

6.5.9. 错误处理

// 全局错误处理
func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if len(c.Errors) > 0 {
            c.JSON(-1, gin.H{
                "errors": c.Errors.Errors(),
            })
        }
    }
}

// 在 handler 中记录错误
func handler(c *gin.Context) {
    if err := doSomething(); err != nil {
        c.Error(err)
        c.AbortWithStatusJSON(500, gin.H{"error": err.Error()})
        return
    }
}

6.5.10. 优雅关闭

func main() {
    r := gin.Default()
    
    server := &http.Server{
        Addr:    ":8080",
        Handler: r,
    }
    
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatal(err)
        }
    }()
    
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    if err := server.Shutdown(ctx); err != nil {
        log.Fatal(err)
    }
}

6.5.11. 参考资源