Gin框架基础篇002_获取/绑定请求参数

根据请求参数上送的形式,可以将参数获取分成以下几种类型:

  • 从请求头中获取参数
  • 从Cookie中获取参数
  • 从Query字符串中获取参数
  • 从URL中获取参数
  • 从Body中获取参数

1. 从请求头中获取参数

从请求头中获取参数的方法有如下几种:

  • c.GetHeader方法:获取指定请求头的单个值,如果一个请求头有多个值,默认返回第一个。
  • c.Request.Header方法:从上下文中拿到原Request并从请求头中取值。
  • ShouldBindHeaderBindHeader方法:绑定请求头信息到结构体。

假设我们的请求上送格式如下:

http 复制代码
GET /param/header HTTP/1.1
Host: 127.0.0.1:8080
Roles: admin
Roles: super
Roles: developer
User-Name: chinehe

其中Roles请求头对应多个值,User-Name请求头对应一个值。

则获取请求头参数的代码如下:

  • c.GetHeader方法:

    go 复制代码
    // 处理方法
    func HeaderParam(c *gin.Context) {
    	roles := c.GetHeader("Roles")
    	userName := c.GetHeader("User-Name")
    	c.JSON(200, gin.H{
    		"roles":    roles,
    		"userName": userName,
    	})
    }
    
    // 输出结果
    {
        "roles": "admin", // 当请求头对应多个值时,c.GetHeader只会返回第一个
        "userName": "chinehe"
    }
  • c.Request.Header方法:

    • c.Request.Header.Get方法:获取单个值

      go 复制代码
      // 处理方法
      func HeaderParam(c *gin.Context) {
      	header := c.Request.Header
      	roles := header.Get("Roles")
      	userName := header.Get("User-Name")
      	c.JSON(200, gin.H{
      		"roles":    roles,
      		"userName": userName,
      	})
      }
      
      // 输出结果
      {
          "roles": "admin", // 当请求头对应多个值时,c.Request.Header.Get只会返回第一个
          "userName": "chinehe"
      }
    • c.Request.Header.Values方法:获取值数组

      go 复制代码
      // 处理方法
      func HeaderParam(c *gin.Context) {
      	header := c.Request.Header
      	roles := header.Values("Roles") // 获取单个值
      	userName := header.Get("User-Name") // 获取值数组
      	c.JSON(200, gin.H{
      		"roles":    roles,
      		"userName": userName,
      	})
      }
      
      // 输出
      {
          "roles": [ // c.Request.Header.Values方法获取值数组
              "admin",
              "super",
              "developer"
          ],
          "userName": "chinehe" // c.Request.Header.Get方法获取单个值
      }
  • ShouldBindHeader方法

    go 复制代码
    // 处理方法
    func HeaderParam(c *gin.Context) {
      // 接收参数的结构体
      // header tag标识请求头名称(大小写不敏感),json tag标识json序列化时的字段名称
    	headerInfo := struct {
    		Roles    []string `header:"roles" json:"roles"`
    		UserName string `header:"user-name" json:"userName"`
    	}{}
    	err := c.ShouldBindHeader(&headerInfo)
    	if err != nil {
    		c.JSON(400, gin.H{
    			"message": err.Error(),
    		})
    	}
    	c.JSON(200, headerInfo)
    }
    
    // 输出结果
    {
        "roles": [
            "admin",
            "super",
            "developer"
        ],
        "userName": [
            "chinehe"
        ]
    }
  • BindHeader:与ShouldBindHeader差不多,区别在于如果绑定参数出错,会中止请求并应答400。

2. 从Cookie中获取参数

从请求头中获取参数的方法有如下几种:

  • c.Cookie方法:获取指定名称的单个Cookie值,如果不存在会报错。

  • c.Request中获取:

    • c.Request.Cookies:获取请求中给所有Cookie。

    • c.Request.CookieNamed:获取请求中指定名称的所有Cookie。

      HTTP Cookie 标准规范

      • RFC 6265 规范:

        • 在同一 Domain 和 Path 下,不应该存在多个同名的Cookie
        • 当服务器通过 Set-Cookie 设置同名Cookie时,新Cookie会覆盖旧Cookie
      • 浏览器行为:

        • 大多数现代浏览器会自动去重,只保留最后一个同名Cookie
        • 即使手动设置多个同名Cookie,最终也只会有一个生效

      为什么Gin仍提供多值支持?

      尽管标准不推荐,但 CookiesNamed 的设计考虑了以下情况:

      • 兼容历史系统: 某些旧系统可能产生不符合规范的Cookie
      • 调试需求: 开发过程中可能需要检查所有接收到的Cookie数据
      • 扩展性: 为未来可能的规范变化预留接口

      所以虽然理论上不建议使用同名多值Cookie,但框架仍提供了相应的解析能力以应对各种边界情况。

    • c.Request.Cookie

  • 直接从请求头中获取:Cookie本质上也不过是名为Cookie的请求头,我们自然可以直接从请求头中获取cookie信息。

