不能直接用全局变量传值,因不线程安全且破坏请求隔离;应使用c.Set()和c.Get()在单次请求的*gin.Context中安全传递数据。

为什么不能直接用全局变量在Gin中间件和Handler间传值
因为Go的goroutine是并发安全的,但全局变量不是线程安全的——多个请求共用同一份内存,A请求写入的值可能被B请求覆盖。更关键的是,Gin的context.Context本身设计就是为单次请求生命周期服务的,强行塞全局变量会破坏请求隔离性,导致数据错乱或panic。
用c.Set()和c.MustGet()传递请求级数据
这是最常用、最符合Gin设计意图的方式。所有数据绑定在*gin.Context上,随请求创建而初始化、随请求结束而销毁。
-
c.Set("user_id", 123)在中间件中存值,支持任意类型(包括struct、map) -
userID, ok := c.Get("user_id")用于安全取值,需类型断言;userID := c.MustGet("user_id").(int)则直接断言,失败会panic,适合确定存在的键 - 键名建议统一定义为常量,比如
const CtxUserID = "user_id",避免字符串硬编码 - 注意:
c.Get()返回interface{},务必做类型检查,否则运行时panic
中间件中如何安全注入并验证上下文数据
典型场景是JWT鉴权后把用户信息注入ctx。别跳过校验步骤,否则后续Handler拿到nil或错误类型会崩溃。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 在中间件末尾必须调用
c.Next(),否则后续Handler不执行 - 推荐先
c.Set()再c.Next(),确保下游能读到 - 若鉴权失败,应直接
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})并return,避免继续执行 - 示例:
func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { c.AbortWithStatusJSON(401, gin.H{"error": "missing token"}) return } userID, err := parseToken(token) if err != nil { c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"}) return } c.Set("user_id", userID) c.Next() } }
跨中间件和Handler共享结构体时的常见坑
很多人想传一个UserInfo结构体,结果在Handler里断言失败——问题往往出在类型定义位置或包导入冲突。
立即学习“go语言免费学习笔记(深入)”;
- 结构体定义必须在所有用到它的文件都能访问的位置,比如
models/user.go,而不是某个中间件内部定义 - 断言时用
u := c.MustGet("user").(*models.UserInfo),注意*是指针,如果Set()时传的是值类型,断言要改成models.UserInfo - 不要在多个中间件里重复
Set()同一个key,后设的会覆盖前设的 - 调试时可加
fmt.Printf("ctx keys: %+v\n", c.Keys)查看当前ctx里有哪些键值对
真正麻烦的不是怎么传,而是谁负责清理、什么时候该校验、类型不一致时怎么报错——这些细节漏掉一个,线上就容易出现难以复现的500。

















