断点续传需自行控制http头、文件打开模式(禁用os.o_append)、seek位置及中断恢复逻辑,必须通过head+range试探验证服务端真实支持能力,并持久化.offset状态至.syncstate文件。

直接用 os.Copy 或简单 io.Copy 配合 http.Get 无法实现可靠断点续传——它既不校验服务端是否真支持 Range,也不处理文件偏移错位、写入覆盖、状态丢失等核心问题。必须自己控制 HTTP 头、文件打开模式、seek 位置和中断恢复逻辑。
如何判断服务端是否真实支持断点续传
光看响应头 Accept-Ranges: bytes 是陷阱:Nginx、CDN 常伪造该字段,但实际对 Range 请求返回 200 OK 或直接 500。必须用试探请求验证行为。
- 先发一个
HEAD /path/to/file,拿到Content-Length和Accept-Ranges - 再发
GET /path/to/file带Range: bytes=0-1023,检查响应码是否为206 Partial Content,且Content-Range值匹配(如bytes 0-1023/12345678) - 若返回
200,说明服务端忽略Range,应清空本地文件走全量下载;若返回416,说明已知长度不一致,需重新HEAD获取真实长度 - 不要依赖客户端传来的
Upload-Offset或Content-Range字段做唯一判断——它们可能被篡改或计算错误
为什么不能用 os.O_APPEND 打开文件续传
os.O_APPEND 是断点续传最隐蔽的坑:它会让所有 Write() 调用强制跳转到文件末尾,完全无视你之前调用的 file.Seek(offset, io.SeekStart)。POSIX 行为 + Go 运行时共同导致数据错位或填充空白。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 正确打开方式是:
os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644)(不加O_TRUNC,不加O_APPEND) - 打开后立刻调用
file.Seek(offset, io.SeekStart),并检查返回值是否等于offset;不等说明文件已被外部修改,不可信 - 写入前可选调用
file.Truncate(expectedSize)清除尾部脏数据,避免稀疏文件问题 - 并发写同一文件时,
Seek+Write非原子,必须用sync.Mutex包裹,或改用单 goroutine 顺序写
如何安全读取并写入断点位置
本地文件已存在时,“当前长度”不等于“可信续传起点”。崩溃、截断、部分写入都会让 fi.Size() 失效。必须结合偏移校验与轻量哈希。
- 每次开始续传前,用
os.Stat()获取当前文件大小作为初始offset,但仅作参考 - 写入前调用
file.Seek(offset, io.SeekStart),失败则中止;成功后建议立即file.Sync(),防止内核缓存未刷盘影响下次读取 - 对大文件或高一致性要求场景,客户端应在请求头带
X-Prev-Chunk-Hash: sha256...,服务端读取对应区间比对;不匹配就返回416并附带正确Content-Range - 用
io.LimitReader(src, expectedBytes)包裹响应体,防止客户端谎报长度后发送多余字节
断点状态必须落地且可重启恢复
把 offset 存内存或只靠文件长度推断,服务重启、进程崩溃、部署更新后就全丢了。状态必须持久化,且足够轻量。
- 推荐方案:为每个同步任务生成一个
.syncstate文件,内容为 JSON:{"filename":"a.zip","offset":1234567,"etag":"abc123","updated":"2026-06-08T23:20:00Z"} - 写入时先写临时文件(如
.syncstate.tmp),再os.Rename()原子提交,避免写一半崩溃导致状态损坏 - 不要用数据库或 Redis 存这种元信息——引入额外依赖、网络延迟、连接失败风险,违背工具轻量初衷
- 每次同步前先读取该文件,校验
filename和etag是否仍匹配(etag可从服务端HEAD响应中获取),不匹配则视为新任务
真正难的不是“怎么发 Range 请求”,而是当网络抖动、磁盘满、权限变更、服务端悄悄降级时,你的工具能否在下次启动时准确识别“哪些已写对、哪些要重试、哪些该放弃”。状态落地、偏移校验、open 模式选择,这三处漏掉任何一环,断点续传就退化成概率性功能。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










