You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gin框架路由中使用中间件时如何多次绑定请求Body?

解决Gin框架中二次绑定请求Body出现EOF错误的问题

问题原因

Gin框架里,请求的Body是单向的io.ReadCloser流。第一次调用ShouldBind时(内部会读取整个Body流),流的读取指针会移到末尾。后续再调用BindJSON尝试读取Body时,已无数据可读,因此返回EOF错误。

解决方案

方案1:缓存请求Body并重置流

在中间件中先读取完整的Body内容缓存,重新设置c.Request.Body,让后续绑定操作能再次读取Body数据。同时可将中间件处理后的参数存入Gin Context的Keys中,供Controller直接使用。

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

func (a AkSk) Check(c *gin.Context) {
    // 读取并缓存请求Body
    bodyBytes, err := io.ReadAll(c.Request.Body)
    if err != nil {
        c.JSON(http.StatusUnauthorized, apis.NewResult(http.StatusUnauthorized, false, err.Error(), ""))
        c.Abort()
        return
    }
    // 重置Body流,让后续操作可再次读取
    c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

    // 绑定到paramsDealed
    paramsDealed := map[string]string{}
    if err := c.ShouldBind(&paramsDealed); err != nil {
        c.JSON(http.StatusUnauthorized, apis.NewResult(http.StatusUnauthorized, false, err.Error(), ""))
        c.Abort()
        return
    }
    // 将处理后的参数存入Context,供Controller使用
    c.Set("paramsDealed", paramsDealed)
}

Controller中即可正常执行BindJSON:

func Controller(c *gin.Context) {
    param := params.Params{}
    err := c.BindJSON(&param)
    if err != nil {
        c.JSON(http.StatusBadRequest, apis.NewResult(http.StatusBadRequest, false, err.Error(), map[string]interface{}{}))
        return
    }

    // 如需使用中间件处理后的参数,直接从Context获取
    // paramsDealed, ok := c.Get("paramsDealed")
    // if !ok {
    //     // 处理参数获取失败逻辑
    // }
}

方案2:仅绑定一次,共享参数

如果中间件和Controller需要的参数结构兼容,可在中间件中完成Body读取和绑定,将结果存入Context的Keys中,Controller直接从Keys中获取参数,避免二次绑定操作。

import (
    "bytes"
    "io"
    "net/http"
    "encoding/json"
    "github.com/gin-gonic/gin"
)

func (a AkSk) Check(c *gin.Context) {
    // 读取并缓存请求Body
    bodyBytes, err := io.ReadAll(c.Request.Body)
    if err != nil {
        c.JSON(http.StatusUnauthorized, apis.NewResult(http.StatusUnauthorized, false, err.Error(), ""))
        c.Abort()
        return
    }

    // 绑定到中间件需要的paramsDealed
    paramsDealed := map[string]string{}
    if err := json.Unmarshal(bodyBytes, &paramsDealed); err != nil {
        c.JSON(http.StatusUnauthorized, apis.NewResult(http.StatusUnauthorized, false, err.Error(), ""))
        c.Abort()
        return
    }
    c.Set("paramsDealed", paramsDealed)

    // 同时绑定到Controller需要的参数结构
    var controllerParam params.Params
    if err := json.Unmarshal(bodyBytes, &controllerParam); err != nil {
        c.JSON(http.StatusUnauthorized, apis.NewResult(http.StatusUnauthorized, false, err.Error(), ""))
        c.Abort()
        return
    }
    c.Set("controllerParam", controllerParam)
}

Controller直接获取参数:

func Controller(c *gin.Context) {
    param, ok := c.Get("controllerParam")
    if !ok {
        c.JSON(http.StatusBadRequest, apis.NewResult(http.StatusBadRequest, false, "参数获取失败", map[string]interface{}{}))
        return
    }

    // 类型断言转换为目标结构
    realParam, ok := param.(params.Params)
    if !ok {
        c.JSON(http.StatusBadRequest, apis.NewResult(http.StatusBadRequest, false, "参数类型错误", map[string]interface{}{}))
        return
    }

    // 执行业务逻辑
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 12:48:18