假设我们的请求上送格式如下:

http 复制代码
GET /param/header HTTP/1.1
Host: 127.0.0.1:8080
Cookie: roles=admin,super,developer; username=john_doe

其中roles请求头对应多个值(以逗号分隔),username请求头对应一个值。

则获取cookie参数的示例代码如下:

  • c.Cookie方法

    go 复制代码
    func CookieParam(c *gin.Context) {
    	userName, _ := c.Cookie("username") // 忽略错误
    	roles, _ := c.Cookie("roles") // 忽略错误
    	c.JSON(200, gin.H{
    		"username": userName,
    		"roles":    roles,
    	})
    }
    
    // 输出
    {
        "roles": "super,admin,develop",
        "username": "chinehe"
    }
  • c.Request中获取:

    • c.Request.Cookies

      go 复制代码
      func CookieParam(c *gin.Context) {
      	req := c.Request
      	// 获取所有cookie
      	cookies := req.Cookies()
      
      	// 遍历获取cookie
      	res := make(map[string]string)
      	for _, cookie := range cookies {
      		res[cookie.Name] = cookie.Value
      	}
      	
      	c.JSON(200, res)
      }
      
      // 输出
      {
          "roles": "super,admin,develop",
          "username": "chinehe"
      }
    • c.Request.CookieNamed

      go 复制代码
      func CookieParam(c *gin.Context) {
      	req := c.Request
      	
      	// 仅供演示用
      	// 实际不要这么写哦
      	userName := req.CookiesNamed("username")[0]
      	roles := req.CookiesNamed("roles")[0]
      	
      	c.JSON(200, gin.H{
      		"username": userName,
      		"roles":    roles,
      	})
      }
      
      // 输出
      {
          "roles": "super,admin,develop",
          "username": "chinehe"
      }
    • c.Request.Cookie

      go 复制代码
      func CookieParam(c *gin.Context) {
      	req := c.Request
      
      	// 获取Cookie
      	userNameCookie, _ := req.Cookie("username") // 忽略错误
      	rolesCookie, _ := req.Cookie("roles") // 忽略错误
      
      	c.JSON(200, gin.H{
      		"username": userNameCookie.Value, // 获取到的是*Cookie类型
      		"roles":    rolesCookie.Value, // 获取到的是*Cookie类型
      	})
      }
      
      // 输出
      {
          "roles": "super,admin,develop",
          "username": "chinehe"
      }
  • 直接从请求头中获取:不常用,略。可以参考《从请求头中获取参数》章节。

3. 从Query字符串中获取参数

从Query字符串中获取参数的方式如下:

  • 从上下文简单获取
    • c.Query方法:获取指定key的query参数值,如果有多个就返回第一个,如果不存在就返回空字符串。
    • c.QueryArray方法:获取指定key的query参数列表,如果不存在就返回空数组。
    • c.QueryMap方法:获取指定key的query参数Map,如果不存在就返回空Map。
    • c.DefaultQuery方法:获取指定key的query参数值,如果有多个就返回第一个,如果不存在就返回参数中的默认值。
    • c.GetQuery方法:方法:获取指定key的query参数值和参数是否存在的标识,如果有多个就返回第一个,如果不存在就返回空字符串和false。
    • c.GetQueryArray方法:获取指定key的query参数列表和参数是否存在的标识,如果不存在就返回空数组和false。
    • c.GetQueryMap方法:获取指定key的query参数Map和参数是否存在的标识,如果不存在就返回空Map和false。
  • 从上下文绑定到结构体
    • c.BindQuery方法:绑定query参数到结构体,如果出错就中止请求并返回400。
    • c.ShouldBindQuery方法:绑定query参数到结构体,方法会返回错误信息。
  • 直接从请求中获取
    • c.Request.URL:从URL属性中获取值

假设HTTP请求如下:

