Go语言 无法读取Postman中的表单数据

hgc7kmma  于 2023-09-28  发布在  Go
关注(0)|答案(3)|浏览(135)

我试图从结构数组中读取文件,但无法读取。 Postman 来了。
下面是我的代码:

type MasterTemplate struct {
    ID       uuid.UUID `form:"id" json:"id"`
    IsVoice  bool      `form:"is_voice" json:"is_voice"`
    Title    string    `form:"title" json:"title"`
    IsSms    bool      `form:"is_sms" json:"is_sms"`
    FilePath string    `form:"file_path" json:"file_path"`
    Content  []struct {
        File             *multipart.FileHeader `form:"file" json:"file"`
        MsgTmplateLangId string                `form:"msg_template_lang_id" json:"msg_template_lang_id"`
        SMSContent       string                `form:"sms_content" json:"sms_content"`
        MsgContentID     string                `form:"msg_content_id" json:"msg_content_id"`
    } `form:"content" json:"content"`
    UserID uuid.UUID `form:"user_id" json:"user_id"`
}

func (tmplController *MessageTemplateController) CreateMsgTemplateController(ctx *gin.Context) {
    userID := ctx.Param("user_id")

    userUUID, err := uuid.Parse(userID)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        ctx.Abort()
        return
    }

    if ctx.Request.ContentLength == 0 {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error": "Invalid/Empty body request",
        })
        ctx.Abort()
        return
    }

    err = ctx.Request.ParseMultipartForm(10 * 1024 * 1024)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        return
    }

    var msgTemplate *models.MasterTemplate

    if err := ctx.ShouldBind(&msgTemplate); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "Gin Error": err.Error(),
        })
        return
    }

    config, err := config.LoadConfig("credentials")
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "rerror": err.Error(),
        })
        return
    }

    
    for _, sms := range msgTemplate.Content {

        lanaguageID, err := uuid.Parse(sms.MsgTmplateLangId)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "rerror": err.Error(),
            })
            return
        }

        templateContent := models.MessageTemplateContenet{
            ID:                uuid.New(),
            LanguageID:        lanaguageID,
            IsSms:             true,
            IsVoice:           false,
            FilePath:          "",
            Content:           sms.SMSContent,
            UserID:            userUUID,
            // MessageTemplateID: data.ID,
            IsDeleted:         false,
        }

        _, err = tmplController.contents.CreateMessageTemplateContent(&templateContent, ctx)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "rerror": err.Error(),
            })
            return
        }
    }

    msgTemplate.IsVoice = true
    msgTemplate.IsSms = false

    msgTemplate.IsSms = false
    msgTemplate.IsVoice = true

    for x, voice := range msgTemplate.Content {
        fmt.Println(voice.MsgTmplateLangId)
        fmt.Println(x)
        fileHeader, err := ctx.FormFile(fmt.Sprintf("content[%c][file]", x))
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
            return
        }
        file, err := voice.File.Open()
        if err != nil {
            ctx.JSON(400, gin.H{"error": "Failed to open uploaded file"})
            return
        }
        defer file.Close()
        fmt.Println(file)

        if !utilities.IsValidFile(fileHeader) {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "error": "Invalid file format. Only .mp3 and .wav files are allowed.",
            })
            return
        }
        if fileHeader.Size > 3*1024*1024 {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "error": "File size exceeds the limit. Maximum file size is 3MB.",
            })
            return
        }
        audioPath := uuid.New().String() + fileHeader.Filename
        uploadPath := filepath.Join("./msgtemplate", audioPath)
        // Save the uploaded file
        if err := ctx.SaveUploadedFile(fileHeader, uploadPath); err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
            return
        }
        session, err := awsservice.S3BucketSessionHandler()
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
            return
        }
        fileURL, err := awsservice.UploadFiles(session, uploadPath, config.AWS_AUDIO_BUCKET)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
            return
        }
        lanaguageID, err := uuid.Parse(voice.MsgTmplateLangId)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "rerror": err.Error(),
            })
            return
        }
        templateContent := models.MessageTemplateContenet{
            ID:         uuid.New(),
            LanguageID: lanaguageID,
            IsSms:      false,
            IsVoice:    true,
            IsDeleted:  false,
            FilePath:   uploadPath,
            Content:    *fileURL,
            UserID:     userUUID,
            // MessageTemplateID: data.ID,
        }

        _, err = tmplController.contents.CreateMessageTemplateContent(&templateContent, ctx)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "rerror": err.Error(),
            })
            return
        }

    }
}
qv7cva1a

