思路很简单,就是将大文件分块切割列出一个编号和清单,然后设置并发数同时上传发给后端,后端根据分块和编号清单数据最后组装还原成原始的大文件,上传的过程中将已完成的批次保存到localstorage中,下次刷新后直接发送未完成部分。
示例代码:
前端 JavaScript 代码:
document.getElementById('fileUpload').addEventListener('change', handleFileSelect, false); function handleFileSelect(event) { const file = event.target.files[0]; const chunkSize = 1024 * 1024; // 比如我们设定每个分块的大小为1MB。 let chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; const concurrency = 3; // 并发上传的数目。 let offset = 0; // 用于记录当前并发上传块的位置。 // 读取localStorage中的上传进度。 function getUploadProgress() { let progress = localStorage.getItem(file.name); return progress ? parseInt(progress, 10) : 0; } // 保存上传进度到localStorage。 function saveUploadProgress(chunk) { localStorage.setItem(file.name, chunk.toString()); } // 创建分块上传的函数。 function uploadChunk(chunk) { var start = chunk * chunkSize; var end = Math.min(file.size, start + chunkSize); var formData = new FormData(); formData.append('file', file.slice(start, end)); // 获取文件的某个片段。 formData.append('fileName', file.name); formData.append('fileSize', file.size); formData.append('chunk', chunk); // 当前片段的编号。 formData.append('chunks', chunks); // 总片段数。 fetch('upload.php', { method: 'POST', body: formData }).then(response => response.text()).then(data => { if (data === 'success') { // 如果块上传成功,则保存进度,并尝试上传下一个块。 saveUploadProgress(chunk + 1); if (currentChunk < chunks - 1) { currentChunk++; uploadNextChunk(); } } else { // 如果上传失败,再次尝试上传该块。 uploadChunk(chunk); } }).catch(err => console.error('上传块出错:', err)); } // 同时上传下一个块的函数。 function uploadNextChunk() { while (offset < concurrency && currentChunk + offset < chunks) { uploadChunk(currentChunk + offset++); } offset = 0; // 重置offset用于下一个并发上传批次。 } // 断点续传逻辑,根据进度决定从哪个块开始上传。 currentChunk = getUploadProgress(); // 开始上传。 uploadNextChunk(); }PHP 后端代码 (upload.php):
<?php $targetDir = "uploads/"; // 上传文件存储目录。 $fileName = basename($_POST['fileName']); // 上传文件的原始名称。 $fileSize = $_POST['fileSize']; // 上传文件的原始大小。 $chunk = $_POST['chunk']; // 当前片段的编号。 $chunks = $_POST['chunks']; // 总片段数。 // 以文件名和分块编号生成临时文件名,避免不同会话中的文件冲突。 $tempFilePath = $targetDir . $fileName . '.' . $chunk; // 把文件分块先保存下来。 if (!move_uploaded_file($_FILES['file']['tmp_name'], $tempFilePath)) { echo 'fail'; return; } // 如果是最后一块,则合并所有的片段。 if ($chunk == $chunks - 1) { $finalFilePath = $targetDir . $fileName; $file = fopen($finalFilePath, 'ab'); for ($i = 0; $i < $chunks; $i++) { $tmpFilePath = $targetDir . $fileName . '.' . $i; $chunkFile = fopen($tmpFilePath, 'rb'); $content = fread($chunkFile, filesize($tmpFilePath)); fwrite($file, $content); fclose($chunkFile); unlink($tmpFilePath); // 删除分块文件。 } fclose($file); } echo 'success';在这个例子里,我们在前端将文件分成了多个块,并使用 fetch API 上传这些块到一个名为 upload.php 的PHP脚本。PHP脚本处理上传的分块,将它们保存到服务器的特定目录,并在上传完所有分块之后将它们合并成原始文件。
这只是一个例子,在生产环境中,你需要增加很多安全和性能优化的措施。这包括处理并发和竞态条件、验证文件完整性、增加验证上传进度的API端点以及可能的权限检查和加密措施。此外,也需要测试并优化上传性能,以适应不同网络和服务器环境。
网友回复