http 复制代码
GET /param/query?username=chinehe&roles=super&roles=admin&roles=develop&info[age]=18&info[nick]=CCCCC HTTP/1.1
Host: 127.0.0.1:8080

示例代码:

  • 从上下文简单获取

    • c.Queryc.QueryArrayc.QueryMap

      go 复制代码
      func QueryParam(c *gin.Context) {
      	userName := c.Query("username") // 获取字符串信息
      	roles := c.QueryArray("roles") // 获取字符串数组信息
      	info := c.QueryMap("info") // 获取map信息
      
      	c.JSON(200, gin.H{
      		"username": userName,
      		"roles":    roles,
      		"info":     info,
      	})
      }
      
      // 输出
      {
          "info": {
              "age": "18",
              "nick": "CCCCC"
          },
          "roles": [
              "super",
              "admin",
              "develop"
          ],
          "username": "chinehe"
      }
    • c.GetQueryc.GetQueryArrayc.GetQueryMap

      go 复制代码
      func QueryParam(c *gin.Context) {
        // 演示代码都忽略返回的标识符
      	userName, _ := c.GetQuery("username") // 获取字符串信息
      	roles, _ := c.GetQueryArray("roles") // 获取字符串数组信息
      	info, _ := c.GetQueryMap("info") // 获取map信息
      
      	c.JSON(200, gin.H{
      		"username": userName,
      		"roles":    roles,
      		"info":     info,
      	})
      }
      
      // 输出
      {
          "info": {
              "age": "18",
              "nick": "CCCCC"
          },
          "roles": [
              "super",
              "admin",
              "develop"
          ],
          "username": "chinehe"
      }
    • c.DefaultQuery

      go 复制代码
      // 仅演示用
      func QueryParam(c *gin.Context) {
        // 都以字符串参数的形式获取,并设置默认值
      	userName := c.DefaultQuery("username", "default-username")
      	roles := c.DefaultQuery("roles", "default-roles")
      	info := c.DefaultQuery("info", "default-info")
      
      	c.JSON(200, gin.H{
      		"username": userName,
      		"roles":    roles,
      		"info":     info,
      	})
      }
      // 输出
      {
          "info": "default-info", // 由于参数中不存在info,采用了默认值
          "roles": "super", // 参数值为数组,该方法自动返回了第一个值
          "username": "chinehe" 
      }
  • 从上下文绑定到结构体

    结构体tag为form

    • c.BindQuery方法

      go 复制代码
      func QueryParam(c *gin.Context) {
      	queryParam := struct {
      		UserName string `form:"username" json:"username"`
      		Roles    []string `form:"roles" json:"roles"`
      		Info     struct {
      			Age  int    `form:"info[age]" json:"age"` // 俺也不知道为什么不支持json那种嵌套的写法
      			Nick string `form:"info[nick]" json:"nick"`
      		} `json:"info"`
      	}{}
      	_ = c.BindQuery(&queryParam)
      	c.JSON(200, queryParam)
      }
      
      // 输出
      {
          "username": "chinehe",
          "roles": [
              "super",
              "admin",
              "develop"
          ],
          "info": {
              "age": 18,
              "nick": "CCCC"
          }
      }
    • c.ShouldBindQuery方法

      go 复制代码
      func QueryParam(c *gin.Context) {
      	queryParam := struct {
      		UserName string   `form:"username" json:"username"`
      		Roles    []string `form:"roles" json:"roles"`
      		Info     struct {
      			Age  int    `form:"info[age]" json:"age"`
      			Nick string `form:"info[nick]" json:"nick"`
      		} `json:"info"`
      	}{}
      	err := c.ShouldBindQuery(&queryParam)
      	if err != nil {
      		c.JSON(400, gin.H{
      			"message": err.Error(),
      		})
      	}
      	c.JSON(200, queryParam)
      }
      
      // 输出
      {
          "username": "chinehe",
          "roles": [
              "super",
              "admin",
              "develop"
          ],
          "info": {
              "age": 18,
              "nick": "CCCC"
          }
      }
  • 直接从请求中获取

    go 复制代码
    func QueryParam(c *gin.Context) {
    	rawQuery := c.Request.URL.RawQuery // 获取原始query字符串
    	values := c.Request.URL.Query() // 获取url.Values
    	c.JSON(200, gin.H{
    		"rawQuery": rawQuery,
    		"values":   values,
    	})
    }
    
    // 输出
    {
        "rawQuery": "username=chinehe&roles=super&roles=admin&roles=develop&info[age]=18&info[nick]=CCCC",
        "values": {
            "info[age]": [
                "18"
            ],
            "info[nick]": [
                "CCCC"
            ],
            "roles": [
                "super",
                "admin",
                "develop"
            ],
            "username": [
                "chinehe"
            ]
        }
    }