qv7cva1a1#


我看到:

var msgTemplate *models.MasterTemplate

if err := ctx.ShouldBind(&msgTemplate); err != nil {
    ctx.JSON(http.StatusBadRequest, gin.H{
        "Gin Error": err.Error(),
    })
    return
}

您正在尝试将传入的multipart文件绑定到MasterTemplate结构。
具体来说,结构体的以下部分可能存在问题:

Content  []struct {
    File             *multipart.FileHeader `form:"file" json:"file"`
    // other fields
} `form:"content" json:"content"`

multipart文件以这种方式包含在结构体切片中时,ShouldBind方法可能无法正确绑定这些文件。
当您尝试使用以下命令打开文件时:

file, err := voice.File.Open()
if err != nil {
    ctx.JSON(400, gin.H{"error": "Failed to open uploaded file"})
    return
}

voice.File来自以前的结构绑定,可能没有正确绑定,导致在尝试打开文件时出现问题。
正如在how to upload multipart file and json in Go with gin gonic?中所看到的,对于简单的情况(当不涉及结构体切片时),表单绑定可以很好地处理文本字段和文件。但是,虽然ShouldBind可以处理结构体中的multipart文件,但当它们位于结构体切片中时,它可能无法正确绑定它们,这是(从您的代码中):

Content  []struct {
    File             *multipart.FileHeader `form:"file" json:"file"`
    MsgTmplateLangId string                `form:"msg_template_lang_id" json:"msg_template_lang_id"`
    SMSContent       string                `form:"sms_content" json:"sms_content"`
    MsgContentID     string                `form:"msg_content_id" json:"msg_content_id"`
} `form:"content" json:"content"`

在处理更复杂的方案时,将文件数据与其他表单数据分开处理可能更安全。
通用示例:来自Tanveer Shafee Prottoy的“File Upload in Go with multipart/form-data”。
您可以使用ctx.MultipartForm()方法和form.File字段直接从请求中的表单数据中获取文件:

form, _ := ctx.MultipartForm()
files := form.File["content[file]"]

然后遍历files以根据需要处理每个文件。
完整的代码是:

func (tmplController *MessageTemplateController) CreateMsgTemplateController(ctx *gin.Context) {
    ...
    // Parse your form data manually
    form, _ := ctx.MultipartForm()
    files := form.File["content[file]"]

    // Iterate over the files in the form data
    for _, file := range files {
        // You can now access each file's data
        fmt.Println(file.Filename)
    }
    ...
}

在上面的代码中,ctx.MultipartForm()用于从请求中获取多部分表单数据,form.File["content[file]"]用于访问表单数据中的文件。
您必须改进代码(例如,在尝试访问files片的元素之前,我甚至没有检查它是否为空)
User那么,如何将其他JSON文本附加到文件中?
因为这样我就可以读取文件了,所以我也关心阅读文件结构中的每个文本。
假设您在multipart/form-data请求中同时发送文件和相关的JSON数据,那么您需要一种方法来绑定请求的数据部分并单独处理文件。
首先,使用ctx.ShouldBind将所有文本表单数据绑定到MasterTemplate结构。

var msgTemplate models.MasterTemplate

if err := ctx.ShouldBind(&msgTemplate); err != nil {
    ctx.JSON(http.StatusBadRequest, gin.H{
        "Gin Error": err.Error(),
    })
    return
}

然后使用ctx.MultipartForm()直接从表单数据中获取文件,并将它们与MasterTemplate.Content切片中的相应条目相关联。

form, _ := ctx.MultipartForm()
files := form.File

// Iterating over files and binding them to msgTemplate.Content
for index, fileHeaderSlice := range files {
    if strings.HasPrefix(index, "content[") {
        // Extract the index from the string
        positionStr := strings.TrimSuffix(strings.TrimPrefix(index, "content["), "][file]")
        position, err := strconv.Atoi(positionStr)
        if err != nil {
            ctx.JSON(http.StatusBadRequest, gin.H{
                "error": "Invalid file index",
            })
            return
        }
        if position < len(msgTemplate.Content) && len(fileHeaderSlice) > 0 {
            msgTemplate.Content[position].File = fileHeaderSlice[0]
        }
    }
}

这将从请求中提取文件,并将它们与msgTemplate.Content切片中的正确位置相关联。
完成此设置后,使用msgTemplate的其余代码基本保持不变。您可以访问每个Content项目中的File,因为它们现在已经正确关联。

