PHP创建大体积Zip文件后下载:如何确保压缩完成再触发下载?
问题描述
已实现PHP中Zip文件的创建与下载,但处理大于200MB的大Zip文件时,PHP会在压缩完成前就开始下载。压缩速度正常,尝试无压缩存储也未解决问题。
现有代码
创建Zip文件的函数
function makeRouteArchive($routeID) { // Get real path for our folder $rootPath = self::get_location($routeID); $relRootPath = $rootPath; while (!scandir($relRootPath)) { $relRootPath = '../'.$relRootPath; } $info = new SplFileInfo($relRootPath); $rootlen = strlen( $info->getRealPath()); $zippath = $this->archive_root; while (!scandir($zippath)){ $zippath = '../'.$zippath; } $routeText = 'oute'.sprintf('%07d',$routeID); $zipfilename = 'route'.sprintf('%07d',$routeID).'.zip'; $zipfile = $zippath.$zipfilename; // Initialize archive object $zip = new ZipArchive(); // Create recursive directory iterator /** @var SplFileInfo[] $files */ $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($relRootPath), RecursiveIteratorIterator::SELF_FIRST ); $zip->open($zipfile, ZipArchive::CREATE | ZipArchive::OVERWRITE); foreach ($files as $name => $file) { // Skip directories (they would be added automatically) if (!$file->isDir()) { // Get real and relative path for current file $filePath = $file->getRealPath(); $relativePath = substr($filePath, $rootlen + 1); if (!strpos($relativePath,$routeText) === false) { if (strpos($relativePath,'Picture_') > 0) { $relativePath = str_replace('Picture_','Pictures/',$relativePath); $relativePath = 'Storage Card/RoutAbel/'.$relativePath; } elseif (strpos($relativePath,'Media_') > 0) { $relativePath = str_replace('Media_','Media/',$relativePath); $relativePath = 'Storage Card/RoutAbel/'.$relativePath; } elseif (strpos($relativePath,'ARobjects_') > 0) { $relativePath = str_replace('ARobjects_','ARobjects/',$relativePath); $relativePath = 'Storage Card/RoutAbel/'.$relativePath; } elseif (strpos($relativePath,'_') == 12 ) { $relativePath = 'Storage Card/RoutAbel/'.substr_replace($relativePath,'/Content/',12,1); $relativePath = str_replace('_Marker','/Content/Marker', $relativePath); $relativePath = str_replace('_Slider','/Content/Slider', $relativePath); } // Add current file to archive $zip->addFile($filePath, $relativePath); } } } // Zip archive will be created only after closing object $zip->close(); return $result; }
主代码
$zipfile = makeRouteArchive($routeID); if (headers_sent()) { echo 'HTTP header already sent'; } else { if (!is_file($zipfile)) { header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found'); echo 'File not found'; } else if (!is_readable($zipfile)) { header($_SERVER['SERVER_PROTOCOL'].' 403 Forbidden'); echo 'File not readable'; } else { while (ob_get_level()) { ob_end_clean(); } ob_start(); header($_SERVER['SERVER_PROTOCOL'].' 200 OK'); header("Content-Type: application/zip"); header("Content-Transfer-Encoding: Binary"); header("Content-Length: ".filesize($file)); header('Pragma: no-cache'); header("Content-Disposition: attachment; filename=\"".basename($zipfile)."\""); ob_flush(); ob_clean(); readfile($zipfile); exit; } }
解决方案
1. 修复Zip函数的返回值错误
原函数最后返回的$result从未定义,导致主代码中$zipfile拿到的是无效值,小文件场景下缓冲机制可能掩盖问题,大文件则会提前触发下载流程。将函数末尾的:
return $result;
改为:
return $zipfile;
2. 修正响应头中的文件长度变量
主代码中filesize($file)里的$file未定义,导致Content-Length头错误,改为:
header("Content-Length: ".filesize($zipfile));
3. 优化输出缓冲逻辑
确保发送响应头前彻底清空所有输出缓冲,避免提前输出内容触发下载,调整主代码中的缓冲处理部分:
// 彻底清空所有输出缓冲 while (ob_get_level()) { ob_end_clean(); } // 直接发送响应头,无需再次启动缓冲 header($_SERVER['SERVER_PROTOCOL'].' 200 OK'); header("Content-Type: application/zip"); header("Content-Transfer-Encoding: Binary"); header("Content-Length: ".filesize($zipfile)); header('Pragma: no-cache'); header("Content-Disposition: attachment; filename=\"".basename($zipfile)."\""); // 直接输出文件内容 readfile($zipfile); exit;
4. 增加大文件处理的超时设置
在makeRouteArchive函数开头添加超时设置,避免压缩大文件时脚本被终止:
function makeRouteArchive($routeID) { // 取消脚本超时限制(需确保服务器允许) set_time_limit(0); // 剩余原有代码... }
5. 验证Zip文件生成状态
在$zip->close()后添加状态检查,确保压缩完成且无错误:
$zip->close(); // 检查Zip是否生成成功 if ($zip->status !== ZipArchive::ER_OK) { // 处理错误,比如返回false或抛出异常 return false; } return $zipfile;
同时在主代码中增加对返回值的判断:
$zipfile = makeRouteArchive($routeID); if (!$zipfile) { header($_SERVER['SERVER_PROTOCOL'].' 500 Internal Server Error'); echo 'Failed to create zip archive'; exit; } // 剩余原有代码...
内容的提问来源于stack exchange,提问作者user1886216
相关产品推荐
相关产品推荐