4. 从URL中获取参数

gin支持定义包含动态参数的路由路径~

4.1. 动态参数路由

gin支持以下方式的动态路由参数

  • 单个路径参数:匹配单个路径段,不包含/。

    go 复制代码
    paramRouter.GET("/url/:username", handler.URLParam)
  • 多个路径参数:匹配多个单路径段,不包含/

    go 复制代码
    paramRouter.GET("/url/:username/:userid", handler.URLParam)
  • 通配符参数:匹配剩余所有路径内容,包含/。通配符参数只允许在路由路径的末尾。

    go 复制代码
    paramRouter.GET("/url/path/*path", handler.URLParam)
  • 混合使用

    go 复制代码
    paramRouter.GET("/url/mix/:username/:userid/*path", handler.URLParam)

4.2. 获取路径参数的方式

从URL中获取路径参数的方式如下:

  • c.Param方法:获取指定名称的路径参数。
  • c.Params属性:所有路径参数,路径参数是有序的。
    • 遍历路径参数数组
    • c.Params.Get方法:返回指定名称的路径参数和一个是否存在的标识符。
    • c.Params.ByName方法:返回指定名称的路径参数,如果不存在返回空字符串。
  • c.BindUri方法:绑定query参数到结构体,如果出错就中止请求并返回400。
  • c.ShouldBindUri方法:绑定query参数到结构体,方法会返回错误信息。

4.3. 示例

下面的示例都使用相同的路由定义:

go 复制代码
// 下面的路由都使用了相同的处理器URLParam
paramRouter.GET("/url/:username", handler.URLParam) // 一个路径参数
paramRouter.GET("/url/:username/:userid", handler.URLParam) // 两个单路径参数
paramRouter.GET("/url/path/*path", handler.URLParam) // 通配符参数
paramRouter.GET("/url/mix/:username/:userid/*path", handler.URLParam) // 两个单路径参数+通配符参数
  • c.Param方法

    go 复制代码
    func URLParam(c *gin.Context) {
    	userName := c.Param("username")
    	userID := c.Param("userid")
    	path := c.Param("path")
    	c.JSON(200, gin.H{
    		"userName": userName,
    		"userID":   userID,
    		"path":     path,
    	})
    }
    
    // 访问/url/chinehe
    {
        "path": "",
        "userID": "",
        "userName": "chinehe"
    }
    
    // 访问/url/chinehe/001
    {
        "path": "",
        "userID": "001",
        "userName": "chinehe"
    }
    // 访问/url/path/mypath/path
    {
        "path": "/mypath/path",
        "userID": "",
        "userName": ""
    }
    // 访问/url/mix/chinehe/001/mypath/path
    {
        "path": "/mypath/path",
        "userID": "001",
        "userName": "chinehe"
    }
  • c.Params属性

    • 遍历路径参数数组

      go 复制代码
      func URLParam(c *gin.Context) {
      	paramMap := make(map[string]string)
      	for _, param := range c.Params {
      		paramMap[param.Key] = param.Value
      	}
      	c.JSON(200, paramMap)
      }
      
      
      // 访问/url/chinehe
      {
          "path": "",
          "userid": "",
          "username": "chinehe"
      }
      
      // 访问/url/chinehe/001
      {
          "path": "",
          "userid": "001",
          "username": "chinehe"
      }
      // 访问/url/path/mypath/path
      {
          "path": "/mypath/path",
          "userid": "",
          "username": ""
      }
      // 访问/url/mix/chinehe/001/mypath/path
      {
          "path": "/mypath/path",
          "userid": "001",
          "username": "chinehe"
      }
    • c.Params.Get方法

      go 复制代码
      func URLParam(c *gin.Context) {
        // 忽略标识符
      	username, _ := c.Params.Get("username")
      	userid, _ := c.Params.Get("userid")
      	path, _ := c.Params.Get("path")
      	c.JSON(200, gin.H{
      		"userName": username,
      		"userID":   userid,
      		"path":     path,
      	})
      }
      
      
      // 访问/url/chinehe
      {
          "path": "",
          "userID": "",
          "userName": "chinehe"
      }
      
      // 访问/url/chinehe/001
      {
          "path": "",
          "userID": "001",
          "userName": "chinehe"
      }
      // 访问/url/path/mypath/path
      {
          "path": "/mypath/path",
          "userID": "",
          "userName": ""
      }
      // 访问/url/mix/chinehe/001/mypath/path
      {
          "path": "/mypath/path",
          "userID": "001",
          "userName": "chinehe"
      }
    • c.Params.ByName方法

      go 复制代码
      func URLParam(c *gin.Context) {
      	username := c.Params.ByName("username")
      	userid := c.Params.ByName("userid")
      	path := c.Params.ByName("path")
      	c.JSON(200, gin.H{
      		"userName": username,
      		"userID":   userid,
      		"path":     path,
      	})
      }
      
      
      // 访问/url/chinehe
      {
          "path": "",
          "userID": "",
          "userName": "chinehe"
      }
      
      // 访问/url/chinehe/001
      {
          "path": "",
          "userID": "001",
          "userName": "chinehe"
      }
      // 访问/url/path/mypath/path
      {
          "path": "/mypath/path",
          "userID": "",
          "userName": ""
      }
      // 访问/url/mix/chinehe/001/mypath/path
      {
          "path": "/mypath/path",
          "userID": "001",
          "userName": "chinehe"
      }
  • c.ShouldBindUri方法

    go 复制代码
    func URLParam(c *gin.Context) {
    	param := struct {
    		UserName string `uri:"username" json:"userName"`
    		UserID   string `uri:"userid" json:"userID"`
    		Path     string `uri:"path" json:"path"`
    	}{}
    	err := c.ShouldBindUri(&param)
    	if err != nil {
    		c.JSON(400, gin.H{
    			"message": err.Error(),
    		})
    	}
    	c.JSON(200, param)
    }
    
    
    // 访问/url/chinehe
    {
        "path": "",
        "userID": "",
        "userName": "chinehe"
    }
    
    // 访问/url/chinehe/001
    {
        "path": "",
        "userID": "001",
        "userName": "chinehe"
    }
    // 访问/url/path/mypath/path
    {
        "path": "/mypath/path",
        "userID": "",
        "userName": ""
    }
    // 访问/url/mix/chinehe/001/mypath/path
    {
        "path": "/mypath/path",
        "userID": "001",
        "userName": "chinehe"
    }
  • c.BindUri方法:参考c.ShouldBindUri方法,略

