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

PHP无法下载保存文件 配置open_basedir正常仍提示文件不存在错误

问题排查与修复方案

核心问题点

  • cURL参数配置错误:CURLOPT_RETURNTRANSFER 设置为 0 时,curl_exec() 会直接输出请求返回的图片内容,仅返回布尔值表示请求成功/失败,你拿到的$tarotcard根本不是图片二进制内容,无法写入文件。
  • 请求URL格式错误:代码中$url = "https:.gfx.tarot.com/..." 写法错误,https后应该是双斜杠//而不是点,直接导致图片请求失败。
  • 文件可写性判断逻辑错误:is_writable() 对不存在的文件判断会直接返回false,你还没创建$file就做判断,导致后续写入文件的代码完全不会执行,图片文件根本不会生成。
  • Zip操作逻辑错误:$zip->addFile() 只是标记要添加的文件路径,不会立即把文件内容写入压缩包,你添加后立刻执行unlink($file)删除源文件,最终生成的压缩包是空的,甚至会生成失败。
  • 无错误判断逻辑:mkdir()、$zip->open()、curl_exec() 等操作都没有加返回值判断,出错时无法定位问题,直接往下执行最终就会出现找不到zip文件的报错。
  • 响应头拼写错误:ContentDisposition 少了横杠,正确写法是Content-Disposition,会导致下载时文件名异常。

修复后的核心代码片段

$Deck = $Decks[$_GET['rip']];
// 先校验Deck合法性避免路径穿越
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $Deck)) {
    die("非法参数");
}

$zip = new ZipArchive();
$zpath = "/tmp/tarot/".$Deck."/";
if(!file_exists($zpath)){
    // 加mkdir返回判断
    if (!mkdir($zpath, 0700, true)) {
        die("目录创建失败");
    }
}
$zhandle = $zpath.$Deck.".zip";

if(!file_exists($zhandle)){
    // 加zip打开结果判断
    if ($zip->open($zhandle, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
        die("无法创建zip文件");
    }

    for($i=0; $i<78; $i++){
        // 修正URL格式
        $url = "https://gfx.tarot.com/images/site/decks/".$Deck."/full_size/".$i.".jpg";
        $proxy = "192.168.1.69:9050";
        $cURL = curl_init($url);
        curl_setopt($cURL, CURLOPT_URL, $url);
        curl_setopt($cURL, CURLOPT_PROXY, $proxy);
        curl_setopt($cURL, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
        curl_setopt($cURL, CURLOPT_FOLLOWLOCATION, 1);
        // 修正参数,开启返回内容而非直接输出
        curl_setopt($cURL, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($cURL, CURLOPT_HEADER, 0);
        $tarotcard = curl_exec($cURL);
        // 加curl请求判断
        if(curl_errno($cURL) || $tarotcard === false){
            curl_close($cURL);
            continue;
        }
        curl_close($cURL);

        $file = $zpath."/".$i.".jpg";
        // 改为判断目录可写,而非不存在的文件
        if(is_writable($zpath)){
            $fhandle = fopen($file, 'wb+');
            if($fhandle){
                fwrite($fhandle, $tarotcard);
                fclose($fhandle);
            }
        }
            
        if(file_exists($file)){
            $zip->addFile($file);
            // 不要在这里删,等zip关闭后再统一清理
            // unlink($file);
        }
    }

    $zip->close();
    // 现在再清理临时图片文件
    for($i=0; $i<78; $i++){
        $file = $zpath."/".$i.".jpg";
        if(file_exists($file)){
            unlink($file);
        }
    }

    // 先判断zip是否生成成功再输出
    if(file_exists($zhandle)){
        header('Content-Type: application/zip');
        // 修正header拼写
        header('Content-Disposition: attachment; filename='.$Deck.'.zip');
        header('Content-Length: '.filesize($zhandle));
        readfile($zhandle);
        // 可选:下载后删除zip避免占用磁盘
        // unlink($zhandle);
    }else{
        die("zip文件生成失败");
    }
}else{
    // 已有文件直接输出
    header('Content-Type: application/zip');
    header('Content-Disposition: attachment; filename='.$Deck.'.zip');
    header('Content-Length: '.filesize($zhandle));
    readfile($zhandle);
}

exit();

额外排查项

如果修复后还是有问题,可排查系统是否开启了systemd的PrivateTmp配置:Apache/httpd服务如果开启了PrivateTmp=true,会给服务分配独立的临时目录,你手动操作看到的/tmp和PHP进程访问的/tmp不是同一个路径,手动切换用户创建的文件PHP自然找不到。可以修改/usr/lib/systemd/system/httpd.service文件将PrivateTmp设为false,然后执行systemctl daemon-reload && systemctl restart httpd生效。

内容的提问来源于stack exchange,提问作者headrift

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 21:18:02