服务端必须禁用 r.parsemultipartform,因 go http 默认在首次访问 r.multipartform 时自动调用 r.parsemultipartform(32

服务端必须禁用 r.ParseMultipartForm
Go HTTP 默认会在首次访问 r.MultipartForm 时自动调用 r.ParseMultipartForm(32 ,把整个请求体读进内存或临时文件——这对分片上传是致命的:body 被提前消费,后续 <code>io.CopyN(r.Body, ...) 会读到空数据,导致分片写入失败或错位。
正确做法是在 handler 开头第一行就调用:r.ParseMultipartForm(0)。传 0 表示禁用自动解析,强制跳过 multipart 解析流程,保留原始 r.Body 流供直接读取。
- 元信息(如
file_id、chunk_index)必须从 URL 查询参数或r.FormValue()获取,不要碰r.MultipartForm.Value—— 它在未解析时为空,已解析则 body 已不可用 - 若仍需用 multipart 表单字段(比如带额外 JSON 元数据),应改用
r.MultipartReader()手动流式解析,避免全量加载 - 并发写同一分片时,用
os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644),靠系统级原子性防止覆盖
客户端分片不能靠 FileReader.readAsArrayBuffer
浏览器端用 FileReader.readAsArrayBuffer 把整个大文件读进内存再切片,不是慢的问题,是会直接触发 GC 压力、UI 卡死甚至 OOM。尤其在 2GB+ 视频场景下,Chrome 会主动 kill 页面。
真正零拷贝、低内存的方案是:Blob.slice(start, end) 直接截取二进制段,返回新 Blob,不触发任何内存复制。
- 每个分片发独立 POST 请求,URL 带
file_id(如/upload/chunk?file_id=abc123&index=5),别塞进 multipart 的file字段里 - HTTP Header 必须设
Content-Type: application/octet-stream;若误设为multipart/form-data又没提供boundary,服务端会返回 400 - 务必携带
X-Chunk-Hash头(如 SHA256 前 8 字节),服务端收到后立刻用crypto/sha256.New()+io.Copy实时校验,不落地再算
os.O_APPEND 是断点续传最大陷阱
写入已存在文件时,用 os.O_APPEND 看似“追加”,实则会让所有 Write() 忽略之前 Seek() 的定位,强制写到文件末尾——结果就是:本该写在 offset=10MB 的数据,被写到了 2GB 末尾,中间全是空白填充,文件静默损坏。
正确姿势是:打开文件时去掉 O_APPEND,用 file.Seek(offset, io.SeekStart) 定位,再用 file.WriteAt(data, offset) 写入。它不依赖当前光标位置,更安全。
- 写入前必须检查
Seek()返回值是否等于期望offset,否则说明文件可能被截断或移动过 - 合并分片时,别用
io.Copy一次性串连所有 chunk 文件——大文件容易 OOM;应循环os.Open每片 +io.CopyN分批写入目标文件 - 分片路径命名必须可排序:用
chunk_{index:04d}(如chunk_0005),别用字符串自然排序(chunk_1、chunk_10会乱序)
续传前必须验证服务端真实支持 Range
光看响应头有 Accept-Ranges: bytes 没用——Nginx、CDN 经常伪造这个 header,但实际对 Range 请求返回 200 或 500。不验证就续传,90% 概率下载出错文件。
必须先发试探请求:HEAD /file.zip 或 GET /file.zip 带 Range: bytes=0-1023,仅当响应码为 206 Partial Content 且 Content-Range 匹配(如 bytes 0-1023/12345678)才启用续传。
- 若返回 200,说明服务端忽略
Range,应清空本地文件重下 - 若返回 416,需用
HEAD获取真实长度再重试,不能硬填本地已知大小 -
Range头格式必须严格为bytes={offset}-,多一个空格、结尾多横杠都会被某些服务端拒绝
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











