PHP指定日期范围多文件批量下载问题求助
我来帮你排查问题,先拆解你代码里的几个关键错误,再给你修复后的版本,最后分享更高效的实现思路:
你的代码里的核心问题
DatePeriod不包含结束日期
你用new DatePeriod(new DateTime($from_date), new DateInterval('P1D'), new DateTime($to_date))遍历日期时,这个方法不会包含结束日期。比如如果$from_date是2024-05-01,$to_date是2024-05-03,只会遍历5月1日和2日,直接漏掉3日的文件。scandir返回了无效目录项
scandir($dir)会返回当前目录(.)和上级目录(..),你没有过滤这些项,导致可能把无效路径加入$valid_files,甚至尝试打包目录,这会让ZipArchive静默失败,最终生成空压缩包。没有验证文件的有效性
即使文件名匹配日期,也可能是目录或不存在的文件,直接调用addFile会失败,但ZipArchive默认不会抛出错误,最终导致压缩包为空。临时压缩包的权限与清理问题
你直接在当前脚本目录创建download.zip,如果该目录没有写入权限,压缩包创建会失败;另外下载完成后没有删除临时文件,会导致服务器积累垃圾文件。潜在的提前输出问题
如果脚本在发送header之前有任何输出(比如PHP标签前的空格、调试echo),会导致header发送失败,下载的文件损坏或为空。
修复后的完整代码
<?php // 确保PHP标签前无任何空格或输出 $folder = $_POST['doc_type']; $from_date = $_POST['from_date']; $to_date = $_POST['to_date']; // 修复DatePeriod:包含结束日期,给结束日期加1天 $start = new DateTime($from_date); $end = new DateTime($to_date); $end->modify('+1 day'); $period = new DatePeriod($start, new DateInterval('P1D'), $end); $valid_files = []; $base_dir = '../../docs/' . $folder; // 先验证目标目录是否存在 if (!is_dir($base_dir)) { die("指定的文件夹不存在,请检查参数"); } $files = scandir($base_dir); foreach ($period as $date){ $formatted_date = $date->format('dmy'); foreach($files as $file){ // 过滤.、..和目录,只处理文件 if ($file === '.' || $file === '..' || is_dir($base_dir . '/' . $file)) { continue; } // 匹配日期前缀的文件 if(strpos($file, $formatted_date) !== false){ $full_path = $base_dir . '/' . $file; // 再次验证文件存在且可读 if (is_file($full_path) && is_readable($full_path)) { $valid_files[] = $full_path; } } } } // 无匹配文件时提示用户 if (empty($valid_files)) { die("没有找到符合日期范围的文件"); } // 使用系统临时目录创建压缩包,避免权限问题 $zipname = tempnam(sys_get_temp_dir(), 'batch_download_') . '.zip'; $zip = new ZipArchive; $zip_status = $zip->open($zipname, ZipArchive::CREATE | ZipArchive::OVERWRITE); if ($zip_status !== true) { die("无法创建压缩包,错误码:$zip_status"); } // 把文件加入压缩包,用原始文件名避免路径嵌套 foreach($valid_files as $file){ $zip->addFile($file, basename($file)); } $zip->close(); // 发送正确的下载头信息 header('Content-Description: File Transfer'); header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="' . htmlspecialchars(basename($zipname)) . '"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($zipname)); // 输出文件并清理临时文件 readfile($zipname); unlink($zipname); exit; ?>
更优的实现思路
1. 用glob直接匹配文件(更高效)
不用遍历所有文件再匹配日期,直接用glob生成日期匹配的文件路径,减少循环次数:
<?php // ...前面的参数获取与目录验证逻辑... foreach ($period as $date){ $formatted_date = $date->format('dmy'); // 直接匹配包含该日期的PDF文件 $pattern = $base_dir . '/*' . $formatted_date . '*.pdf'; $matched_files = glob($pattern, GLOB_NOSORT); if (!empty($matched_files)) { foreach ($matched_files as $file) { if (is_file($file) && is_readable($file)) { $valid_files[] = $file; } } } } // ...后续打包逻辑同上... ?>
2. 直接输出压缩包到浏览器(无需临时文件)
可以跳过创建本地临时文件,直接将压缩包内容输出到php://output,节省磁盘空间且更高效:
<?php // ...前面的valid_files获取逻辑... // 发送下载头 header('Content-Description: File Transfer'); header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="batch_download.zip"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); // 直接输出到浏览器,无需本地文件 $zip = new ZipArchive; if ($zip->open('php://output', ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { die("无法创建压缩包"); } foreach($valid_files as $file){ $zip->addFile($file, basename($file)); } $zip->close(); exit; ?>
内容的提问来源于stack exchange,提问作者Paddy Hallihan

