大文件上传实现
通过分片上传、断点续传等方式实现大文件上传
问题
如何实现大文件上传?
解答
大文件上传的核心思路是将文件变小,通过压缩或分块后再上传。主要有以下几种实现方式:
1. 分片上传
将大文件拆分成小的文件块(chunk),通过多个并行请求依次上传。服务器接收后存储,最后合并所有文件块还原原始文件。这种方法可以降低单个请求负载,支持断点续传。
前端分片实现:
// 使用 File.prototype.slice 方法分割文件
function createChunks(file, chunkSize = 2 * 1024 * 1024) {
const chunks = [];
let start = 0;
while (start < file.size) {
const chunk = file.slice(start, start + chunkSize);
chunks.push(chunk);
start += chunkSize;
}
return chunks;
}
// 上传分片
async function uploadChunks(file, chunks) {
const fileHash = await calculateHash(file); // 计算文件 hash
const uploadPromises = chunks.map((chunk, index) => {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('hash', fileHash);
formData.append('index', index);
formData.append('total', chunks.length);
return fetch('/upload', {
method: 'POST',
body: formData
});
});
// 等待所有分片上传完成
await Promise.all(uploadPromises);
// 通知服务端合并
await fetch('/merge', {
method: 'POST',
body: JSON.stringify({ hash: fileHash, total: chunks.length })
});
}
服务端合并实现(Node.js):
const fs = require('fs');
const path = require('path');
function mergeChunks(fileHash, total, outputPath) {
const writeStream = fs.createWriteStream(outputPath);
for (let i = 0; i < total; i++) {
const chunkPath = path.join(__dirname, 'chunks', `${fileHash}-${i}`);
const readStream = fs.createReadStream(chunkPath);
readStream.pipe(writeStream, { end: i === total - 1 });
// 删除分片文件
readStream.on('end', () => {
fs.unlinkSync(chunkPath);
});
}
}
2. 断点续传
记录上传进度,网络中断后从上次位置继续上传:
async function uploadWithResume(file, chunks) {
const fileHash = await calculateHash(file);
// 查询已上传的分片
const uploaded = await fetch(`/check?hash=${fileHash}`).then(r => r.json());
const uploadPromises = chunks.map((chunk, index) => {
// 跳过已上传的分片
if (uploaded.includes(index)) {
return Promise.resolve();
}
return uploadChunk(chunk, fileHash, index);
});
await Promise.allSettled(uploadPromises); // 使用 allSettled 处理失败情况
}
3. 其他优化方案
- 流式上传:客户端使用流方式逐步读取文件,通过 POST 请求发送数据流,减少内存占用
- 压缩上传:客户端先压缩文件再上传,减少上传时间和带宽消耗
- 并发控制:限制同时上传的分片数量,避免请求过多
- 使用云存储服务:如 Amazon S3、阿里云 OSS 等,提供了优化的大文件上传方案
关键点
- 前端使用
File.prototype.slice方法分割文件,服务端使用流(readStream/writeStream)合并分片 - 每个分片携带文件 hash、分片序号和总数,服务端按序号合并保证顺序正确
- 使用
Promise.all等待所有分片上传完成后,发送合并请求通知服务端 - 上传失败时,使用
Promise.allSettled处理,根据返回的失败信息重传对应分片 - 实现断点续传需要记录上传进度,查询已上传分片后跳过继续上传
目录