前言
大家好,这里是程序员阿亮!
不知道大家做项目时候是否有遇到这种场景:
文件上传进度99%了!但是突然网络抖动,进度归0!
重复的文件被不同用户重复上传导致服务器上重复文件达几百上千份,服务器资源被无效占用!
面对这种场景,业界有一种方案,也是我项目中使用的方案:
分片上传与多重哈希判重
一、核心目的
我们的目标是解决三个核心问题:
-
稳定性:网络中断后,能从断开的地方继续传(断点续传)。
-
效率:相同的文件,服务器只存一份(秒传)。
-
安全性:防止哈希碰撞导致的文件覆盖(多重 Hash 校验)。
1.1 为什么是"分片" + "多重 Hash"?
-
分片 (Sharding):将大象切成肉块。1GB 的文件切成 200 个 5MB 的小块。传一个小块失败了,重试代价很小。
-
多重 Hash (Multi-Hashing):
-
MD5:计算快,用作"快速索引"。
-
SHA-256:计算慢但极难碰撞,用作"最终身份确认"。
-
策略 :MD5 负责初筛(快),SHA-256 负责终审(准)。
-
二、核心流程
1.后端存储结构
后端通常采用两级存储方案:临时切片区 (Temp/Staging) 和 持久存储区 (Final/Storage) 。
/data/uploads/
├── temp/ # 临时目录:存放未完成的切片
│ └── [MD5_Value]/ # 以文件的 MD5 哈希作为文件夹名
│ ├── 0 # 切片文件,以索引 index 命名
│ ├── 1
│ └── 2
└── final/ # 持久目录:存放合并后 的完整文件
├── 0a/ # (可选) 两级散列,防止单目录文件过多
│ └── 0a1b2c...3f.zip # 以 SHA-256 哈希命名
└── d4/
└── d4e5f6...78.mp4
-
临时文件夹名 :使用 MD5。
- 原因:MD5 计算极快。当用户选择文件后,前端能在几秒内算出 MD5 并发给后端。后端立即以这个 MD5 创建文件夹,作为本次上传任务的"会话 ID"。
-
最终文件名 :使用 SHA-256。
- 原因:MD5 存在理论上的碰撞可能。为了确保在文件传输中 A 的文件绝不会覆盖 B (即使 MD5 撞了),最终持久化存储必须使用更长、更安全的 SHA-256。
在核心流程中实际上前后端都需要计算SHA-256,为什么呢?
| 角色 | 计算时机 | 目的 |
|---|---|---|
| 前端 (Client) | 只有当服务器反馈"MD5 已存在"时才算 | 申请秒传。证明自己拥有的文件确实和服务器上的一模一样。 |
| 后端 (Server) | 文件合并完成后(Merge)必算 | 终审校验。确保传输过程中没有丢包、没有被篡改,并生成最终的文件身份指纹。 |
这是因为MD5有可能冲突 并且后端也有前端不可信原则
2.详细流程
第一步:预检查 (Verify / Pre-flight)
当接收到前端传来的 MD5 时:
-
查库 :在数据库中搜索
file_md5 = [MD5]。 -
命中:如果库里有,说明可能重复。此时后端要求前端:"请计算并提供该文件的 SHA-256"。
-
二次核对:前端传回 SHA-256 后,后端对比。如果完全一致,返回"秒传成功",流程结束。
-
未命中 :如果库里没有,检查
temp/[MD5]文件夹是否存在,返回已存在的切片列表(实现断点续传)。
第二步:切片接收 (Upload Chunk)
-
路径锁定 :根据前端传来的
hash (MD5)定位到temp/[MD5]/文件夹。 -
原子写入:
-
后端接收到二进制流。
-
先写入一个临时文件
[index].part。 -
写入完成后,使用
fs.rename(在 Linux 下是原子操作) 将其重命名为[index]。 -
原因:防止某个切片只传了一半时后端崩溃,导致切片损坏。
-
第三步:流式合并 (Stream Merging)
当收到 merge 请求时:
-
排序 :读取
temp/[MD5]/下所有文件,按数字大小严格排序。 -
创建流 :创建一个指向
final/目录的WriteStream。 -
顺序抽取 :使用
ReadStream依次读取切片 0, 1, 2... -
管道合并 :通过
pipe将切片流注入目标文件。- 注意 :合并过程必须是串行的(传完 0 再传 1),否则文件顺序会乱。
第四步:身份确认与入库 (Final Validation)
这是最体现"详细"的地方:
-
后端重算 :合并完成后,后端对
final/下的那个完整大文件进行SHA-256计算。 -
一致性比对:
-
如果前端在合并请求里带了
sha256_client,后端核对sha256_server === sha256_client。 -
如果不一致,说明文件坏了,删掉报错。
-
-
元数据入库:
- 在数据库记录:
文件名,原始大小,MD5,SHA-256,存储路径,
- 在数据库记录:
-
清理垃圾 :删除
temp/[MD5]/文件夹及其所有切片。
总结
这套方案是许多项目实践使用的方案,既能提高用户的使用感,又能保护服务器资源!

