大文件分片上传技术实践:分片上传、断点续传与MD5秒传
大文件分片上传是现代Web应用中常见的需求,尤其是在处理大文件上传时,分片上传、断点续传和MD5秒传技术可以显著提升用户体验和上传效率。以下是一个详细的技术实践方案:
1. 分片上传
分片上传是将大文件分割成多个小块(chunks),然后逐个上传到服务器。这样可以减少单次上传的数据量,降低网络波动对上传的影响。
实现步骤:
-
文件分片:使用
File
对象的slice
方法将文件分割成多个小块。const chunkSize = 5 * 1024 * 1024; // 5MB const chunks = []; let start = 0; while (start < file.size) { const chunk = file.slice(start, start + chunkSize); chunks.push(chunk); start += chunkSize; }
-
上传分片:使用
FormData
将每个分片上传到服务器。const uploadChunk = async (chunk, index) => { const formData = new FormData(); formData.append('file', chunk); formData.append('chunkIndex', index); formData.append('totalChunks', chunks.length); formData.append('fileId', fileId); // 文件唯一标识 await fetch('/upload', { method: 'POST', body: formData, }); };
-
合并分片:所有分片上传完成后,通知服务器合并分片。
const mergeChunks = async () => { await fetch('/merge', { method: 'POST', body: JSON.stringify({ fileId, fileName: file.name }), headers: { 'Content-Type': 'application/json', }, }); };
2. 断点续传
断点续传允许用户在上传过程中断后,从中断的地方继续上传,而不需要重新上传整个文件。
实现步骤:
-
记录上传进度:在客户端记录已上传的分片索引。
const uploadedChunks = new Set();
-
检查已上传分片:在上传前,向服务器查询已上传的分片。
const getUploadedChunks = async () => { const response = await fetch(`/uploaded-chunks?fileId=${fileId}`); const data = await response.json(); return data.uploadedChunks; };
-
跳过已上传分片:在上传时跳过已上传的分片。
const uploadedChunks = await getUploadedChunks(); for (let i = 0; i < chunks.length; i++) { if (!uploadedChunks.includes(i)) { await uploadChunk(chunks[i], i); } }
3. MD5秒传
MD5秒传是通过计算文件的MD5值,与服务器上的文件进行比对,如果文件已存在,则直接返回文件地址,无需重复上传。
实现步骤:
-
计算文件MD5:使用
spark-md5
等库计算文件的MD5值。const calculateMD5 = (file) => { return new Promise((resolve, reject) => { const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; const chunkSize = 2 * 1024 * 1024; // 2MB const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; const spark = new SparkMD5.ArrayBuffer(); const fileReader = new FileReader(); fileReader.onload = function (e) { spark.append(e.target.result); currentChunk++; if (currentChunk < chunks) { loadNext(); } else { resolve(spark.end()); } }; fileReader.onerror = function () { reject('MD5 calculation failed'); }; const loadNext = () => { const start = currentChunk * chunkSize; const end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); }; loadNext(); }); };
-
检查文件是否存在:将MD5值发送到服务器,检查文件是否已存在。
const checkFileExists = async (md5) => { const response = await fetch(`/check-file?md5=${md5}`); const data = await response.json(); return data.exists ? data.fileUrl : null; };
-
秒传处理:如果文件已存在,直接返回文件地址;否则,继续上传。
const md5 = await calculateMD5(file); const fileUrl = await checkFileExists(md5); if (fileUrl) { console.log('File already exists:', fileUrl); } else { await uploadFile(file); }
4. 服务器端处理
服务器端需要处理分片上传、合并分片、记录上传进度、检查文件MD5等逻辑。
示例(Node.js + Express):
-
分片上传:
app.post('/upload', (req, res) => { const { fileId, chunkIndex } = req.body; const chunk = req.files.file; const chunkPath = `./uploads/${fileId}-${chunkIndex}`; chunk.mv(chunkPath, (err) => { if (err) return res.status(500).send(err); res.send('Chunk uploaded'); }); });
-
合并分片:
app.post('/merge', (req, res) => { const { fileId, fileName } = req.body; const mergedFilePath = `./uploads/${fileName}`; const writeStream = fs.createWriteStream(mergedFilePath); for (let i = 0; i < totalChunks; i++) { const chunkPath = `./uploads/${fileId}-${i}`; const chunk = fs.readFileSync(chunkPath); writeStream.write(chunk); fs.unlinkSync(chunkPath); } writeStream.end(); res.send('File merged'); });
-
检查文件MD5:
app.get('/check-file', (req, res) => { const { md5 } = req.query; const filePath = `./uploads/${md5}`; if (fs.existsSync(filePath)) { res.json({ exists: true, fileUrl: `/uploads/${md5}` }); } else { res.json({ exists: false }); } });
总结
通过分片上传、断点续传和MD5秒传技术,可以显著提升大文件上传的效率和用户体验。前端负责文件分片、上传进度管理和MD5计算,后端负责分片存储、合并和文件校验。这种方案不仅适用于Web应用,也可以扩展到移动端和桌面端应用。