断点续传不能用os.o_append,因其强制write()跳转至文件末尾并忽略seek(),导致数据错位和静默损坏;必须用os.o_create|os.o_wronly打开文件,先seek校验偏移再writeat写入,并配合服务端content-range校验与.resume状态落盘。

为什么 os.O_APPEND 不能用在断点续传写入中
它会让 file.Seek() 失效——无论你定位到哪,Write() 都强制写到文件末尾,中间填满 \0,静默损坏文件。这不是 Go 的 bug,是 POSIX 标准行为。
- 必须用
os.O_CREATE | os.O_WRONLY打开已存在文件(不加os.O_TRUNC) - 写入前调
file.Seek(offset, io.SeekStart),并检查返回值是否等于offset;不等说明文件可能被截断或被其他进程修改 - 更稳妥的写法是
file.WriteAt(data, offset),它不依赖当前文件指针,避免竞态 - 别信“
os.O_APPEND+Seek就能追加”的直觉,实测会出错
io.LimitReader 和 io.ReadAt 哪个更适合分片上传
io.LimitReader 更安全:它封装原 *os.File,不移动底层文件偏移,支持并发多次调用;而直接对同一 *os.File 并发调 io.CopyN 或 Read,会导致指针错乱、内容重叠或空块。
- 推荐组合:
file.ReadAt(buf, offset)—— 线程安全、无副作用、不改文件内部状态 - 若用
io.LimitReader,构造方式为io.LimitReader(file, int64(chunkSize)),注意它返回新io.Reader,可复用 - 避免在 goroutine 里反复
os.Open同一文件;一次os.OpenFile(..., os.O_RDONLY, 0),全程复用句柄 - 最后一块大小不足时,
io.LimitReader自动截断,无需额外判断长度
如何校验断点位置是否可信
仅靠本地文件长度判断续传起点极危险:文件可能被篡改、部分写入失败、或被其他程序覆盖。必须引入服务端协同校验。
- 下载场景:收到响应后,解析
Content-Rangeheader,如"bytes 1024-9999/10000",提取起始偏移并与本地written比对;不匹配则清空重下 - 上传场景:服务端应基于
file_id + offset + size三元组做幂等判断,而非仅靠chunk_index - 客户端可预计算每 MB 的 SHA256 前缀,上传时带
X-Prev-Chunk-Hash,服务端比对对应区间 - 本地断点状态必须落盘,推荐独立
.resume文件(如video.mp4.resume),内容为offset=12345678\nurl=https://...
服务端禁用 ParseMultipartForm 是硬性要求
Go HTTP 默认会在首次访问 r.MultipartForm 时自动调用 r.ParseMultipartForm(32,把整个请求体读进内存或临时文件——这会提前消费 <code>r.Body,导致后续 io.Copy 读到空数据,分片写入失败或错位。
- 必须在 handler 第一行加
r.ParseMultipartForm(0),显式禁用自动解析 - 元信息(
upload_id、chunk_index)从 URL query 或r.FormValue()获取,不要碰r.MultipartForm.Value - 若需解析 multipart 元数据,改用
r.MultipartReader()流式处理,避免全量加载 - HTTP Header 必须设
Content-Type: application/octet-stream;误设multipart/form-data且无 boundary 会直接 400
Stat() 校验文件大小和 modtime,以及 .resume 文件写入失败时怎么回退——这些边界没覆盖好,断点就变成“断点陷阱”。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











