大文件传输后哈希不一致,99%因校验未与I/O同步:必须用io.Copy+hash.Hash流式计算,禁用os.ReadFile/io.ReadAll;需校验io.Copy错误、确保流完整消费、独立hash实例、小写比对、服务端合并后重算SHA256。

大文件传输后哈希值不一致,99% 是因为校验和计算没跟上数据流的真实消费路径,不是算法或网络问题。
io.Copy + hash.Hash 是唯一靠谱的流式校验模式
别用 os.ReadFile 或 io.ReadAll 先读全量再算哈希——GB 级文件直接 OOM。所有校验必须和 I/O 同步发生。
-
io.Copy返回实际写入字节数,必须检查err != nil且n > 0,否则可能因磁盘满、权限错、连接断而静默截断 - 每个校验任务必须用独立的
hash.Hash实例(如sha256.New()),复用不调Reset()会累加上次结果 - 用
h.Sum(nil)获取结果,不要用h.Sum([]byte{})—— 后者可能复用底层数组,在并发场景下被覆盖
上传时边读边算 MD5/SHA256 的正确姿势
HTTP 上传中 r.Body 是单次可读流,读完即 EOF。想校验又保存,必须用 io.TeeReader 把数据“分发”给哈希器和目标写入器。
- 先创建
h := md5.New()或sha256.New() - 包装请求体:
teeReader := io.TeeReader(r.Body, h) - 后续用
io.Copy(dst, teeReader)落盘,dst可以是*os.File、对象存储 writer 或内存 buffer - 最后取
fmt.Sprintf("%x", h.Sum(nil)),这个值和落盘内容严格一致 - 切记:如果
io.Copy没跑完就 return,哈希只算了一部分——必须确保整个流被消费
分块上传(tus/S3 Multipart)怎么端到端校验
单次 HTTP 请求内的 io.TeeReader 只保当前块一致性。完整文件校验必须在服务端合并后,独立重算一次。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 客户端生成完整文件哈希(不是每块哈希拼接),通过
X-Expected-SHA256Header 传,别放 URL 或 form 字段里 - 服务端合并所有分片后,用
os.Open+io.Copy(h, f)重新计算,再比对 - 拒绝信任
r.Header.Get("Content-MD5")—— 它是 Base64 编码的 RFC 1864 校验和,和原始十六进制 MD5 字符串不等价 - 若需 CRC32,注意字节序:
binary.BigEndian.Uint32用于 HTTP/ZIP 协议,binary.LittleEndian.Uint32是 Go 默认
生产环境必须用 SHA256,MD5 仅限内部快速比对
MD5 已被证实可构造碰撞,禁止用于安全敏感场景(如签名、权限控制)。SHA256 是当前生产推荐底线。
- 校验失败时,错误信息要带具体动作和路径,例如
"read /tmp/upload_abc: input/output error",方便日志归因 - 空文件的 SHA256 是固定值(
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855),但必须显式判断n == 0并报错,防止逻辑绕过 - 多个文件并行校验时,每个开独立 goroutine + 独立
hash.Hash实例,避免共享状态污染
最容易被忽略的是:校验必须发生在保存前,且不能依赖任何中间缓存或临时变量;一旦文件已写入磁盘,再开新句柄重算,就得承担竞态、权限、符号链接等额外风险。

















