You need to enable JavaScript to run this app. 如何将Gin中间件的上下文值传递给gqlgen的GraphQL解析器?
优惠活动
大模型
产品
解决方案
定价
更多

如何将Gin中间件的上下文值传递给gqlgen的GraphQL解析器?

问题根因定位
  • 全局变量不具备请求隔离能力,在多并发请求场景下会出现值互相覆盖的问题,完全无法适配HTTP服务的并发处理逻辑
  • 之前编写的var ConstID = middleware.GetUID()属于包初始化阶段执行的代码,此时服务还未启动任何HTTP请求,拿到的永远是全局UID的初始值"dummy",哪怕后续中间件更新了UID,这个变量也不会重新赋值
  • 即使全程用全局UID传递值,高并发时请求A把UID改成用户1的ID,还没等解析器取走,请求B就把UID改成了用户2的ID,最终会出现用户的创建/更新人归属错误,存在严重的安全隐患
正确实现方案(基于Gin+gqlgen官方链路)

1. 改造Token认证中间件,去掉全局变量

完全移除代码里的全局UID变量、GetUID方法,提取到user_id后直接存在Gin的请求上下文里:

func TokenAuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        err := auth.TokenValid(c.Request)
        if err != nil {
            c.JSON(http.StatusUnauthorized, "You need to be authorized to access this route")
            c.Abort()
            return
        }
        // 提取token中的user_id
        userID, err := auth.ExtractTokenID(c.Request)
        if err != nil {
            c.JSON(http.StatusUnauthorized, "Invalid token identity")
            c.Abort()
            return
        }
        // 把user_id存入Gin上下文,和当前请求绑定
        c.Set("user_id", userID)
        c.Next()
    }
}

2. 编写上下文透传中间件,把Gin上下文值传递给GraphQL解析器

gqlgen的解析器只会拿到标准库context.Context,所以需要把存在Gin上下文里的user_id,转到标准请求上下文里,让解析器可以获取:

// 自定义上下文键类型,避免和其他内置/第三方包的键冲突
type contextKey string
const CtxKeyUserID = contextKey("graphql_user_id")

// 透传Gin存储的user_id到标准请求上下文
func TransferUserIDToGraphQL() gin.HandlerFunc {
    return func(c *gin.Context) {
        userID, exists := c.Get("user_id")
        if !exists {
            c.Next()
            return
        }
        // 基于当前请求生成带user_id的新上下文
        ctx := context.WithValue(c.Request.Context(), CtxKeyUserID, userID)
        // 替换原请求的上下文,后续转给gqlgen的就是带值的上下文
        c.Request = c.Request.WithContext(ctx)
        c.Next()
    }
}

3. 路由注册时挂载对应中间件

把认证中间件、上下文透传中间件按顺序挂载到GraphQL的查询路由上:

// 初始化gqlgen服务
graphqlHandler := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolver.Resolver{}}))
// 挂载GraphQL查询接口
r.POST(
    "/query",
    TokenAuthMiddleware(),    // 第一步:校验token,提取user_id
    TransferUserIDToGraphQL(), // 第二步:把user_id透传到GraphQL上下文
    gin.WrapH(graphqlHandler),
)
// GraphiQL Playground如果不需要认证,就单独挂路由即可
r.GET("/playground", gin.WrapH(playground.Handler("GraphQL Playground", "/query")))

4. 在解析器中从上下文取user_id

完全删除包级的ConstID全局变量,在每个需要取用户身份的解析器函数里,直接从传入的ctx参数中读取user_id:

func (r *mutationResolver) CreateFarmer(ctx context.Context, input model.NewFarmer) (*model.Farmer, error) {
    // 从上下文读取当前请求的user_id
    userID, ok := ctx.Value(CtxKeyUserID).(string)
    if !ok {
        return nil, fmt.Errorf("unauthorized: missing valid user identity")
    }

    db := model.FetchConnection()
    defer db.Close()

    now := time.Now()
    farmer := model.Farmer{
        Name:        input.Name,
        Surname:     input.Surname,
        Dob:         input.Dob,
        Fin:         input.Fin,
        PlotLocLat:  input.PlotLocLat,
        PlotLocLong: input.PlotLocLong,
        CreatedAt:   now,
        UpdatedAt:   now,
        CreatedBy:   userID,
        UpdatedBy:   userID,
    }
    db.Create(&farmer)
    return &farmer, nil
}

该方案完全基于请求链路传递身份值,没有任何全局共享变量,每个请求的user_id都是独立隔离的,既不会出现值不更新的问题,也能避免高并发下的身份串用风险。

内容的提问来源于stack exchange,提问作者Helen Grey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 00:06:09