5. 从Body中获取参数

5.1. 获取原始数据

有如下两种方式可以获取Body中的原始数据:

  • c.GetRawData:获取请求原始数据
  • 直接从c.Request.Body读取

特别注意:请求的Body只能被读取一次,因此用上述方法读取请求体内容后,不能在使用任何方法从请求中读取请求体内容!

c.GetRawData示例:

go 复制代码
func BodyParam(c *gin.Context) {
  // c.GetRawData()方法获取请求原始数据
	data, err := c.GetRawData()
	if err != nil {
		c.JSON(400, gin.H{
			"message": err.Error(),
		})
	}
	c.JSON(200, gin.H{
		"data": string(data),
	})
}

测试请求:

http 复制代码
POST /param/body HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/plain
Content-Length: 11

Hello World

应答:

json 复制代码
{
    "data": "Hello World"
}

直接从c.Request.Body读取示例:

go 复制代码
func BodyParam(c *gin.Context) {
  // 使用io.ReadAll读取请求体内容
	data, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(400, gin.H{
			"message": err.Error(),
		})
	}
	c.JSON(200, gin.H{
		"data": string(data),
	})
}

测试请求:

http 复制代码
POST /param/body HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/plain
Content-Length: 11

Hello World

应答:

json 复制代码
{
    "data": "Hello World"
}

5.2. 绑定Body数据

5.2.1. 绑定方法梳理

