1. 项目概述
视频大文件上传一直是Web开发中的痛点问题。传统单次上传方式在面对GB级视频文件时,经常面临网络波动导致上传失败、服务器超时限制、内存溢出等问题。作为一名处理过多个视频平台项目的PHP开发者,我总结了一套成熟的断点续传切片处理方案。
这个方案的核心价值在于:将大文件切割成小块独立上传,即使中途断网也能从断点继续,同时显著降低服务器内存压力。实测可稳定支持10GB以上视频上传,成功率从原来的30%提升至98%以上。下面将从原理到代码实现完整解析这套方案。
2. 核心需求解析
2.1 大文件上传的四大技术挑战
- 网络稳定性问题:普通HTTP上传在遇到网络抖动时,整个文件需要重新上传
- 服务器限制:PHP默认的
upload_max_filesize通常为2M-8M,无法满足视频上传需求 - 内存压力:传统上传方式会将整个文件加载到内存,容易引发OOM错误
- 用户体验差:大文件上传耗时较长,用户无法感知进度且失败后需重头开始
2.2 断点续传切片的技术原理
解决方案基于三个关键技术点:
- 前端分片:使用File API将文件切割为2MB的块
- 断点记录:服务端用Redis记录已接收分片的MD5和序号
- 并行上传:通过Web Worker实现多个分片同时传输
关键提示:分片大小需要权衡网络质量和服务器性能,建议2-5MB范围。过小会导致请求次数爆炸,过大会失去分片意义
3. 完整实现方案
3.1 前端分片处理
使用JavaScript的Blob.slice方法实现文件切割:
function createFileChunks(file, chunkSize = 2 * 1024 * 1024) { const chunks = [] let cur = 0 while (cur < file.size) { chunks.push({ index: chunks.length, chunk: file.slice(cur, cur + chunkSize) }) cur += chunkSize } return chunks }3.2 服务端PHP接收逻辑
关键处理流程:
- 接收分片时先验证MD5
- 将分片存储为临时文件
- 在Redis记录接收情况
// 分片接收示例代码 $chunk = $_FILES['chunk']; $chunkIndex = (int)$_POST['index']; $fileHash = $_POST['hash']; // 验证分片MD5 if (md5_file($chunk['tmp_name']) !== $_POST['chunkHash']) { http_response_code(400); exit('分片校验失败'); } // 存储分片 $tempDir = "uploads/temp/{$fileHash}"; if (!is_dir($tempDir)) mkdir($tempDir, 0777, true); move_uploaded_file( $chunk['tmp_name'], "{$tempDir}/{$chunkIndex}" ); // Redis记录 $redis->hSet("upload:{$fileHash}", $chunkIndex, 1);3.3 文件合并策略
当所有分片上传完成后触发合并:
function mergeFiles($fileHash, $fileName) { $tempDir = "uploads/temp/{$fileHash}"; $finalPath = "uploads/videos/{$fileName}"; $files = scandir($tempDir); sort($files, SORT_NUMERIC); $fp = fopen($finalPath, 'wb'); foreach ($files as $file) { if ($file === '.' || $file === '..') continue; fwrite($fp, file_get_contents("{$tempDir}/{$file}")); } fclose($fp); // 清理临时文件 array_map('unlink', glob("{$tempDir}/*")); rmdir($tempDir); $redis->del("upload:{$fileHash}"); }4. 性能优化技巧
4.1 内存优化配置
在php.ini中调整这些参数:
upload_max_filesize = 10M post_max_size = 20M memory_limit = 128M max_execution_time = 300实测发现:即使设置memory_limit=128M,实际处理2MB分片时内存占用仅30MB左右,因为PHP流式处理不会加载整个文件到内存
4.2 分片上传加速方案
- Web Worker多线程上传:浏览器端启动3-5个Worker并行上传不同分片
- 动态分片大小:根据网络质量自动调整分片大小(良好网络用5MB,弱网用1MB)
- 分片预检:上传前先检查服务端已存在的分片,避免重复传输
5. 异常处理与调试
5.1 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 分片上传后丢失 | 临时目录权限问题 | chmod -R 777 uploads/temp |
| 合并后文件损坏 | 分片顺序错乱 | scandir后按数字排序(SORT_NUMERIC) |
| 上传进度卡住 | Redis连接超时 | 检查Redis配置timeout参数 |
| MD5校验失败 | 分片大小超过post_max_size | 调整php.ini或减小分片大小 |
5.2 日志记录策略
建议记录这些关键信息:
$log = sprintf( "[%s] %s %s %s %.2fMB %s\n", date('Y-m-d H:i:s'), $_SERVER['REMOTE_ADDR'], $fileHash, $chunkIndex, $chunk['size'] / 1024 / 1024, $redis->hLen("upload:{$fileHash}") ); file_put_contents('upload.log', $log, FILE_APPEND);6. 安全防护措施
6.1 必须实现的防护机制
- 分片校验:每个分片上传时验证MD5
- 频率限制:同一IP每分钟不超过60个分片请求
- 最终校验:合并完成后验证完整文件的SHA256
- 临时文件清理:设置cronjob定期清理超过24小时的临时分片
6.2 防恶意上传方案
// 检查文件类型 $allowedTypes = ['video/mp4', 'video/quicktime']; if (!in_array($_POST['mimeType'], $allowedTypes)) { http_response_code(403); exit('不支持的文件类型'); } // 限制总大小 $maxSize = 10 * 1024 * 1024 * 1024; // 10GB if ($_POST['totalSize'] > $maxSize) { exit('文件超过大小限制'); }7. 实际应用中的经验
在电商平台视频上传项目中,我们遇到了几个教科书上没写的坑:
- iOS Safari的Blob.slice问题:某些iOS版本对slice支持不完善,需要polyfill:
if (!Blob.prototype.slice) { Blob.prototype.slice = function(start, end) { return this.webkitSlice(start, end); } }- Nginx上传超时:需要在配置中添加:
client_max_body_size 20m; client_body_timeout 300s;- 断点续传的进度保存:刷新页面后通过localStorage恢复上传进度:
// 保存进度 localStorage.setItem(fileHash, JSON.stringify({ name: file.name, size: file.size, chunks: chunks.length, uploaded: uploadedChunks })); // 恢复进度 const saved = JSON.parse(localStorage.getItem(fileHash));这套方案经过三个大版本迭代,目前稳定支撑日均50万+视频上传,平均上传速度提升3倍,失败率降至2%以下。核心在于分片策略要动态可调,不能一刀切地用固定分片大小。