在php8.5中处理视频上传,如果仍用普通的multipart表单直接提交大文件,很容易碰到脚本超时、内存溢出以及客户端网络抖动导致前功尽弃的情况。大文件分片上传的核心思路,是把一个完整的视频在浏览器端切割为多个小块,逐块发送给后端,后端接收后暂存,最终合并为原始文件。这种方法不仅降低了单次请求的体积,也天然支持了暂停、续传与进度展示。

前端如何切片并发送视频块
前端切片依赖浏览器原生的Blob对象,它的slice方法能够在不加载整个文件到内存的情况下,返回指定字节范围的子文件对象。我们通常会为一次上传任务生成一个唯一标识,比如利用时间戳加随机数,或者由后端在下发上传凭证时返回。每个分片除了二进制内容,还要携带分片索引、总分片数、文件哈希等字段,方便后端校验与排序。
在发送时可以使用XMLHttpRequest或fetch,考虑到兼容性与上传进度回调,XMLHttpRequest更为直观。下面给出一个简化的原生JS示例,演示如何读取本地视频文件并分片提交。注意代码中file.slice的起始与结束位置计算,以及formData中附加的元数据。
function uploadVideo(file) {
var chunkSize = 2 * 1024 * 1024; // 每片2MB
var totalChunks = Math.ceil(file.size / chunkSize);
var fileId = 'vid_' + Date.now() + '_' + Math.floor(Math.random() * 1000);
var index = 0;
function sendNext() {
if (index >= totalChunks) {
// 通知后端合并
var endForm = new FormData();
endForm.append('action', 'merge');
endForm.append('fileId', fileId);
endForm.append('totalChunks', totalChunks);
endForm.append('fileName', file.name);
fetch('upload.php', { method: 'POST', body: endForm });
return;
}
var start = index * chunkSize;
var end = Math.min(start + chunkSize, file.size);
var blob = file.slice(start, end);
var form = new FormData();
form.append('action', 'upload');
form.append('fileId', fileId);
form.append('chunkIndex', index);
form.append('totalChunks', totalChunks);
form.append('chunk', blob);
fetch('upload.php', { method: 'POST', body: form }).then(function() {
index++;
sendNext();
});
}
sendNext();
}
上面的逻辑是串行发送,实际项目中可以改为并发控制,比如同时传3片,以提升带宽利用率。但并发数过高会给php8.5的php-fpm进程带来压力,需要结合服务器配置权衡。另外,前端最好在每次发送前检查已上传分片列表,实现断点续传,而不是每次都从第一片开始。
php8.5后端接收与暂存分片
后端接收部分,php8.5依旧使用超全局数组$_FILES与$_POST来获取分片内容和元数据。关键是要把每个分片按fileId存入独立临时目录,文件名采用零填充的索引,避免字典序混乱。比如第3片存为0003.part,这样合并时按名称排序就是正确顺序。同时应在接收时校验分片大小,防止被截断或恶意填充。
下面是一段处理单分片上传的php代码。注意我们用is_uploaded_file和move_uploaded_file来保证安全,并用str_pad格式化序号。在php8.5中,这些函数行为稳定,无需额外扩展。临时目录建议放在网站根目录之外,防止被直接访问。
<?php
$action = $_POST['action'] ?? '';
if ($action === 'upload') {
$fileId = $_POST['fileId'];
$chunkIndex = (int)$_POST['chunkIndex'];
$uploadDir = '/data/tmp_uploads/' . $fileId . '/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$chunkName = str_pad($chunkIndex, 5, '0', STR_PAD_LEFT) . '.part';
$target = $uploadDir . $chunkName;
if (isset($_FILES['chunk']) && is_uploaded_file($_FILES['chunk']['tmp_name'])) {
move_uploaded_file($_FILES['chunk']['tmp_name'], $target);
echo json_encode(['ok' => true, 'chunk' => $chunkIndex]);
} else {
echo json_encode(['ok' => false, 'error' => 'invalid chunk']);
}
}
?>
暂存阶段还要考虑清理机制。如果客户端中断后不再续传,这些临时分片会占用磁盘。可以通过定时脚本扫描临时目录的修改时间,超过24小时未更新的直接删除。此外,php8.5的默认upload_max_filesize和post_max_size对单分片大小不构成障碍,因为我们每片仅2MB,远小于常规限制,这也是分片上传降低服务端配置压力的体现。
合并分片与完整性校验
当所有分片上传完毕,前端请求合并动作,后端需要按序号读取.part文件,依次写入最终视频文件。合并时建议使用二进制追加模式,并保证读取顺序严格递增。合并完成后可计算整个文件的哈希,与前端的哈希比对,确认内容一致。若不一致,应删除残缺文件并要求重传。
以下php代码展示合并与校验的基本流程。这里用glob获取分片,再用sort按文件名排序,因为文件名是零填充的数字,字符串排序即数值排序。最终用rename或copy落地,并清理临时目录。
<?php
$action = $_POST['action'] ?? '';
if ($action === 'merge') {
$fileId = $_POST['fileId'];
$totalChunks = (int)$_POST['totalChunks'];
$fileName = basename($_POST['fileName']);
$uploadDir = '/data/tmp_uploads/' . $fileId . '/';
$parts = glob($uploadDir . '*.part');
sort($parts);
if (count($parts) != $totalChunks) {
echo json_encode(['ok' => false, 'error' => 'missing chunks']);
exit;
}
$finalPath = '/data/videos/' . $fileName;
$out = fopen($finalPath, 'wb');
foreach ($parts as $part) {
$in = fopen($part, 'rb');
stream_copy_to_stream($in, $out);
fclose($in);
unlink($part);
}
fclose($out);
rmdir($uploadDir);
echo json_encode(['ok' => true, 'path' => $finalPath]);
}
?>
完整性校验除了哈希比对,还可以在上传每个分片时让其携带该片的哈希,后端逐一校验,尽早发现错误片。对于视频文件,合并后可用ffprobe等工具探测格式是否合法,避免用户上传伪装成视频的恶意文件。整体来看,php8.5实现大文件分片上传并不复杂,重点在于前后端契约清晰、临时文件管理与失败重试机制健全,这样才能在生产环境稳定运行。
php8.5大文件分片上传video_upload修改时间:2026-08-18 18:40:44