JS—大文件上传
个人博客:haichenyi.com。感谢关注
一. 目录
- 一–目录
- 二–分片上传
- 三–生成文件唯一标识(文件哈希)
- 四–断点续传 & 秒传
- 五–分片上传(并发控制)
- 六–服务端合并分片
- 七–优化
- 八–总结
大文件上传的核心痛点是:文件过大,用户网络环境不稳定,服务器压力过大,容易中断,用户体验极差。只要解决这几个问题就行了。其实,最重要的一个问题就是文件过大。文件太大了,我们就分成几份上传就行了。这就是分片。
大文件上传的核心流程
1. 前端分片 → 2. 计算文件唯一标识 → 3. 上传分片 → 4. 服务端合并分片 → 5. 完整性校验
二. 分片上传
将大文件分割成多个固定的小文件,减少单次请求的压力。举个栗子:100M的文件,分成10份,每个10M,这样就可以解决问题。核心代码如下
//主要就是File.slice
function createFileChunks(file, chunkSize = 10 * 1024 * 1024) {
//分片数组
const chunks = []
//文件总大小
const fileSize = file.size
let start = 0
while (start < fileSize) {
//单个分片
let chunk = {
//文件数据
chunk: file.slice(start, start + fileSize),
//索引,用于文件合并时的顺序
index: chunks.length
}
chunks.push(chunk)
start += chunkSize
}
return chunks
}
三. 生成文件唯一标识(文件哈希)
使用文件内容生成唯一标识,用于标识文件,实现秒传和断点续传。代码如下
function calculateFileHash(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
const spark = new SparkMD5.ArrayBuffer();
reader.readAsArrayBuffer(file);
reader.onload = (e) => {
spark.append(e.target.result);
const hash = spark.end();
resolve(hash);
};
})
}
四. 断点续传 & 秒传
秒传: 在上传之前,检查服务端是否存在已经有相同哈希值的文件,有则跳过,没有,则上传
断点续传: 上传前,检查已经上传的分片列表,跳过已上传的分片
五. 分片上传(并发控制)
使用Promise池控制并发数(如同时上传3个分片),避免浏览器阻塞。
async function uploadChunks(chunks, fileHash, concurrency = 3) {
const pool = new Set(); // 并发池
for (const { chunk, index } of chunks) {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('hash', fileHash);
formData.append('index', index);
const task = fetch('/api/upload-chunk', {
method: 'POST',
body: formData,
}).then(() => {
pool.delete(task);
});
pool.add(task);
if (pool.size >= concurrency) {
await Promise.race(pool); // 等待任意任务完成
}
}
await Promise.all(pool); // 等待剩余任务完成
}
六. 服务端合并分片
当文件分片全都上传完成之后,前端触发文件合并逻辑,服务端合并文件。我们上传文件分片的时候上传了文件的hash值和索引。直接按照文件的hash值过滤出同一个文件的分片,然后按照索引,一次写入file即可。
七. 优化
- 分片上传进度显示。
- 错误重试机制,为每个分片上传添加重试逻辑(最多3次)。
- 浏览器本地持久化,使用localStorage或IndexedDB保存上传状态,页面刷新后恢复。
八. 总结
大文件上传的核心在于分治策略:
- 分片:将大问题拆解为小任务
- 断点续传:增强容错能力
- 并发控制:平衡速度与稳定性