Gin中间件中的Context扩展
中间件是扩展Context功能的最佳位置,通过中间件可以为请求添加通用能力。
Context扩展模式
1. 值注入模式
Go
func InjectUserMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
user, err := parseToken(token)
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
// 注入用户信息到Context
c.Set("userID", user.ID)
c.Set("user", user)
c.Set("role", user.Role)
c.Next()
}
}
2. 请求增强模式
Go
func RequestEnhancementMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 添加TraceID
traceID := c.GetHeader("X-Trace-ID")
if traceID == "" {
traceID = uuid.New().String()
}
c.Set("traceID", traceID)
// 添加请求开始时间
c.Set("startTime", time.Now())
// 增强请求Context
ctx := context.WithValue(c.Request.Context(), "traceID", traceID)
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
3. Context包装模式
Go
type EnhancedContext struct {
*gin.Context
User *User
TraceID string
}
func EnhanceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
enhanced := &EnhancedContext{
Context: c,
TraceID: uuid.New().String(),
}
// 从token解析用户
if user, err := getUserFromToken(c); err == nil {
enhanced.User = user
}
// 替换Context(需自定义处理)
// Gin不支持直接替换,使用Set存储
c.Set("enhanced", enhanced)
c.Next()
}
}
高级扩展场景
认证授权扩展
Go
func RBACMiddleware(requiredPermissions ...string) gin.HandlerFunc {
return func(c *gin.Context) {
user, _ := c.Get("user")
u := user.(*User)
// 检查权限
permissions, err := getPermissions(u.ID)
if err != nil {
c.AbortWithStatusJSON(500, gin.H{"error": "permission check failed"})
return
}
c.Set("permissions", permissions)
for _, req := range requiredPermissions {
if !containsPermission(permissions, req) {
c.AbortWithStatusJSON(403, gin.H{"error": "forbidden"})
return
}
}
c.Next()
}
}
// 使用
r.GET("/admin", RBACMiddleware("admin:read"), handler)
请求追踪扩展
Go
func TracingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
traceID := c.GetHeader("X-Trace-ID")
if traceID == "" {
traceID = generateTraceID()
}
spanID := generateSpanID()
// 存储追踪信息
c.Set("traceID", traceID)
c.Set("spanID", spanID)
// 设置响应头
c.Header("X-Trace-ID", traceID)
c.Header("X-Span-ID", spanID)
// 记录请求开始
log.Printf("[%s] Request started: %s %s", traceID, c.Request.Method, c.Request.URL.Path)
c.Next()
// 记录请求结束
log.Printf("[%s] Request completed: status=%d", traceID, c.Writer.Status())
}
}
性能监控扩展
Go
func MetricsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
duration := time.Since(start)
status := c.Writer.Status()
// 记录指标
metrics.RecordRequest(path, c.Request.Method, status, duration)
}
}
Context扩展最佳实践
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 用户信息 | c.Set("user", user) | 中间件解析,处理器获取 |
| 追踪信息 | c.Set + Header | 请求头传递,响应头返回 |
| 超时控制 | WithContext | 标准库Context传播 |
| 权限信息 | c.Set("perms", perms) | 避免重复查询 |
类型安全访问封装
Go
// 定义上下文键
type ctxKey string
const (
UserKey ctxKey = "user"
TraceKey ctxKey = "traceID"
)
// 类型安全的获取方法
func GetUser(c *gin.Context) (*User, error) {
val, exists := c.Get(string(UserKey))
if !exists {
return nil, ErrUserNotFound
}
user, ok := val.(*User)
if !ok {
return nil, ErrInvalidUserType
}
return user, nil
}
func SetUser(c *gin.Context, user *User) {
c.Set(string(UserKey), user)
}
注意:Context值仅在同请求内有效,不要在goroutine中异步使用。
要点总结
- 中间件是Context扩展的最佳位置
- 使用c.Set/c.Get存储和获取请求范围数据
- 结合标准库context.WithValue传播到下游调用
- 封装类型安全的访问方法避免断言错误
- 追踪、认证、监控是常见扩展场景
📝 发现内容有误?点击此处直接编辑