文件切片难点在于传输一致性而非切片本身;浏览器用 blob.slice 零拷贝,服务端禁用 parsemultipartform 防内存溢出。

Go 文件切片本身不难,难的是切完怎么传、传断了怎么续、服务端怎么接住——90% 的失败不是因为不会切,而是切完没管好上下文一致性。
客户端怎么安全切片并定位 offset
浏览器端别用 FileReader.readAsArrayBuffer 把整个大文件读进内存再切,容易卡死或 OOM。直接用 Blob.slice(start, end) 零拷贝截取二进制段,返回新 Blob 对象。
Node.js 或 CLI 场景下,用 os.Open 打开文件一次,每个分片前调用 file.Seek(offset, io.SeekStart) 定位,再用 io.LimitReader(file, chunkSize) 读取固定长度——避免多个 goroutine 共享一个 *os.File 导致指针混乱。
关键细节:
- 分片大小建议 ≥ 5MB(尤其对接 OSS/S3),太小可能触发
EntityTooSmall错误 - 每个分片必须带唯一
uploadId和严格递增的partNumber(从 1 开始,不能跳号) - 上传前检查本地缓存状态(如
.upload_state.json),跳过已成功分片,只重试失败的offset
服务端为什么必须禁用 ParseMultipartForm
Go 标准库默认在首次访问 r.MultipartForm 时自动调用 r.ParseMultipartForm(32 ,会把整个请求体读进内存或临时文件,导致后续无法从 <code>r.Body 流式读取分片数据——这是 90% 断点续传失败的根源。
正确做法是在 handler 开头第一行就写:r.ParseMultipartForm(0)(传 0 表示禁用自动解析)。之后所有元信息(file_id、chunk_index、total_chunks)必须从 r.URL.Query().Get("...") 或 r.FormValue("...") 获取,别碰 r.MultipartForm.Value。
分片二进制数据直接从 r.Body 读,推荐用:io.CopyN(dst, r.Body, chunkSize),并确保 dst 是用 os.O_CREATE | os.O_WRONLY | os.O_EXCL 打开的文件,防止并发重复写同一分片。
续传时 Range 头怎么写才不被服务端忽略
客户端发 Range 请求不是加个 header 就完事。常见错误是写成 "bytes=1024- "(末尾多空格)或 "bytes=1024-" 后面还跟横杠,服务端直接返回 200 或 400。
必须严格格式化为:req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset)),且仅当服务端真实支持时才启用该逻辑。
验证方式不是看 Accept-Ranges: bytes 响应头(Nginx/CDN 常伪造),而是发试探请求:HEAD /file.zip 或 GET /file.zip 带 Range: bytes=0-1023,只接受 206 Partial Content 且 Content-Range 匹配(如 bytes 0-1023/12345678)。
若返回 200,说明服务端不支持,得清空本地文件重下;若返回 416,需先用 HEAD 拿真实长度再重试。
写文件为什么不能用 os.O_APPEND
os.O_APPEND 是断点续传最大陷阱:它会让 Write() 忽略之前所有 Seek(),强制写到文件末尾。结果就是本该写入第 10MB 的数据,实际落到了 20MB 位置,中间全是空白填充或覆盖错位。
正确打开方式:f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644)(不加 O_TRUNC,不加 O_APPEND)。
打开后立刻:n, _ := f.Seek(offset, io.SeekStart),并检查 n == offset;不等说明文件已被外部修改,不能继续。
写入前可选调用 f.Truncate(expectedSize) 确保文件长度足够,防止稀疏文件问题;并发写同一文件时,Seek+Write 非原子,必须用 sync.Mutex 包裹,或改用单 goroutine 顺序写。
真正复杂的地方不在“怎么切”,而在于客户端 offset、服务端分片索引、磁盘落盘路径、合并时序、校验指纹这五者必须时刻对齐——漏掉任意一环,续传就变成静默损坏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











