You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP使用ZipArchive生成Zip文件下载后无法解压排查

PHP ZipArchive生成压缩包无法解压问题修复

问题现象

通过PHP ZipArchive类编写代码生成并下载Zip压缩包,下载得到的文件无法被解压工具正常打开,手动创建的正常压缩包与异常压缩包差异对比如下:
压缩包差异对比图

原问题代码

$zip = new ZipArchive;
$zip_name = 'test.zip';
if ($zip->open($zip_name, ZipArchive::CREATE) === TRUE)
{
 
    $zip->addFile('1.csv', 'demofile/1.csv');
    $zip->addFile('2.csv', 'demofile/2.csv');
    $zip->addFile('1.csv', 'demofile2/1.json');

    $zip->addFromString('demofile/1.csv', $content_1);
    $zip->addFromString('demofile/2.csv', $content_2);
    $zip->addFromString('demofile2/1.json', $contents);
    $zip->close();

    header('Content-type: application/zip');
    header('Content-Disposition: attachment; filename="'.$zip_name.'"');
    header("Content-length: ".filesize($zip_name));

    readfile($zip_name);

    exit();
}

故障原因

  • 同路径重复写入导致压缩包结构损坏:代码先调用addFile为3个包内路径添加本地文件,紧接着又调用addFromString给完全相同的3个路径写入字符串内容。ZipArchive不会自动覆盖同路径的旧条目,重复写入会直接破坏压缩包中央目录结构,解压工具无法正确解析文件索引。另外addFile('1.csv', 'demofile2/1.json')本身存在逻辑错误,是将csv格式文件存为json后缀,就算不重复写入也不符合预期。
  • 输出流污染:代码没有在输出Zip文件前清空输出缓冲区,只要脚本在readfile()执行前输出了任何内容(包括PHP标签外的空格、BOM头、Notice/Warning级报错),都会被拼接到Zip二进制流的开头,直接导致Zip文件头损坏。
  • 临时文件读写冲突:代码将生成的Zip文件固定存为运行目录下的test.zip,没有加文件锁也没有做唯一命名,高并发场景下会出现多个请求同时读写同一个文件的情况,可能读到写入不完整的损坏文件。
  • 缺少缓存控制头:没有设置禁止缓存的响应头,浏览器或中间代理可能缓存之前损坏的压缩包,导致用户反复下载到错误文件。

修复方案

  1. 删除冗余的addFile调用:既然已经通过addFromString传入了各文件的实际内容,不需要再调用addFile读取本地文件,从根源避免同路径重复写入问题。
  2. 打开Zip文件时增加ZipArchive::OVERWRITE标记,确保每次生成都覆盖旧文件,不会残留旧文件的损坏内容。
  3. 在输出文件前调用缓冲区清理函数,清空所有提前输出的无关内容,避免二进制流被污染。
  4. 补充缓存控制响应头,禁用客户端和代理缓存。
  5. 文件输出完成后删除临时生成的Zip文件,避免服务器残留垃圾文件,也减少后续请求的读写冲突概率。

修复后可直接使用的代码

$zip = new ZipArchive;
$zip_name = 'test.zip';
// 增加OVERWRITE标记,每次生成覆盖旧文件
if ($zip->open($zip_name, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE)
{
    // 移除冗余addFile调用,仅通过addFromString写入目标内容
    $zip->addFromString('demofile/1.csv', $content_1);
    $zip->addFromString('demofile/2.csv', $content_2);
    $zip->addFromString('demofile2/1.json', $contents);
    $zip->close();

    // 清空输出缓冲区,避免无关内容污染Zip流
    ob_clean();

    // 补充完整响应头
    header('Content-type: application/zip');
    header('Content-Disposition: attachment; filename="'.$zip_name.'"');
    header("Content-length: ".filesize($zip_name));
    header('Pragma: no-cache');
    header('Expires: 0');

    readfile($zip_name);
    
    // 删除临时文件
    unlink($zip_name);
    exit();
}

内容的提问来源于stack exchange,提问作者kovrita is The life

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 08:51:32