Golang中如何处理上传的文件

Golang中如何处理上传的文件 大家好,我有一段代码,它从请求中获取一个多部分文件,并将其发送给负责将其作为电子邮件附件发送的包。我遇到的问题在于,当我尝试直接从请求中获取文件并发送时,文件内容为空;而如果在发送之前运行一些其他代码,通过电子邮件发送的文件就正常了。我实在不明白为什么会发生这种情况,任何有助于理解这里到底发生了什么问题的帮助都将不胜感激 🙏 🙏

var files []*os.File    	

uploadedFilesData := req.MultipartForm.File["attachments"]

	for _, v := range uploadedFilesData {
		mFile, err := v.Open()

		if err != nil {
			helpers.ClientError(w, err, http.StatusUnprocessableEntity)
			return
		}

		fileData, err := ioutil.ReadAll(mFile)
		mFile.Close()

		if err != nil {
			helpers.ServerError(w, err, ec.appLogger, "Failed to read the multipart file data")
			return
		}

		almostUniqueFileName := strconv.Itoa(int(time.Now().Unix())) + v.Filename
		fileName := "/tmp/kb_messaging/" + almostUniqueFileName

		newFile, err := os.Create(fileName)

		if err != nil {
			helpers.ServerError(w, err, ec.appLogger, "Failed to create tmp file")
			return
		}

		_, err = newFile.Write(fileData)
		if err != nil {
			helpers.ServerError(w, err, ec.appLogger, "Failed to write multipart file data to tmp file")
			return
		}
		

files = append(files, newFile)
	}
status, messageID, mErr := message.SendEmailWithFileAttachments(files, emailModel.Receiver)

虽然这段代码能运行,但附件只是空文件。但是,如果我将存储文件的逻辑与发送前重新读取它们的逻辑分开,文件就不再是空的了。

uploadedFilesData := req.MultipartForm.File["attachments"]

for _, v := range uploadedFilesData {
	mFile, err := v.Open()

	if err != nil {
		helpers.ClientError(w, err, http.StatusUnprocessableEntity)
		return
	}

	fileData, err := ioutil.ReadAll(mFile)
	mFile.Close()

	if err != nil {
		helpers.ServerError(w, err, ec.appLogger, "Failed to read the multipart file data")
		return
	}

	almostUniqueFileName := strconv.Itoa(int(time.Now().Unix())) + v.Filename
	fileName := "/tmp/kb_messaging/" + almostUniqueFileName

	newFile, err := os.Create(fileName)

	if err != nil {
		helpers.ServerError(w, err, ec.appLogger, "Failed to create tmp file")
		return
	}

	_, err = newFile.Write(fileData)
	if err != nil {
		helpers.ServerError(w, err, ec.appLogger, "Failed to write multipart file data to tmp file")
		return
	}
}

var files []*os.File

tmpDir, err := ioutil.ReadDir("/tmp/kb_messaging/")

if err != nil {
	helpers.ServerError(w, result.Error, ec.appLogger, "Failed to open tmp dir")
	return
}

for _, v := range tmpDir {

	newFile, err := os.Open("/tmp/kb_messaging/" + v.Name())

	if err != nil {
		helpers.ServerError(w, result.Error, ec.appLogger, "Failed to read file from tmp dir")
		return
	}
	defer newFile.Close()

	files = append(files, newFile)
}

status, messageID, mErr := message.SendEmailWithFileAttachments(files, emailModel.Receiver)

这真的很令人困惑,虽然第二种方法有效,但我实在不明白为什么第一种方法没有按预期工作! 附注:我使用的是自己开发的这个包,用于帮助通过 Mailgun 发送电子邮件:https://pkg.go.dev/github.com/nizigama/gomailer


更多关于Golang中如何处理上传的文件的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

关闭它并没有太大改变,问题在于当负责将其作为邮件附件发送的包尝试读取文件时,通过文件切片传递给它的文件,在使用 ioutil.ReadAll 读取时会返回一个“无效参数”错误。

更多关于Golang中如何处理上传的文件的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的回复,但问题是,当您尝试使用 ioutil.readall 读取新创建文件的数据时,无论是否延迟关闭文件,都会得到一个空切片,结果是一样的,一个空切片。https://play.golang.org/p/Vai_ly0nbnM

如果你使用的是 readAll,那么在读取之前需要先打开文件;而如果你使用 readFile,则无需打开。

https://play.golang.org/p/b2zZTHMp9nF

nizigama:

ioutil.ReadAll(mFile)

我能够从文件中获取数据。只是打印大小来检查这一点。

Go Playground - The Go Programming Language

大家好,我是Go语言和编程的新手,所以我也很想了解更多关于这方面的信息。

