断点续传不支持单次哈希校验,必须在合并后重算最终哈希或采用分块哈希;未完成临时文件的哈希无意义,分片需独立校验并双重验证编号与哈希,合并后仍须最终校验确保逻辑完整性。

断点续传本身不支持单次哈希校验——因为 MD5/SHA256 是全局摘要,缺一块就全错;你真正要做的,是在服务端合并后重新计算最终哈希,或在上传阶段改用分块哈希(如每片独立 SHA256),而不是试图对“半截文件”做完整哈希比对。
为什么不能对未完成的临时文件做 MD5/SHA256 校验
常见错误是:客户端传了 60% 的数据,服务端把这部分写入 temp.part,然后调用 ComputeFileSHA256("temp.part") 去比对预期值。这必然失败,且毫无意义。
-
io.Copy对未读完的文件流会返回io.EOF,但哈希值仍是“已读部分”的摘要,和完整文件完全不同 - 临时文件可能被截断(磁盘满、权限不足、客户端中断),
os.Stat().Size和原始Content-Length不一致时,哈希结果完全不可信 - 即使大小吻合,也只说明“写入字节数对”,不代表内容正确——比如网络丢包导致某段重复或错位,哈希仍会错
分块上传场景下如何安全校验每个 part
必须把哈希校验拆到每个分片粒度,并强制客户端显式提供 X-Part-Number 和 X-Part-Hash,服务端绝不信任到达顺序。
- 接收每个 part 时,先解析
X-Part-Number:非整数或超出total_parts范围直接返回400 Bad Request - 用
os.Open重新打开刚写入的临时分片文件,调用io.Copy(hasher, file)计算 SHA256 —— 不能校验内存中原始[]byte,防止静默截断 - 比对时用
bytes.Equal(localHash[:], expectedHash),而非十六进制字符串比较,避免空格/大小写/编码污染 - 临时文件名必须带唯一标识(如 UUID),禁止用
part_3这类可预测命名,防覆盖或路径遍历
合并完成后必须重算最终哈希
所有分片通过编号+哈希双重校验后,仍需在磁盘上对合并后的完整文件执行一次最终校验——这是唯一能确认“逻辑完整性”的方式。
- 不要跳过这步去相信“所有分片都对 = 整体对”,分片拼接过程可能出错(如
os.WriteAt偏移错位、并发写冲突) - 用和客户端约定一致的算法(推荐
sha256,禁用md5),调用标准流式函数:ComputeFileSHA256(mergedPath) - 若校验失败,错误日志必须包含:原始声明哈希、实际计算哈希、文件大小、
os.Stat().ModTime()—— 用于排查是传输问题还是服务端写入异常 - 注意
os.O_CREATE | os.O_WRONLY | os.O_EXCL打开合并目标文件,避免竞态覆盖
MD5 字符串校验的几个致命细节
如果协议强制要求 MD5(比如对接旧系统),字符串比对环节极易翻车,必须手动清洗和标准化。
- HTTP header 中的
X-File-MD5可能带前后空格、换行、"引号,用strings.TrimSpace+strings.TrimPrefix预处理 - MD5 哈希字符串应为 32 个小写十六进制字符,收到
ABCDEF...或ab:cd:ef...格式需统一转换,否则==比对直接失败 - 别用
hex.DecodeString直接解码——它对非法字符 panic,应先用正则^[a-f0-9]{32}$校验格式,再解码 - 即便 MD5 字符串合法,也要检查对应文件是否为空:
if stat.Size() == 0 { return errors.New("empty file") },因空文件 MD5 固定为d41d8cd98f00b204e9800998ecf8427e
最常被忽略的是:校验失败后,临时分片和合并文件必须显式 os.Remove 清理,否则磁盘悄悄占满;而清理动作本身可能失败(如文件被占用),得用 os.IsNotExist 判断并记录 warn 级日志,不能静默吞掉错误。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











