// 假设通过POST接收合并请求
if ($_POST['action'] == 'merge') {
$file_md5 = $_POST['file_md5'];
$filename = $_POST['filename']; // 原始文件名
$temp_dir = 'uploads/temp/' . $file_md5 . '/';
$final_path = 'uploads/final/' . $filename;
// 检查临时目录是否存在
if (!is_dir($temp_dir)) {
http_response_code(404);
echo json_encode(['error' => 'Temporary directory not found']);
exit;
}
// 获取目录下所有分片文件并按索引排序
$chunk_files = glob($temp_dir . '*');
sort($chunk_files, SORT_NUMERIC);
// 打开最终文件进行写入
$final_handle = fopen($final_path, 'wb');
if ($final_handle === false) {
http_response_code(500);
echo json_encode(['error' => 'Cannot create final file']);
exit;
}
$merge_success = true;
foreach ($chunk_files as $chunk_file) {
$chunk_data = file_get_contents($chunk_file);
if ($chunk_data === false || fwrite($final_handle, $chunk_data) === false) {
$merge_success = false;
break;
}
// 可选:删除已合并的分片以释放空间
unlink($chunk_file);
}
fclose($final_handle);
// 删除临时目录
rmdir($temp_dir);
if ($merge_success) {
echo json_encode([
'success' => true,
'message' => 'File merged successfully.',
'url' => $final_path
]);
} else {
// 如果合并失败,清理可能已部分写入的最终文件
if (file_exists($final_path)) {
unlink($final_path);
}
http_response_code(500);
echo json_encode(['error' => 'File merge failed']);
}
}四、前端进度显示实现
前端需要计算并展示整体进度。整体进度 = (已成功上传的分片数 / 总分片数) * 100%。
<!DOCTYPE html>
<html>
<head>
<title>视频上传进度显示</title>
</head>
<body>
<input type="file" id="videoFile" accept="video/*">
<button onclick="startUpload()">开始上传</button>
<br><br>
<div>上传进度: <span id="progressText">0%</span></div>
<progress id="progressBar" value="0" max="100"></progress>
<script>
const CHUNK_SIZE = 2 * 1024 * 1024; // 每个分片2MB
let totalChunks = 0;
let uploadedChunks = 0;
const uploadUrl = 'https://www.ipipp.com/upload.php'; // 上传接口地址
async function startUpload() {
const fileInput = document.getElementById('videoFile');
if (!fileInput.files.length) {
alert('请先选择一个视频文件');
return;
}
const file = fileInput.files[0];
totalChunks = Math.ceil(file.size / CHUNK_SIZE);
uploadedChunks = 0;
updateProgress(); // 初始化进度显示
const fileMd5 = await calculateFileMD5(file); // 假设此函数能计算文件MD5
const chunks = [];
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
chunks.push({
index: i,
blob: file.slice(start, end)
});
}
// 控制并发数,例如最多同时上传3个分片
const MAX_CONCURRENT = 3;
for (let i = 0; i < chunks.length; i += MAX_CONCURRENT) {
const chunkBatch = chunks.slice(i, i + MAX_CONCURRENT);
await Promise.all(chunkBatch.map(chunk => uploadChunk(chunk, fileMd5, totalChunks)));
}
// 所有分片上传完成后,请求合并
await mergeFile(fileMd5, file.name);
alert('文件上传并合并完成!');
}
function uploadChunk(chunk, fileMd5, totalChunks) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file_md5', fileMd5);
formData.append('chunk_index', chunk.index);
formData.append('total_chunks', totalChunks);
formData.append('chunk_data', chunk.blob);
const xhr = new XMLHttpRequest();
xhr.open('POST', uploadUrl, true);
// 监听当前分片的上传进度(可选,用于更细粒度的显示)
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
// 可以在这里计算更精确的进度,本例简化处理
}
};
xhr.onload = function() {
if (xhr.status === 200) {
uploadedChunks++;
updateProgress();
resolve();
} else {
reject(new Error(`Upload failed for chunk ${chunk.index}`));
}
};
xhr.onerror = function() {
reject(new Error('Network error'));
};
xhr.send(formData);
});
}
async function mergeFile(fileMd5, filename) {
const formData = new FormData();
formData.append('action', 'merge');
formData.append('file_md5', fileMd5);
formData.append('filename', filename);
const response = await fetch(uploadUrl, {
method: 'POST',
body: formData
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error);
}
console.log('Merge result:', result);
}
function updateProgress() {
const percent = totalChunks > 0 ? Math.round((uploadedChunks / totalChunks) * 100) : 0;
document.getElementById('progressText').textContent = percent + '%';
document.getElementById('progressBar').value = percent;
}
// 简易的文件MD5计算函数(注意:前端计算大文件MD5可能耗时,实际项目中可使用Web Crypto API或上传后由服务器计算)
async function calculateFileMD5(file) {
// 此处为简化示例,返回一个模拟值。实际应用应使用密码学API。
return 'simulated_md5_hash_for_' + file.name + '_' + file.size;
}
</script>
</body>
</html>五、优化与注意事项
断点续传: 服务器端可以记录已上传的分片索引。在上传开始前,前端先查询服务器,跳过已上传的分片,实现断点续传功能。
安全性: 务必对上传的文件进行严格的验证,包括文件类型(通过MIME类型和后缀)、大小限制,并将最终文件存储在Web根目录之外或配置正确的访问权限,防止恶意文件上传和执行。
服务器配置: 确保PHP配置(
php.ini)中的upload_max_filesize、post_max_size以及max_execution_time等参数足以支持大文件上传。进度准确性: 本示例以前端成功发送请求作为分片上传成功的依据。更严谨的做法是,以后端确认存储成功并返回特定响应为准。
替代方案: 对于更复杂的应用,可以考虑使用专门的JavaScript库(如Resumable.js、Uppy)或云存储服务提供的SDK,它们通常内置了强大的分片上传和进度管理功能。
通过上述分片上传的方案,我们能够有效地实现视频文件上传的进度显示,大幅提升用户在处理大文件上传时的体验。开发者可以根据项目的具体需求,对前后端的逻辑进行进一步的定制和增强。