多文件上传需严格处理解析、校验、并发控制三环节:必须调用r.ParseMultipartForm()并设合理内存阈值,defer file.Close()须在open后立即执行,上传协程需用sync.WaitGroup同步,且io.Copy阻塞需配合连接池限流。

多文件上传不是“加个 multiple 就完事”,核心卡点在解析、校验、并发控制三处——漏掉任一环节,轻则丢文件、重则被上传攻击打穿服务。
必须调用 r.ParseMultipartForm() 才能拿到文件列表
不调这个方法,r.MultipartForm.File 是 nil,r.FormFile() 会 panic 报错 “http: multipart handled by ParseMultipartForm”。这不是可选步骤,是硬性前置条件。
- 参数别传
0:表示全部落盘,失去内存控制,临时文件可能堆积在/tmp不清理 - 建议设为
32 (32MB):兼顾小文件内存读取效率和大文件磁盘缓冲安全 - 如果总上传量可能超 100MB,就在循环里累加
fileHeader.Size做总大小拦截,别等全读完再校验
r.MultipartForm.File["files"] 返回的是切片,不是单个文件
前端用 <input type="file" name="files" multiple> 提交后,后端拿到的 r.MultipartForm.File["files"] 天然是 []*multipart.FileHeader,直接 range 遍历即可,不用区分“单文件还是多文件”逻辑。
- 每个
fileHeader包含原始文件名、Size、Header 等元信息,但fileHeader.Filename绝对不可信 - 用
filepath.Base(fileHeader.Filename)剥离路径,防止../../../etc/passwd类攻击 - 用
uuid.New().String() + ext重命名,ext 从http.DetectContentType()推断,而非靠后缀
并发上传时 http.Client 必须复用,且配好连接池
如果用多 goroutine 并发调用 http.Post 上传到同一域名(比如 MinIO 或 S3),每个 goroutine 里 new 一个 http.Client,很快就会触发 dial tcp: lookup xxx: no such host —— 这不是 DNS 问题,是端口耗尽或连接池打满。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 全局声明一个
client := &http.Client{Timeout: 30 * time.Second} - 修改
client.Transport.MaxIdleConnsPerHost,设为并发数(如 50),默认值 2 在并发 >2 时就会排队 - 别在循环里
defer file.Close():它绑定的是当前 goroutine 的栈,协程退出前没执行就泄露;应放在上传函数内部、file, err := fh.Open()后立刻 defer
上传协程不等就退出,文件全丢是常态
Go 不会等 goroutine 自行结束。写 go uploadFile(...) 后 main 函数返回,整个进程就退出,所有未完成的 HTTP 请求直接中断——现象就是“没报错,但一个文件都没传上去”。
- 用
sync.WaitGroup:上传前wg.Add(1),函数末尾(包括 error 分支)必须wg.Done() -
wg.Wait()放在所有go uploadFile()调用之后、return之前 - 绝对别用
time.Sleep(5 * time.Second)代替wg.Wait():网络延迟波动大,休眠短了丢数据,长了阻塞响应
最易被忽略的是:大文件上传时,io.Copy() 会阻塞 goroutine 直到服务端响应完成。如果并发数设得过高、又没限制 MaxIdleConnsPerHost,协程堆积会导致调度器过载甚至 OOM——这比丢文件更难排查。

















