必须手动限流、分片、落盘,三者缺一不可;用 http.maxbytesreader 限制请求体大小,再调用 r.parsemultipartform 设置内存阈值。

Go 做大文件上传,不加控制直接读 r.Body 必崩——1GB 文件就可能触发 OOM,不是代码写得不够好,而是默认行为就不支持。必须手动限流、分片、落盘,三者缺一不可。
怎么防止服务端被大文件请求打垮
Go 的 http.Server 默认不限制请求体大小,http.MaxBytesReader 只管响应体,对恶意上传的请求体完全无效。你得在 handler 开头就包一层:
-
limitReader := http.MaxBytesReader(w, r.Body, 2*1024*1024*1024)—— 限制单次上传 ≤2GB - 立刻调用
r.ParseMultipartForm(32 (32MB 内存缓冲),超出部分自动落盘到 <code>os.TempDir(),避免表单解析吃光内存 - 别依赖
io.ReadAll(r.Body),改用io.Copy(dst, limitReader)直接流式写入磁盘或对象存储
为什么分片上传不能靠“按名字排序”拼接
前端传 chunk_1、chunk_2,后端按字符串排序拼接?错。文件系统不保证 readdir 顺序,网络传输可能乱序,甚至不同客户端生成的文件名规则不一致。真正可靠的是三个字段:
-
X-Upload-ID(或 query 参数upload_id):本次上传会话唯一标识 -
X-Part-Number(或chunkIndex):从 0 或 1 开始需前后端严格约定 -
X-Total-Parts(或totalChunks):用于校验是否收齐,但不能单独依赖它判断完成
服务端收到分片后,应先查 sync.Map 或 Redis 中该 upload_id 对应的已接收索引集合,重复或越界直接拒收;拼接时用 io.MultiReader 按 chunkIndex 升序打开各临时文件,而非 os.WriteAt 手动 seek —— 分片大小不均时极易写错位置。
合并前最容易漏掉的三件事
90% 的“合并成功但文件打不开”问题,都出在这三步没做:
- 没检查每个分片文件是否真实存在且非空:
fi, _ := file.Stat(); if fi.Size() == 0就该返回 400 - 没校验最终文件哈希:合并完必须用
sha256.Sum256重新计算,和前端传来的fileHash字段比对,不一致就删掉目标文件并报错 - 没做原子写入:先
os.CreateTemp("", "merge_*.tmp")写入,再os.Rename(tempPath, finalPath)替换,否则并发读取时可能拿到半成品
为什么上传卡在 99% 其实和 Go 无关
这不是你的 handler 写错了,而是中间代理(Nginx、ALB、防火墙)默认 60 秒超时把连接干掉了。关键配置必须同步改:
- Nginx:加
client_max_body_size 4G; proxy_read_timeout 3600; proxy_send_timeout 3600; proxy_buffering off; - Go 服务端:
http.Server.ReadTimeout和WriteTimeout都设为至少 3600 秒,别用默认值 - 前端每个分片请求必须带准确
Content-Length,禁用 chunked encoding;用fetch时 body 要是Blob或ArrayBuffer,别用FormData—— 后者加了 boundary,长度无法静态确定
真正难的不是写完逻辑,而是所有环节——前端切片边界、HTTP 头、代理超时、磁盘权限、哈希校验、原子替换——全部对齐。少对一环,上传就变成玄学。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











