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

Laravel中file_type为1时ZIP文件下载返回空200响应问题求助

Laravel中file_type为1时ZIP文件下载返回空200响应问题求助

大家好,我最近碰到个头疼的问题:在Laravel项目里做了个根据file_type下载带文件夹或仅文件的ZIP接口,当file_type为2时一切正常,但file_type为1时,前端Angular调用接口会返回空的200响应,控制台还报net::ERR_FAILED 200 (OK)的错误。

奇怪的是,服务器上storage/app/tmp/目录下生成的ZIP文件是存在的,手动解压后里面的文件和文件夹都完整,而且Laravel日志、Nginx日志里完全没有错误信息,这让我排查起来特别迷茫,想请教下有没有朋友碰到过类似问题,或者能给点排查方向?

相关代码如下:

1. Laravel控制器下载方法

public function downloadZip($file_type, $resId) {
    $reservation = Reservation::with('contact.insurer')->where('id', $resId)->first();

    if (Auth::user()->hasRole('verzekeraar')) {
        $contact = Auth::user()->userable;
        if ($contact->insurer_id != $reservation->contact->insurer_id) {
            return AppJsonResponse::UnAuthorized('U heeft geen rechten voor dit dossier.');
        }
    }

    $files = [];
    $dirs = [];

    if ($file_type == 2) {
        $files = $reservation->files;
    } else {
        $dirs = Folder::whereNull('parent_id')->where('reservation_id', $reservation->id)->get();
        $files = File::whereNull('parent_id')
            ->where('reservation_id', $reservation->id)
            ->where('file_type_id', 1)
            ->get();
    }

    $zip_file = storage_path('app/tmp/wrapper.zip');

    // Ensure tmp directory exists
    if (!file_exists(storage_path('app/tmp'))) {
        mkdir(storage_path('app/tmp'), 0755, true);
    }

    // Initializing PHP class
    $zip = new \ZipArchive();
    if ($zip->open($zip_file, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== TRUE) {
        \Log::error("Cannot open <$zip_file>\n");
        return AppJsonResponse::Error('ZIP creation failed.');
    }

    if ($file_type == 2) {
        foreach ($files as $file) {
            if ($file->file_type_id == $file_type) {
                $zip->addFile(Storage::disk('private')->getDriver()->getAdapter()->applyPathPrefix($file->path), 'storage/' . $file->name . '.' . strtolower($file->ext));
            }
        }
    } else {
        $ultPath = 'storage/';
        $this->zipDocuments($dirs, $files, $ultPath, $reservation->id, $zip);
    }

    if (!$zip->close()) {
        \Log::error("Failed to close the zip file.");
    } else {
        \Log::debug("Successfully closed zip file: $zip_file");
    }

    if (file_exists($zip_file)) {
        Log::info("File size of ZIP: " . filesize($zip_file));
        return response()->download($zip_file, 'wrapper.zip', [
            'Content-Type' => 'application/zip',
        ]);
    } else{
        \Log::error('ZIP file is missing or too small: ' . $zip_file);
        return AppJsonResponse::Error('ZIP creation failed.');
    }
}

2. 递归添加文件夹的辅助方法

public function zipDocuments($dirs, $files, $ultPath, $resId, $zip) {
    foreach ($dirs as $dir) {
        $currentPath = $ultPath . $dir->name . '/';
        \Log::debug("Entering directory: $currentPath");

        // Add all files in current directory
        $filesInDir = File::where('parent_id', $dir->id)
            ->where('reservation_id', $resId)
            ->where('file_type_id', 1)
            ->get();
        foreach ($filesInDir as $file) {
            $fullPath = Storage::disk('private')->getDriver()->getAdapter()->applyPathPrefix($file->path);
            if (!file_exists($fullPath)) {
                \Log::warning("File not found: $fullPath");
                continue;
            }
            if($zip->addFile($fullPath, $currentPath . $file->name . '.' . strtolower($file->ext)) === true){
                // 原代码无日志记录
            }
        }

        // Recursively process subfolders
        $subDirs = Folder::where('parent_id', $dir->id)->where('reservation_id', $resId)->get();
        $this->zipDocuments($subDirs, [], $currentPath, $resId, $zip);
    }

    // Add top-level files (only for first call)
    foreach ($files as $file) {
        $fullPath = Storage::disk('private')->getDriver()->getAdapter()->applyPathPrefix($file->path);
        if (!file_exists($fullPath)) {
            \Log::warning("Top-level file not found: $fullPath");
            continue;
        }
        $zip->addFile($fullPath, $ultPath . $file->name . '.' . strtolower($file->ext));
    }

    return $zip;
}

3. Angular前端请求代码

downloadZip(id, file_type): any {
  return this.http.get('/audits/downloadzip/'+file_type+'/'+id, {
    observe: 'response', 
    responseType: 'blob', 
    headers: new HttpHeaders({'Cache-Control': 'no-cache'})
  });
}

目前排查到的现象:

  • 当file_type=1时,服务器storage/app/tmp/wrapper.zip存在且内容完整,filesize日志显示文件大小正常(比如几MB)
  • 前端请求返回200 OK,但响应体为空,控制台报net::ERR_FAILED
  • file_type=2时,ZIP下载完全正常,前端能收到文件

可能的解决方案和排查点:

1. 清空输出缓冲区后再返回响应

Laravel的response()->download()有时会因为输出缓冲区残留的内容(比如之前的调试输出、框架日志片段)导致响应异常,你可以在返回前强制清空缓冲区:

if (file_exists($zip_file)) {
    Log::info("File size of ZIP: " . filesize($zip_file));
    
    // 清空输出缓冲区,避免残留内容干扰
    ob_clean();
    flush();
    
    return response()->download($zip_file, 'wrapper.zip', [
        'Content-Type' => 'application/zip',
    ])->deleteFileAfterSend(true); // 可选:下载完成后自动删除临时文件
}

或者换成更底层的文件输出方式,绕开Laravel的响应封装:

if (file_exists($zip_file)) {
    Log::info("File size of ZIP: " . filesize($zip_file));
    
    header('Content-Type: application/zip');
    header('Content-Disposition: attachment; filename="wrapper.zip"');
    header('Content-Length: ' . filesize($zip_file));
    
    ob_clean();
    flush();
    readfile($zip_file);
    
    unlink($zip_file); // 下载后删除临时文件
    exit; // 确保后续没有代码输出
}

2. 检查zipDocuments中文件添加是否有隐形错误

目前你的zipDocuments方法里,addFile返回false时没有日志记录,可能某些文件添加失败导致ZIP结构隐性异常(手动解压正常但HTTP传输时出错)。可以添加日志排查:

// 在zipDocuments的文件循环中
if($zip->addFile($fullPath, $currentPath . $file->name . '.' . strtolower($file->ext)) === true){
    \Log::debug("成功添加文件到ZIP: {$fullPath}");
} else {
    \Log::error("添加文件到ZIP失败: {$fullPath}");
}

3. 检查前端拦截器是否干扰Blob响应

如果你的Angular项目有全局HTTP拦截器,可能会尝试将所有响应解析为JSON,导致Blob类型的响应被破坏。可以在拦截器里跳过Blob类型的响应处理:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  return next.handle(request).pipe(
    tap((event: HttpEvent<any>) => {
      // 仅处理非Blob类型的响应
      if (event instanceof HttpResponse && request.responseType !== 'blob') {
        // 你的原有JSON响应处理逻辑
      }
    }),
    catchError((error: HttpErrorResponse) => {
      // 错误处理时同样判断responseType
      if (request.responseType === 'blob') {
        return throwError(() => error);
      }
      // 原有错误处理
    })
  );
}

4. 服务器配置调整

  • Nginx:如果使用Nginx作为反向代理,开启proxy_buffering可能会对大文件下载产生影响,可以在location配置中关闭:
location /api {
    proxy_buffering off;
    proxy_pass http://your-laravel-upstream;
    # 其他配置...
}
  • Apache:禁用ZIP文件的gzip压缩(ZIP本身已是压缩格式,二次压缩可能导致传输异常):
<FilesMatch "\.zip$">
    SetEnv no-gzip 1
</FilesMatch>

5. 确认临时文件的读取权限

虽然目录权限是755,但Web服务器用户(比如www-data)可能没有临时文件的读取权限,你可以在创建文件后手动设置:

// 关闭ZIP文件后添加
chmod($zip_file, 0644);

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 09:25:28