Context 数据存储

Context 提供了键值存储功能,用于在中间件和处理器之间传递数据。

基本的 Set 和 Get

package main

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

func main() {
    r := gin.Default()
    
    r.Use(func(c *gin.Context) {
        c.Set("appName", "MyApp")
        c.Set("version", "1.0.0")
        c.Next()
    })
    
    r.GET("/info", func(c *gin.Context) {
        appName, exists := c.Get("appName")
        if !exists {
            c.String(http.StatusInternalServerError, "应用名称未设置")
            return
        }
        
        version, _ := c.Get("version")
        
        c.JSON(http.StatusOK, gin.H{
            "app":     appName,
            "version": version,
        })
    })
    
    r.Run(":8080")
}

MustGet 方法

当确定值存在时,可以用 MustGet

r.GET("/must", func(c *gin.Context) {
    appName := c.MustGet("appName").(string)
    c.String(http.StatusOK, "应用: %s", appName)
})

如果值不存在,MustGet 会 panic,所以只在确定值存在时使用。

类型安全的获取方法

Gin 提供了类型安全的获取方法:

r.GET("/typed", func(c *gin.Context) {
    c.Set("name", "张三")
    c.Set("age", 25)
    c.Set("score", 95.5)
    c.Set("active", true)
    c.Set("createdAt", time.Now())
    c.Set("timeout", time.Hour)
    
    name := c.GetString("name")
    age := c.GetInt("age")
    score := c.GetFloat("score")
    active := c.GetBool("active")
    createdAt := c.GetTime("createdAt")
    timeout := c.GetDuration("timeout")
    
    c.JSON(http.StatusOK, gin.H{
        "name":      name,
        "age":       age,
        "score":     score,
        "active":    active,
        "createdAt": createdAt,
        "timeout":   timeout,
    })
})

存储复杂对象

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
    Role  string `json:"role"`
}

func Auth() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        
        user := &User{
            ID:    1,
            Name:  "张三",
            Email: "zhangsan@example.com",
            Role:  "admin",
        }
        
        c.Set("currentUser", user)
        c.Set("userID", user.ID)
        c.Set("userRole", user.Role)
        
        c.Next()
    }
}

r.GET("/profile", Auth(), func(c *gin.Context) {
    user, exists := c.Get("currentUser")
    if !exists {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
        return
    }
    
    u := user.(*User)
    c.JSON(http.StatusOK, u)
})

封装获取方法

封装便捷的获取函数:

func GetCurrentUser(c *gin.Context) (*User, error) {
    val, exists := c.Get("currentUser")
    if !exists {
        return nil, errors.New("用户未登录")
    }
    
    user, ok := val.(*User)
    if !ok {
        return nil, errors.New("用户信息格式错误")
    }
    
    return user, nil
}

func GetUserID(c *gin.Context) (int, error) {
    val, exists := c.Get("userID")
    if !exists {
        return 0, errors.New("用户ID不存在")
    }
    
    id, ok := val.(int)
    if !ok {
        return 0, errors.New("用户ID格式错误")
    }
    
    return id, nil
}

func IsAdmin(c *gin.Context) bool {
    role, exists := c.Get("userRole")
    if !exists {
        return false
    }
    return role == "admin"
}

r.GET("/me", Auth(), func(c *gin.Context) {
    user, err := GetCurrentUser(c)
    if err != nil {
        c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusOK, user)
})

批量设置

func SetRequestContext(c *gin.Context, data map[string]interface{}) {
    for k, v := range data {
        c.Set(k, v)
    }
}

r.Use(func(c *gin.Context) {
    SetRequestContext(c, map[string]interface{}{
        "requestID": uuid.New().String(),
        "startTime": time.Now(),
        "clientIP":  c.ClientIP(),
    })
    c.Next()
})

检查值是否存在

r.GET("/check", func(c *gin.Context) {
    if _, exists := c.Get("currentUser"); exists {
        c.String(http.StatusOK, "已登录")
    } else {
        c.String(http.StatusOK, "未登录")
    }
})

获取所有存储的数据

r.GET("/all", func(c *gin.Context) {
    c.Set("key1", "value1")
    c.Set("key2", "value2")
    
    keys := c.Keys
    
    c.JSON(http.StatusOK, keys)
})

实际应用示例

func RequestID() gin.HandlerFunc {
    return func(c *gin.Context) {
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }
        c.Set("requestID", requestID)
        c.Header("X-Request-ID", requestID)
        c.Next()
    }
}

func Timer() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Set("startTime", time.Now())
        c.Next()
        
        start, _ := c.Get("startTime")
        duration := time.Since(start.(time.Time))
        
        requestID, _ := c.Get("requestID")
        log.Printf("[%s] %s %s - %v",
            requestID,
            c.Request.Method,
            c.Request.URL.Path,
            duration,
        )
    }
}

r.Use(RequestID())
r.Use(Timer())

小结

Context 的数据存储功能是中间件通信的基础。使用 SetGet 存取数据,MustGet 用于确定存在的值。建议封装便捷的获取函数,处理类型断言和错误情况。记住 Context 的数据只在当前请求有效。