多文件上传与 Multipart 解析细节

本章解决什么问题:一次提交多张图片或附件时,单个 FormFile 不够用。本章讲 c.MultipartForm 的用法、multipart 的内存/磁盘机制,以及 form 里普通字段与文件字段如何并存。

用 MultipartForm 拿全部字段

c.MultipartForm() 会触发整份表单解析,返回 *multipart.Form:其中 File 字段是 map[string][]*multipart.FileHeader,键是表单字段名,值是同名文件列表;Value 字段存放普通文本字段(值为 []string)。逐个保存即可:

package main

import (
	"net/http"
	"path/filepath"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.MaxMultipartMemory = 8 << 20

	r.POST("/upload", func(c *gin.Context) {
		form, err := c.MultipartForm() // 解析整个 multipart 表单
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		files := form.File["photos"] // 键与 <input name="photos"> 一致
		for _, f := range files {
			dst := filepath.Join("uploads", f.Filename)
			if err := c.SaveUploadedFile(f, dst); err != nil {
				continue // 单张失败不阻断其余,正式项目应记录日志
			}
		}
		c.JSON(200, gin.H{"count": len(files)})
	})

	r.Run(":8080")
}

对应 HTML:<form method="post" enctype="multipart/form-data"><input type="file" name="photos" multiple>。客户端也可多次提交同名字段,Go 的 multipart 解析会把它们聚到同一个键下。

解析细节:内存与磁盘

c.FormFile 与 c.MultipartForm 内部都会走请求的 ParseMultipartForm。机制是:

  • 请求体先按引擎的 MaxMultipartMemory 阈值切分:阈值内的部分驻留内存;
  • 超出阈值的部分以临时文件写入系统临时目录,请求处理结束由 net/http 清理;
  • 解析非 multipart 的 Content-Type 会直接报错,先判断再解析更友好。

若在 handler 中提前确认不再需要这些文件,可调用 c.Request.MultipartForm.RemoveAll() 手动释放临时文件,降低大并发下的磁盘占用。

与普通字段共存

同一表单里既能带文件又能带文本(如“标题 + 图片”)。文件走 form.File,文本走 form.Value 或 c.PostForm。注意:multipart 表单里的文本字段用 c.PostForm 也能读到,因为内部解析的是同一份 multipart。

关键点

  • 多文件统一用 c.MultipartForm(),返回的 form.File 以字段名为键、文件列表为值。
  • 阈值语义:MaxMultipartMemory 是内存/磁盘的分界,不是数量或体积上限。
  • 临时文件的清理由 net/http 负责,密集上传场景可手动 RemoveAll 提前释放。
  • 每个文件仍需单独做扩展名白名单与大小校验(见上一章)。

小结

多文件上传只是“遍历 form.File 里的列表再逐个 SaveUploadedFile”。理解了解析阈值与临时文件机制,就能解释为什么小文件快、大文件吃磁盘。下一章转向会话状态:Cookie。

笔记加载中…