必须用 http.maxbytesreader 在 handler 开头显式替换 r.body 限流,parsemultipartform 参数需合理设置(不能为0或过大),并配合分片、落盘与流式处理防 oom。

直接用 io.ReadAll(r.Body) 读大文件必崩——不是代码写得不好,是 Go 默认行为就不支持。1GB 文件就可能触发 OOM,必须手动限流、分片、落盘,三者缺一不可。
如何用 http.MaxBytesReader 正确限制请求体大小
Go 的 http.MaxBytesReader 默认只限制响应体,对恶意上传的请求体完全无效。你必须在 handler 开头立刻包裹 r.Body,且不能只调用一次就完事:
- 必须显式替换
r.Body:r.Body = http.MaxBytesReader(w, r.Body, 2*1024*1024*1024)(限制 2GB) - 不能放在
r.ParseMultipartForm之后——那时 body 可能已被部分读取,限流失效 - 若用 Gin 等框架,需在中间件中提前替换
c.Request.Body,否则路由 handler 拿不到受控 reader - 该限流仅防总字节数超限,不解决内存峰值;它只是兜底,不是替代流式处理的方案
为什么 ParseMultipartForm 的参数不能设为 0 或过大
r.ParseMultipartForm 控制表单解析时内存与磁盘的分配策略,参数值直接影响是否吃光内存或频繁 IO:
- 设为
0:等价于r.ParseForm(),无法提取multipart.File字段,r.FormFile返回 nil - 设为过大(如
500 ):全部分片数据进内存,高并发下秒级 OOM - 设为过小(如
1 ):每 1MB 就落盘一次,产生大量小临时文件,I/O 压力陡增 - 推荐值:
32 (32MB),兼顾解析效率与内存安全;记得配 <code>defer r.MultipartForm.RemoveAll()清理句柄和临时文件
分片上传拼接时为什么不能按文件名字符串排序
前端传 chunk_1、chunk_2,后端用 filepath.Glob("chunk_*") 再 sort.Strings?错。文件系统 readdir 不保证顺序,网络传输可能乱序,不同客户端命名规则也可能不一致。
- 真正可靠的是三个字段:
X-Upload-ID(会话唯一标识)、X-Part-Number(从 0 或 1 起,前后端必须严格约定)、X-Total-Parts(仅作校验,不能单独用于判断完成) - 服务端收到分片后,先查
sync.Map或 Redis 中该upload_id对应的已接收索引集合,重复或越界直接拒收 - 拼接时用
io.MultiReader按part_number升序打开各临时文件,再io.Copy到目标路径;别用os.WriteAt手动 seek——分片大小不均时极易写错位置
合并前最容易漏掉的三件事
90% 的“合并成功但文件打不开”,都出在这三步没做:
- 没检查每个分片文件是否真实存在且非空:
fi, _ := file.Stat(); if fi.Size() == 0就该返回 400 - 没校验最终文件哈希:合并完必须用
sha256.Sum256重新计算,和前端传来的fileHash字段比对,不一致就删掉目标文件并报错 - 没做原子写入:先
os.CreateTemp("", "merge_*.tmp")写入,再os.Rename(tempPath, finalPath)替换,否则并发读取时可能拿到半成品
代理超时、分片丢失、哈希未校验、临时文件堆积——这些点不在代码主干里,但任何一个出问题,上传就变成玄学。别指望“逻辑通了就能跑通”,大文件上传的坑全在细节里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