gin提供了各种各样的方法来绑定请求体数据,支持多种请求体格式:

  • Bind系列:绑定请求体参数到指定的结构体,如果出错,中止请求并应答400。
    • Bind:根据请求的HTTP Method和请求头中的Content-Type,自动选择按照哪种格式解析请求体。
      • c.Bind方法
    • BindT:按照指定的格式解析请求体。
      • c.BindWith方法(已弃用):手动指定按照那种格式解析请求体,内置支持:json、msgpack、plain、protobuf、toml、xml、yaml。
      • c.MustBindWith方法:手动指定按照那种格式解析请求体,内置支持:json、msgpack、plain、protobuf、toml、xml、yaml。
      • c.BindJSON方法:以JSON格式解析请求体。
      • c.BindPlain方法:以原始格式解析请求体。(目标对象应该是字节数组或字符串)
      • c.BindTOML方法:以TOML格式解析请求体。
      • c.BindXML方法:以XML格式解析请求体。
      • c.BindYAML方法:以YAML格式解析请求体。
  • ShouldBind系列:绑定请求体参数到指定的结构体,如果出错,返回报错让程序员自行处理。
    • ShouldBind:根据请求的HTTP Method和请求头中的Content-Type,自动选择按照哪种格式解析请求体。
      • c.ShouldBind方法
    • ShouldBindT:按照指定的格式解析请求体。
      • c.ShouldBindWith方法:手动指定按照那种格式解析请求体,内置支持:json、msgpack、plain、protobuf、toml、xml、yaml。
      • c.ShouldBindJSON方法:以JSON格式解析请求体。
      • c.ShouldBindPlain方法:以原始格式解析请求体。(目标对象应该是字节数组或字符串)
      • c.ShouldBindTOML方法:以TOML格式解析请求体。
      • c.ShouldBindXML方法:以XML格式解析请求体。
      • c.ShouldBindYAML方法:以YAML格式解析请求体。
    • ShouldBindBodyWithT:与ShouldBindT类似,但是会在上下文中存储请求体数据,以便再次调用时重用 。这些方法会在绑定之前读取请求体,因此如果你只会调用一次的话,建议使用ShouldBindT方法以获取更高的性能。
      • c.ShouldBindBodyWith方法:手动指定按照那种格式解析请求体,内置支持:json、msgpack、plain、protobuf、toml、xml、yaml。
      • c.ShouldBindBodyWithJSON方法:以JSON格式解析请求体。
      • c.ShouldBindBodyWithPlain方法:以原始格式解析请求体。(目标对象应该是字节数组或字符串)
      • c.ShouldBindBodyWithTOML方法:以TOML格式解析请求体。
      • c.ShouldBindBodyWithXML方法:以XML格式解析请求体。
      • c.ShouldBindBodyWithYAML方法:以YAML格式解析请求体。

5.2.2. 区别与总结

  • Bind系列与Should系列的方法,区别在于出错时的处理方式:
    • Bind系列:中止请求,并应答400.
    • ShouldBind系列:将错误交给调用者自行处理。
  • Bind/ShouldBind与BindT/ShouldBindT系列方法,区别在于是否指定按照哪种格式解析请求体:
    • Bind/ShouldBind系列:根据请求的HTTP Method和请求头中的Content-Type,自动选择按照哪种格式解析请求体。
    • BindT/ShouldBindT系列:按照指定的格式解析请求体。
  • ShouldBindT与ShouldBindBodyWithT系列方法,区别在于是否会在上下文中存储请求体数据:
    • ShouldBindT系列:不会存储请求体数据,不能多次调用,性能较好。
    • ShouldBindBodyWithT系列:会存储请求体数据,可以多次调用,性能较差。

5.2.3 示例

在上述的所有绑定方法中,最常用的应该是ShouldBind方法或者ShouldBindT系列的方法。其他的方法自然有其对应的用处,开发者应该充分了解他们之间的区别并自行决定使用哪个方法.

示例演示使用ShouldBind方法自动处理JSON格式的请求体参数:

代码:

go 复制代码
// UserParam 演示用参数结构体
type UserParam struct {
	UserID   string    `json:"userID"`
	UserName string    `json:"userName"`
	Info     *UserInfo `json:"info"`
}

type UserInfo struct {
	Age   int      `json:"age"`
	Roles []string `json:"roles"`
}

// BodyParam 处理函数
func BodyParam(c *gin.Context) {
	param := new(UserParam)
	err := c.ShouldBind(param)
	if err != nil {
		c.JSON(400, gin.H{
			"message": err.Error(),
		})
	}
	c.JSON(200, param)
}

请求:

http 复制代码
POST /param/body HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Content-Length: 130

{
    "userID":"001",
    "userName":"chinehe",
    "info":{
        "age":18,
        "roles":["super","admin","develop"]
    }
}

结果:

json 复制代码
{
    "userID": "001",
    "userName": "chinehe",
    "info": {
        "age": 18,
        "roles": [
            "super",
            "admin",
            "develop"
        ]
    }
}

