直接用Symfony的UploadedFile处理大文件会失败,因PHP默认upload_max_filesize和post_max_size仅2M–8M,且UploadedFile一次性加载全量数据,易致超时、内存溢出或Nginx 413错误;分片上传为必需方案,需前端传fileId、chunkIndex、totalChunks,后端用Redis校验完整性并按序合并临时分片。

为什么直接用 Symfony 的 UploadedFile 会失败
因为 PHP 默认的 upload_max_filesize 和 post_max_size 通常设为 2M–8M,且 UploadedFile 会一次性把整个文件读入内存或临时磁盘,大文件(比如 500MB 视频)上传中途超时、内存溢出、Nginx 报 413 Request Entity Too Large 都是常态。分片不是“可选优化”,而是必须绕过的底层限制。
前端分片后,后端怎么识别和拼接?
关键靠三个客户端传来的参数:唯一标识 fileId、当前分片序号 chunkIndex、总分片数 totalChunks。服务端不做校验就拼接,容易被恶意请求覆盖或错序写入。推荐做法:
- 用
fileId作为 Redis 键前缀,记录已上传的chunkIndex集合(如SET fileId:abc123:chunks 0 1 2),避免重复上传同一片 - 每个分片保存为独立临时文件,路径形如
/tmp/uploads/{fileId}/{chunkIndex},不直接写入最终目标路径 - 收到最后一片(
chunkIndex == totalChunks - 1)时,用php://temp或fopen(..., 'wb')按序合并,再用move_uploaded_file()或symfony/filesystem移入正式存储 - 合并前检查 Redis 中是否所有
chunkIndex都存在,缺片就返回400 Missing chunk
如何在 Controller 里安全接收分片并存临时文件
别用 $request->files->get('file') —— 它依赖 $_FILES,而分片请求通常用 FormData + blob.slice() 发送,文件体在 $request->getContent() 里。正确做法是手动解析原始 body:
public function uploadChunk(Request $request, Filesystem $fs): Response
{
$fileId = $request->query->get('fileId');
$chunkIndex = (int) $request->query->get('chunkIndex', 0);
$totalChunks = (int) $request->query->get('totalChunks', 1);
<pre class='brush:php;toolbar:false;'>$chunkContent = $request->getContent();
if ($chunkContent === false || strlen($chunkContent) === 0) {
return new Response('Empty chunk', 400);
}
$chunkPath = sys_get_temp_dir().'/uploads/'.$fileId.'/'.$chunkIndex;
$fs->mkdir(dirname($chunkPath));
file_put_contents($chunkPath, $chunkContent);
// 更新 Redis 记录
$this->redis->sAdd('fileId:'.$fileId.':chunks', $chunkIndex);
return new Response('OK');}
注意:file_put_contents() 要确保磁盘有足够空间;生产环境建议用 stream_copy_to_stream() 处理超大块,避免内存峰值。
Nginx 和 PHP-FPM 的关键配置项必须改
否则前端分片发得再稳,网关层也会拦腰截断:
- Nginx:增大
client_max_body_size 2G(不是 2M),加client_body_timeout 300防止长连接超时 - PHP-FPM:调高
request_terminate_timeout = 300和request_slowlog_timeout = 60 - PHP ini:设
upload_max_filesize = 2G、post_max_size = 2G、max_execution_time = 300—— 即使单片只有 5MB,总上传耗时也可能远超默认 30 秒 - 别忘了
memory_limit至少设为512M,合并阶段可能触发大内存分配
这些值不是越大越好,但低于实际分片总大小或预期上传时长,就一定会失败。线上压测时,用 curl -F "file=@/path/to/100mb.bin" ... 模拟单片上传,比等前端调试快得多。


