v09wglhw

v09wglhw2#

x1c 0d1x看起来你正在使用Gin框架来处理一个包含表单字段和文件上传的多部分请求。在你的代码片段中,你定义了一个结构体来绑定传入的请求数据并相应地处理它。我的主要问题是如何包含与文件msg_template_lang_id相关的信息,但无法读取,因为当我打印时,即使我用ctx.Request.MultipartForm读取文件,然后用shouldbind读取其他部分,我也会得到空的。
从提供的代码中,有几件事可能会导致问题:
嵌套结构和多部分文件的绑定问题:在Gin中,用嵌套结构体绑定多部分文件可能很棘手,尤其是当表单具有非平面结构时。
循环中的索引不正确:您使用x作为索引,并尝试使用fmt.Sprintf(“content[%c][file]",x)将其转换为字符。因为x是一个整数,你需要用%d而不是%c将它转换成字符串。

for x, voice := range msgTemplate.Content {
    fmt.Println(voice.MsgTmplateLangId)
    fmt.Println(x)
    fileHeader, err := ctx.FormFile(fmt.Sprintf("content[%d][file]", x))
    // rest of your code
}

尝试使用FormFile方法直接从请求中获取文件。您可能需要调整表单的字段名称以匹配从客户端发送的内容。确保客户端发送的文件在form-data中具有正确的字段名称。如果您继续面临绑定问题,您可能不得不手动解析多部分表单数据,而不是依赖于Gin的自动绑定。这将涉及遍历多部分表单数据并手动处理每个部分,包括文件。
伪代码:-

err = ctx.Request.ParseMultipartForm(10 * 1024 * 1024)
if err != nil {
    ctx.JSON(http.StatusBadRequest, gin.H{
        "error": err.Error(),
    })
    return
}

formdata := ctx.Request.MultipartForm

// Handle the fields
formfields := formdata.Value
// Extract values from formfields as needed

// Handle the files
files := formdata.File["content"]
for i, fileHeader := range files {
    file, err := fileHeader.Open()
    if err != nil {
        ctx.JSON(400, gin.H{"error": "Failed to open uploaded file"})
        return
    }
    defer file.Close()

    // Process the file as needed
}

确保Postman正确地发送多部分请求,每个文件部分都具有与服务器期望的名称相一致的正确名称。您可能希望在服务器端打印请求,以查看发送的确切内容,并相应地调整代码。确保您的Postman请求设置正确。确保选择“Body”选项卡下的“form-data”,然后根据服务器代码预期的结构输入键和值,包括文件。

mxg2im7a

mxg2im7a3#


下面是重构后的代码:

func (tmplController *MessageTemplateController) CreateMsgTemplateController(ctx *gin.Context) {
    err := ctx.Request.ParseMultipartForm(10 * 1024 * 1024)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        return
    }

    // We get the user ID from the route the we convert it to UUID 
    // We convert to UUID since that is the input type
    userUUID, err := uuid.Parse(ctx.Param("user_id"))
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        return
    }

    // We get the other parameters in the struct
    msgTemplate := models.MasterTemplate{
        ID:      ctx.Request.Form.Get("id"),
        Title:   ctx.Request.Form.Get("title"),
        IsSms:   ctx.Request.Form.Get("is_sms") == "true",
        IsVoice: ctx.Request.Form.Get("is_voice") == "true",
        UserID:  userUUID,
    }

    msgTemplate.Content = make([]models.Content, 0)
    for i := 0; ; i++ {
        fileInputName := fmt.Sprintf("content[%d][file]", i)
        if file, err := ctx.FormFile(fileInputName); err == nil {
            contentPrefix := fmt.Sprintf("content[%d]", i)
            msgTemplateLangIDKey := fmt.Sprintf("%s[msg_template_lang_id]", contentPrefix)
            smsContentKey := fmt.Sprintf("%s[sms_content]", contentPrefix)

            var fc models.Content
            fc.File = file
            fc.SMSContent = ctx.Request.Form.Get(smsContentKey)
            fc.MsgTmplateLangId = ctx.Request.Form.Get(msgTemplateLangIDKey)

            msgTemplate.Content = append(msgTemplate.Content, fc)
        } else {
            break
        }
    }

    // Now we can loop through the content to do what we want to now

    ctx.JSON(http.StatusOK, gin.H{
        "response": msgTemplate,
    })
}

相关问题