mirror of
https://gitee.com/johng/gf
synced 2026-06-30 19:17:30 +08:00
feat(ghttp): 支持文件上传字段的嵌套结构解析
当前问题,使用嵌套字段时无法自动绑定到嵌套字段的文件 【已解决】
```
type TestData struct {
ID int64 `json:"id" dc:"ID"`
Name string `json:"name" dc:"Name"`
File *ghttp.UploadFile `json:"file" dc:"File" type:"file"`
Files *ghttp.UploadFiles `json:"files" dc:"Files" type:"file"`
}
type TestReq struct {
g.Meta `path:"/v1/admin/user/test" tags:"AdminUser" method:"POST" summary:"Test"`
ID int64 `json:"id" dc:"ID"`
Data TestData `json:"data" dc:"Data"`
File *ghttp.UploadFile `json:"file" dc:"File" type:"file"`
}
```
使用 multipart/form-data 上传时
```
------WebKitFormBoundarypwjjDUNvfZkxxlhH
Content-Disposition: form-data; name="data[id]"
11111111111111112
------WebKitFormBoundarypwjjDUNvfZkxxlhH
Content-Disposition: form-data; name="data[name]"
11111111111111112
------WebKitFormBoundarypwjjDUNvfZkxxlhH
Content-Disposition: form-data; name="data[description]"
11111111111111112
------WebKitFormBoundarypwjjDUNvfZkxxlhH
Content-Disposition: form-data; name="data[file]"; filename="xxxx.jpg"
Content-Type: image/jpeg
------WebKitFormBoundarypwjjDUNvfZkxxlhH
Content-Disposition: form-data; name="file"; filename="xxxxxr.jpg"
Content-Type: image/jpeg
------WebKitFormBoundarypwjjDUNvfZkxxlhH
Content-Disposition: form-data; name="data[files][]"; filename="debug.skk.moe_1732736392647.png"
Content-Type: image/png
------WebKitFormBoundarypwjjDUNvfZkxxlhH
Content-Disposition: form-data; name="data[files][]"; filename="85ee46523adb6a8ee4bf95795c91bef28e24983ed38afbec6789fb5077d75e3f.jpg"
Content-Type: image/jpeg
------WebKitFormBoundarypwjjDUNvfZkxxlhH
Content-Disposition: form-data; name="id"
1000
------WebKitFormBoundarypwjjDUNvfZkxxlhH--
```
【问题描述】
之前使用:
```
var (
request = g.RequestFromCtx(ctx)
)
var data = request.GetRequestMap()
```
获得的结果是扁平化的:
```
{
"data": {
"description": "11111111111111112",
"id": "11111111111111112",
"name": "11111111111111112"
},
"data[file]": {
"Filename": "xxxxx.jpg",
},
"data[files][]": [
{
"Filename": "xxx.png",
},
{
"Filename": "xxx.jpg",
}
],
"file": {
"Filename": "xxxx.jpg",
"Size": 5252553
},
"id": "1000"
}
```
由于没有将 `map["data[file]"]` 以 `map["data"]["file"]` 的形式存储,导致最终进行 `r.Parse` 时无法将文件正确绑定到结构体字段:
```
File *ghttp.UploadFile `json:"file" dc:"File" type:"file"`
Files *ghttp.UploadFiles `json:"files" dc:"Files" type:"file"`
```
这些字段无论如何都是 nil。
【解决方案】
现在已修复此问题,通过解析嵌套的字段名并构建正确的嵌套Map结构。修复后,`GetRequestMap()` 返回的结果如下:
```
{
"data": {
"description": "11111111111111112",
"id": "11111111111111112",
"name": "11111111111111112",
"file": {
"Filename": "xxxxx.jpg",
},
"files[]": [
{
"Filename": "xxx.png",
},
{
"Filename": "xxx.jpg",
}
]
},
"file": {
"Filename": "xxxx.jpg",
"Size": 5252553
},
"id": "1000"
}
```
这样,嵌套结构中的文件字段现在可以正确绑定到相应的结构体字段了。这一修复实现了对表单中嵌套文件字段的完整支持。
This commit is contained in:
@ -7,6 +7,8 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/net/goai"
|
||||
"github.com/gogf/gf/v2/os/gstructs"
|
||||
@ -113,10 +115,26 @@ func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]inte
|
||||
// File uploading.
|
||||
if r.MultipartForm != nil {
|
||||
for name := range r.MultipartForm.File {
|
||||
if uploadFiles := r.GetUploadFiles(name); len(uploadFiles) == 1 {
|
||||
m[name] = uploadFiles[0]
|
||||
uploadFiles := r.GetUploadFiles(name)
|
||||
// 处理嵌套字段名称,如 data[files][]
|
||||
if strings.Contains(name, "[") && strings.Contains(name, "]") {
|
||||
// 解析字段名并创建嵌套结构
|
||||
keys := parseFormNameToKeys(name)
|
||||
if len(keys) > 0 {
|
||||
// 使用解析后的键创建嵌套结构
|
||||
if len(uploadFiles) == 1 {
|
||||
createNestedMapForFiles(m, keys, uploadFiles[0])
|
||||
} else {
|
||||
createNestedMapForFiles(m, keys, uploadFiles)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m[name] = uploadFiles
|
||||
// 常规字段处理,保持原有逻辑
|
||||
if len(uploadFiles) == 1 {
|
||||
m[name] = uploadFiles[0]
|
||||
} else {
|
||||
m[name] = uploadFiles
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,3 +284,78 @@ func mergeTagValueWithFoundKey(data map[string]interface{}, overwritten bool, fi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseFormNameToKeys 解析表单字段名称,例如 "data[files][]" 会解析为 ["data", "files[]"]
|
||||
func parseFormNameToKeys(name string) []string {
|
||||
// 查找第一个[的位置
|
||||
firstBracket := strings.Index(name, "[")
|
||||
if firstBracket < 0 {
|
||||
return []string{name}
|
||||
}
|
||||
|
||||
// 提取基本名称
|
||||
base := name[:firstBracket]
|
||||
keys := []string{base}
|
||||
|
||||
// 提取所有括号中的内容
|
||||
remaining := name[firstBracket:]
|
||||
for len(remaining) > 0 {
|
||||
// 找到一对括号
|
||||
closeBracket := strings.Index(remaining, "]")
|
||||
if closeBracket < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// 提取括号中的内容
|
||||
key := remaining[1:closeBracket]
|
||||
|
||||
// 处理空括号情况 如 []
|
||||
if len(key) > 0 {
|
||||
keys = append(keys, key)
|
||||
} else {
|
||||
// 对于空括号,将其附加到上一个键
|
||||
lastIndex := len(keys) - 1
|
||||
if lastIndex >= 0 {
|
||||
keys[lastIndex] = keys[lastIndex] + "[]"
|
||||
}
|
||||
}
|
||||
|
||||
// 继续处理剩余部分
|
||||
if len(remaining) > closeBracket+1 {
|
||||
remaining = remaining[closeBracket+1:]
|
||||
} else {
|
||||
remaining = ""
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// createNestedMapForFiles 根据解析的键创建嵌套的map结构
|
||||
func createNestedMapForFiles(m map[string]interface{}, keys []string, value interface{}) {
|
||||
if len(keys) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理最后一个层级
|
||||
if len(keys) == 1 {
|
||||
m[keys[0]] = value
|
||||
return
|
||||
}
|
||||
|
||||
// 处理中间层级
|
||||
key := keys[0]
|
||||
if m[key] == nil {
|
||||
m[key] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// 如果当前值不是map,则创建一个新的map
|
||||
subMap, ok := m[key].(map[string]interface{})
|
||||
if !ok {
|
||||
subMap = make(map[string]interface{})
|
||||
m[key] = subMap
|
||||
}
|
||||
|
||||
// 递归处理剩余键
|
||||
createNestedMapForFiles(subMap, keys[1:], value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user