go断点续传核心是客户端精准定位写入位置、服务端真实支持range且双方状态一致;需手动设range头、校验206状态码,避免依赖os.o_append或伪造accept-ranges。

Go 语言做远程文件断点续传,核心不是“能不能发请求”,而是客户端能否精准定位写入位置、服务端是否真正支持 Range、双方状态能否一致。硬套 os.O_APPEND 或依赖 r.ParseMultipartForm 默认行为,90% 会失败。
怎么发一个真正有效的 Range 请求
HTTP 断点续传靠的是客户端明确告诉服务端“从第 N 字节开始下载”,这必须通过 Range 请求头实现。Go 的 http.Client 不自动处理,得手动加:
- 先用
os.Stat检查本地文件是否存在、大小是多少,offset := fileInfo.Size() - 构造请求头:
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset)),注意格式必须严格为bytes=12345-,不能多空格、不能写成bytes=12345-- - 务必检查响应状态码:只接受
206 Partial Content;若返回200 OK,说明服务端忽略 Range,应清空本地文件重下 - 别只看响应头有
Accept-Ranges: bytes——Nginx、CDN 常伪造该 header,但实际不支持;必须靠试探请求验证
服务端怎么可靠接收分片并避免内存爆炸
服务端若直接调用 r.ParseMultipartForm(32 ,标准库会把整个请求体读进内存或临时文件,破坏流式写入能力,且无法控制落盘路径和时机。
- 在 handler 开头立即调用
r.ParseMultipartForm(0)(传 0 表示禁用自动解析),否则r.Body已被消费,后续读不到原始字节流 - 元信息(如
file_id、chunk_index)应从 URL 查询参数或表单字段获取,别依赖r.MultipartForm.Value——它可能为空或延迟可用 - 分片数据直接从
r.Body读取,用io.CopyN(dst, r.Body, chunkSize)写入磁盘,路径建议为./uploads/{file_id}/chunk_{index:04d} - 写入前用
os.MkdirAll确保目录存在;并发写时加os.O_EXCL防止覆盖同一分片文件
本地文件怎么写才不会错位或覆盖
os.O_APPEND 是断点续传最大陷阱:它会让 Write() 忽略所有 Seek(),强制写到末尾,导致数据错位或空白填充。
- 正确打开方式是:
os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644),**不加os.O_APPEND,也不加os.O_TRUNC** - 打开后立即
file.Seek(offset, io.SeekStart),并检查返回值是否等于offset;不等说明文件被其他进程修改过 - 写入前建议用
file.Truncate(expectedSize)确保文件长度至少为预期值,防止稀疏文件问题 - 别用
bufio.Writer包裹*os.File,缓冲会导致实际写入位置和Seek位置不一致
断点状态怎么存才安全且防篡改
断点信息不能只存在内存里,但也不该塞进数据库或复杂配置——简单即可靠。
- 最轻量方式:把当前已下载字节数写进同名的
.download.state文件(如foo.zip.download.state),内容就一行纯数字 - 每次启动先
os.Stat检查状态文件是否存在;存在则读取,并校验原文件大小是否匹配该数值——不匹配说明文件被手动修改过,应清空状态重下 - 写状态文件必须用
os.WriteFile(原子替换),别用os.Create + Write,否则中断时留下半截状态 - 仅靠文件大小判断是否续传不可靠,关键业务建议增加哈希校验:客户端上传前对已传部分计算 SHA256,服务端比对上一块 hash,不一致则返回
416并附带正确Content-Range
真正难的不是写几行 Seek 或 Range,而是在网络不稳定、服务端配置不一、文件系统差异大的真实场景里,让偏移量、写入位置、状态文件、服务端分片存储全部对齐。任何一环松动,续传就会变成覆盖或错位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











