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

Go语言中如何不落地磁盘从multipart.Part获取multipart.File?

问题描述

之前在Go API中,通过r.FormFile("file")从请求体提取multipart.File,再传给MinIO客户端上传到S3,功能正常。现在添加额外表单数据后,再调用r.FormFile会触发“error getting form file”错误。改用MultipartReader读取表单数据后,拿到的multipart.Part不是multipart.File类型,希望在不写入磁盘的前提下,获取可直接传给MinIO的文件对象。

原正常代码

提取文件:

file, handler, err := r.FormFile("file") 

上传至MinIO:

err = uploadToMinio(rs, file, fileSize, fileName, guid.String(), userId)

当前出错的代码

var err error

start := time.Now()

const maxUploadSize = 500 * 1024 * 1024 // 500 Mb

var requiredByDate FileRequiredDateData

mr, err := r.MultipartReader()

if err != nil {
    log.Println(err)
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
}

for {
    part, err := mr.NextPart()

    // 读取完毕,退出循环
    if err == io.EOF {
        break
    }

    // 读取出错
    if err != nil {
        log.Println("multipart reader other error")
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    log.Println(part.FormName())

    if part.FormName() == "data" {
        log.Println("multipart reader found multipart form name data")
        decoder := json.NewDecoder(part)
        err = decoder.Decode(&requiredByDate)
        if err != nil {
            log.Println("error in decoding request body data")
            log.Println(err.Error())
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }

    if part.FormName() == "file" {
        // 此处报错:error getting form file
        file, handler, err := r.FormFile("file") 

        if err != nil {
            log.Println("error getting form file")
            log.Println(err.Error())
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusInternalServerError)
            return
        }

        defer file.Close()

        // ... 省略其他代码

        err = uploadToMinio(rs, file, fileSize, fileName, guid.String(), userId)

        if err != nil {
            log.Println(err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            return
        }
    }
}

解决方案

核心问题:请求体只能被读取一次,调用MultipartReader后已经消费了整个请求流,再调用r.FormFile会因为没有剩余数据报错。

直接使用multipart.Part上传

multipart.Part实现了io.Reader和io.Closer接口,完全可以直接替代multipart.File传给MinIO上传函数(只要uploadToMinio的第二个参数接受io.Reader或io.ReadCloser类型)。修改后的文件处理逻辑如下:

if part.FormName() == "file" {
    fileName := part.FileName()
    // 可选:从请求头获取文件大小,若Header中无此字段则需其他方式处理
    // fileSize, _ := strconv.ParseInt(part.Header.Get("Content-Length"), 10, 64)

    // 直接将part作为数据源传入上传函数
    err = uploadToMinio(rs, part, fileSize, fileName, guid.String(), userId)
    
    if err != nil {
        log.Println(err)
        http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        return
    }
    defer part.Close()
}

若需严格匹配multipart.File类型

如果uploadToMinio强制要求multipart.File类型(该类型额外实现了Seek方法),可以封装multipart.Part:

// 封装multipart.Part为multipart.File
type partFile struct {
    *multipart.Part
}

// 流式读取的Part不支持Seek,若上传函数不需要此方法可直接返回错误
func (pf partFile) Seek(offset int64, whence int) (int64, error) {
    return 0, errors.New("seek not supported")
}

// 使用示例:
file := partFile{part}
err = uploadToMinio(rs, file, fileSize, fileName, guid.String(), userId)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:06:32