go实现断点续传需禁用parsemultipartform,手动读取request.body流式写入分片;元信息从url或formvalue获取;客户端用blob.slice切片、独立post上传;服务端校验sha256、排序合并并检查完整性。

Go 标准库的 http.Request 默认会把整个请求体读进内存,对大文件上传不友好,也撑不住断点续传的分片逻辑。必须绕过 ParseMultipartForm 的默认行为,手动读取原始 Body 流。
服务端必须禁用 ParseMultipartForm 并直读 Body
Go HTTP 服务端默认调用 ParseMultipartForm 会提前消费 request.Body,导致后续无法再读取分片数据。这不是配置问题,是行为陷阱。
- 在 handler 开头立即调用
r.ParseMultipartForm(0)(传0表示不限制内存缓存),强制标准库跳过自动解析,避免 body 被吞掉 - 元信息(如
uploadId、chunkIndex、totalChunks)应从 URL 查询参数或r.FormValue()获取,不要依赖r.MultipartForm.Value—— 后者在未解析时为空,已解析则 body 已不可用 - 分片二进制数据直接从
r.Body读取,用io.CopyN或io.LimitReader控制长度,避免读超 - 写入临时文件时用
os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_EXCL),防止并发重复写同一分片导致内容错乱
客户端分片不能靠 FileReader 全量读再切
浏览器端用 FileReader.readAsArrayBuffer 读完整个大文件再切片,会卡 UI、触发 GC、甚至 OOM。这不是性能优化问题,是架构错误。
- 正确做法是用
Blob.slice(start, end)直接截取二进制段,返回新Blob,零拷贝、不占内存 - 每个分片发独立 POST 请求,URL 带
uploadId(如/api/upload/abc123/chunk),不要塞进 multipart 的 file 字段里 - HTTP Header 必须设
Content-Type: application/octet-stream;若设成multipart/form-data却没带 boundary,服务端会解析失败并返回400 Bad Request - 务必在请求中携带
X-Chunk-Hash头(如 SHA256 前 8 字节),服务端收到后立刻用crypto/sha256.New()+io.Copy实时校验,不落地再算
合并前必须按 chunkIndex 排序且逐片校验
只按文件名字符串排序(如 chunk_1、chunk_10)会导致顺序错乱;只检查数量不校验内容,合并后文件可能静默损坏。
- 读取所有分片文件路径后,用
sort.Slice按chunkIndex数值升序排列,别信字典序 - 合并时不用
cat或一次性io.Copy所有分片——大文件易 OOM;应循环打开每个分片,用io.CopyN分批写入目标文件 - 合并前检查
len(chunkFiles) == totalChunks,缺一不可;哪怕只少一片,也要返回409 Conflict,不静默跳过 - 最终文件生成后,立刻计算其 SHA256,与客户端上传时声明的原始文件 hash 比对,不一致就删掉目标文件并报错
断点续传不能依赖客户端本地状态
客户端存的 upload_state.json 可能被清除、篡改或不同设备不一致。断点续传的唯一可信源,是服务端实时返回的已传分片列表。
- 提供轻量接口如
GET /api/upload/status?uploadId=abc123,响应格式为{"uploaded_chunks": [1,2,4], "total_chunks": 10} - 服务端状态建议存在 Redis 的
SET中(key 为uploadId:chunks),SMEMBERS拉全量,毫秒级响应;避免查数据库或遍历磁盘目录 - 客户端拿到缺失索引后,只重传这些分片;若发现服务端返回的
total_chunks与本地不一致,说明上下文已失效,应放弃续传、重新初始化 - 每个分片上传成功后,服务端必须原子化更新状态(如 Redis
SADD),失败则不更新,否则会出现“客户端以为传了,服务端没记”的撕裂
最易被忽略的一点:分片大小固定但最后一片往往不足,io.ReadAt 或 Blob.slice 返回的实际长度可能小于预期,必须检查 n 并显式截断,否则脏数据会污染整个文件。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