我在想,添加一个 newFile.Close() 会不会改变行为?我无法从技术角度准确解释,但这可能与文件IO操作被认为尚未完成有关,也许?

    newFile, err := os.Create(fileName)

	if err != nil {
		helpers.ServerError(w, err, ec.appLogger, "Failed to create tmp file")
		return
	}

	_, err = newFile.Write(fileData)
	if err != nil {
		helpers.ServerError(w, err, ec.appLogger, "Failed to write multipart file data to tmp file")
		return
	}

	defer newFile.Close()

files = append(files, newFile)

非常感谢 @Gowtham_Girithar,我想我现在明白发生了什么。

newFile, err := os.Create(fileName)

		if err != nil {
			fmt.Println("Create error", err)
			return
		}

		_, err = newFile.Write(fileData)

这段代码将数据写入文件,但不会将其保留在程序的内存中(我指的是变量中)。这就是为什么当我尝试使用 ioutil.ReadAll 读取文件数据时,会得到一个空切片。但是,当我使用 ioutil.ReadFile 并传入文件名时,我就能获取到数据,因为 ioutil.ReadFile 会从系统中打开文件,然后使用 ReadAll 来获取字节,就像这样:

bx, err := ioutil.ReadFile(newFile.Name())
		if err != nil {
			fmt.Println("Error reading file data", err)
			http.Error(writer, err.Error(), http.StatusInternalServerError)
			return
		}
		fmt.Println("byte data")
		fmt.Printf("byte data:%v\nbytes count %v\n", bx, len(bx))

再次感谢 @Gowtham_Girithar 的帮助,我真的很感激

问题出在文件指针的位置。在第一种方法中,你创建文件并写入数据后,文件指针位于文件末尾。当你将这个 *os.File 直接传递给邮件发送函数时,读取会从当前位置(即文件末尾)开始,导致读取到空内容。

以下是修复后的代码:

var files []*os.File

uploadedFilesData := req.MultipartForm.File["attachments"]

for _, v := range uploadedFilesData {
    mFile, err := v.Open()
    if err != nil {
        helpers.ClientError(w, err, http.StatusUnprocessableEntity)
        return
    }

    fileData, err := io.ReadAll(mFile)
    mFile.Close()
    if err != nil {
        helpers.ServerError(w, err, ec.appLogger, "Failed to read the multipart file data")
        return
    }

    almostUniqueFileName := strconv.Itoa(int(time.Now().Unix())) + v.Filename
    fileName := "/tmp/kb_messaging/" + almostUniqueFileName

    newFile, err := os.Create(fileName)
    if err != nil {
        helpers.ServerError(w, err, ec.appLogger, "Failed to create tmp file")
        return
    }

    _, err = newFile.Write(fileData)
    if err != nil {
        helpers.ServerError(w, err, ec.appLogger, "Failed to write multipart file data to tmp file")
        return
    }
    
    // 关键修复:将文件指针重置到文件开头
    _, err = newFile.Seek(0, 0)
    if err != nil {
        helpers.ServerError(w, err, ec.appLogger, "Failed to seek file")
        return
    }
    
    files = append(files, newFile)
}

status, messageID, mErr := message.SendEmailWithFileAttachments(files, emailModel.Receiver)

或者,你也可以在写入后关闭文件,然后在需要时重新打开:

var files []*os.File

uploadedFilesData := req.MultipartForm.File["attachments"]

for _, v := range uploadedFilesData {
    mFile, err := v.Open()
    if err != nil {
        helpers.ClientError(w, err, http.StatusUnprocessableEntity)
        return
    }

    fileData, err := io.ReadAll(mFile)
    mFile.Close()
    if err != nil {
        helpers.ServerError(w, err, ec.appLogger, "Failed to read the multipart file data")
        return
    }

    almostUniqueFileName := strconv.Itoa(int(time.Now().Unix())) + v.Filename
    fileName := "/tmp/kb_messaging/" + almostUniqueFileName

    // 写入文件并立即关闭
    err = os.WriteFile(fileName, fileData, 0644)
    if err != nil {
        helpers.ServerError(w, err, ec.appLogger, "Failed to write tmp file")
        return
    }
    
    // 重新打开文件用于邮件发送
    newFile, err := os.Open(fileName)
    if err != nil {
        helpers.ServerError(w, err, ec.appLogger, "Failed to open tmp file")
        return
    }
    defer newFile.Close()
    
    files = append(files, newFile)
}

status, messageID, mErr := message.SendEmailWithFileAttachments(files, emailModel.Receiver)

第二种方法有效是因为 os.Open() 打开文件时,文件指针默认在文件开头,而 os.Create() 和写入操作后,文件指针在文件末尾。

回到顶部