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
相关产品推荐
相关产品推荐

