思路很简单,就是将大文件分块切割列出一个编号和清单,然后设置并发数同时上传发给后端,后端根据分块和编号清单数据最后组装还原成原始的大文件,上传的过程中将已完成的批次保存到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端点以及可能的权限检查和加密措施。此外,也需要测试并优化上传性能,以适应不同网络和服务器环境。
网友回复