5.2.4 请求体格式与结构体Tag对照表

在使用Gin框架绑定请求体数据时,不同的数据格式需要使用不同的绑定方法和结构体tag。下表详细列出了各种请求体格式对应的绑定方法和结构体tag:

请求体格式 Content-Type 绑定方法 结构体Tag 说明
JSON application/json ShouldBindJSON/MustBindWith json 最常用的格式,适用于API接口
XML application/xml ShouldBindXML/MustBindWith xml 常用于企业级应用和SOAP服务
YAML application/yaml ShouldBindYAML/MustBindWith yaml 常用于配置文件,人类可读性强
TOML application/toml ShouldBindTOML/MustBindWith toml 新兴配置文件格式,简洁明了
表单 application/x-www-form-urlencoded ShouldBind/ShouldBindWith form HTML表单默认提交格式
多部分表单 multipart/form-data ShouldBind/ShouldBindWith form 文件上传时使用
纯文本- text/plain ShouldBindPlain/MustBindWith - 原始字符串或字节数据

使用说明:

  1. 自动绑定 vs 手动绑定

    • ShouldBind系列方法会根据请求的Content-Type自动选择合适的绑定方法
    • ShouldBindWith系列方法需要手动指定绑定方式,提供更多控制权
  2. 结构体Tag使用

    • 不同的数据格式需要使用对应的tag,如JSON使用json,XML使用xml
    • form tag既适用于表单也适用于查询参数
    • 某些格式如纯文本不需要特定tag
  3. 示例结构体定义:

go 复制代码
// 不同格式的数据绑定示例
type User struct {
    ID       int      `json:"id" xml:"id" form:"id" yaml:"id"`
    Name     string   `json:"name" xml:"name" form:"name" yaml:"name"`
    Email    string   `json:"email" xml:"email" form:"email" yaml:"email"`
    IsActive bool     `json:"is_active" xml:"is_active" form:"is_active" yaml:"is_active"`
    Roles    []string `json:"roles,omitempty" xml:"roles" form:"roles" yaml:"roles"`
}

通过这个对照表,开发者可以根据需要选择合适的数据格式和绑定方法来处理请求体数据。

5.3. Form格式Body

除了前面介绍的各种参数获取方式以及通用的请求体绑定方法之外,Gin还专门提供了处理form表单格式数据的方法。Form表单是Web开发中最常见的数据提交方式之一,特别是在HTML表单提交、文件上传等场景中广泛使用。

5.3.1. Form表单数据获取方法

Gin提供了多种方法来获取form表单数据:

  • 从上下文简单获取

    • c.PostForm方法:获取指定key的form参数值,如果有多个就返回第一个,如果不存在就返回空字符串。
    • c.PostFormArray方法:获取指定key的form参数列表,如果不存在就返回空数组。
    • c.PostFormMap方法:获取指定key的form参数Map,如果不存在就返回空Map。
    • c.DefaultPostForm方法:获取指定key的form参数值,如果有多个就返回第一个,如果不存在就返回参数中的默认值。
    • c.GetPostForm方法:获取指定key的form参数值和参数是否存在的标识,如果有多个就返回第一个,如果不存在就返回空字符串和false。
    • c.GetPostFormArray方法:获取指定key的form参数列表和参数是否存在的标识,如果不存在就返回空数组和false。
    • c.GetPostFormMap方法:获取指定key的form参数Map和参数是否存在的标识,如果不存在就返回空Map和false。
  • 从上下文绑定到结构体

    • c.Bind方法:自动识别form格式并绑定form参数到结构体,如果出错就中止请求并返回400。
    • c.ShouldBind方法:自动识别form格式并绑定form参数到结构体,方法会返回错误信息。
    • c.BindWith方法:手动指定按照form格式解析请求体并绑定到结构体,如果出错就中止请求并返回400。(已弃用)
    • c.MustBindWith方法:手动指定按照form格式解析请求体并绑定到结构体,方法会返回错误信息。
    • c.ShouldBindWith方法:手动指定按照form格式解析请求体并绑定到结构体,方法会返回错误信息。
    • c.ShouldBindBodyWith方法:手动指定按照form格式解析请求体并绑定到结构体,会存储请求体数据以便重复使用,方法会返回错误信息。

5.3.2. 示例

假设HTTP请求如下:

http 复制代码
POST /param/form HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 58

username=chinehe&roles=super&roles=admin&roles=develop&age=18

示例代码:

  • 从上下文简单获取

    go 复制代码
    func FormParam(c *gin.Context) {
        userName := c.PostForm("username")           // 获取字符串信息
        roles := c.PostFormArray("roles")            // 获取字符串数组信息
        age := c.DefaultPostForm("age", "unknown")   // 获取字符串信息,设置默认值
        
        c.JSON(200, gin.H{
            "username": userName,
            "roles":    roles,
            "age":      age,
        })
    }
    
    // 输出
    {
        "age": "18",
        "roles": [
            "super",
            "admin",
            "develop"
        ],
        "username": "chinehe"
    }
  • 从上下文绑定到结构体

    结构体tag为form

    go 复制代码
    type UserParam struct {
        UserName string   `form:"username" json:"username"`
        Roles    []string `form:"roles" json:"roles"`
        Age      int      `form:"age" json:"age"`
    }
    
    func FormParam(c *gin.Context) {
        var userParam UserParam
        // 自动识别Content-Type为application/x-www-form-urlencoded并绑定
        if err := c.ShouldBind(&userParam); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        
        c.JSON(200, userParam)
    }
    
    // 输出
    {
        "age": 18,
        "roles": [
            "super",
            "admin",
            "develop"
        ],
        "username": "chinehe"
    }

5.3.3. Multipart Form数据处理

对于包含文件上传的multipart/form-data格式表单,Gin同样提供了专门的处理方法:

  • c.MultipartForm()方法:解析multipart表单,返回*multipart.Form对象
  • c.FormFile()方法:获取表单中的文件对象
  • c.SaveUploadedFile()方法:保存上传的文件到指定路径

文件上传示例:

http 复制代码
POST /upload HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"

chinehe
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="avatar.jpg"
Content-Type: image/jpeg

(binary data)
------WebKitFormBoundary7MA4YWxkTrZu0gW--

处理文件上传的代码示例:

go 复制代码
func UploadHandler(c *gin.Context) {
    // 获取multipart表单
    form, err := c.MultipartForm()
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 获取表单中的非文件字段
    username := form.Value["username"][0]
    
    // 获取上传的文件
    files := form.File["avatar"]
    
    // 遍历所有上传的文件
    for _, file := range files {
        // 保存文件到指定路径
        if err := c.SaveUploadedFile(file, "./uploads/"+file.Filename); err != nil {
            c.JSON(500, gin.H{"error": err.Error()})
            return
        }
    }
    
    c.JSON(200, gin.H{
        "message":  "上传成功",
        "username": username,
    })
}

简化版文件上传处理:

go 复制代码
func SimpleUploadHandler(c *gin.Context) {
    // 获取单个上传文件
    file, err := c.FormFile("avatar")
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 获取其他表单字段
    username := c.PostForm("username")
    
    // 保存文件
    if err := c.SaveUploadedFile(file, "./uploads/"+file.Filename); err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(200, gin.H{
        "message":  "上传成功",
        "username": username,
        "filename": file.Filename,
    })
}

文件上传注意事项

  • 处理文件上传时需要设置合适的内存限制,防止恶意上传大文件导致内存溢出
  • 应当验证上传文件的类型和大小,确保安全性
  • 保存文件时应当生成唯一的文件名,避免文件名冲突
相关推荐
IT_陈寒2 小时前
JavaScript 性能优化:7 个 V8 引擎偏爱的编码模式让你提速 40%
前端·人工智能·后端
程序员阿鹏2 小时前
List和Set的区别
java·开发语言·数据结构·后端·list
IT 行者2 小时前
SpringBoot版本升级插件:用OpenRewrite 轻松升级 Spring Boot 2 到 4
java·spring boot·后端
摸鱼仙人~2 小时前
企业级 RAG 问答系统开发上线流程分析
后端·python·rag·检索
我命由我123453 小时前
Python 开发问题:No Python interpreter configured for the project
开发语言·后端·python·学习·pycharm·学习方法·python3.11
闲云一鹤3 小时前
Claude Code 接入第三方AI模型(MiMo-V2-Flash)
前端·后端·claude
shiwulou13 小时前
如何在 Windows 中使用 Kimi CLI
后端
开心就好20253 小时前
iOS 抓包工具在不同场景的实际作用
后端
廋到被风吹走3 小时前
【Spring】核心类研究价值排行榜
java·后端·spring