如何在Laravel中读取AWS及本地Zip文件夹并在线展示内容?
在Laravel中实现AWS Zip文件在线预览方案
一、AWS S3中Zip文件的列表展示与在线预览实现
1. 获取S3上的Zip文件列表
先在Laravel后端写接口拉取AWS S3中的Zip文件信息,前端用jQuery请求并渲染成可点击的列表:
后端路由
Route::get('/zip-files', 'ZipFileController@list')->name('zip.files');
控制器方法
use Illuminate\Support\Facades\Storage; class ZipFileController extends Controller { public function list() { $disk = Storage::disk('s3'); // 筛选出所有.zip后缀的文件,可根据实际存储路径调整过滤规则 $zipFiles = collect($disk->files())->filter(function ($file) { return str_ends_with($file, '.zip'); })->map(function ($file) use ($disk) { return [ 'name' => basename($file), 'key' => $file, 'size' => $disk->size($file) ]; }); return response()->json($zipFiles); } }
前端jQuery渲染代码
<div id="zipList"></div> <script> $(document).ready(function() { $.get('/zip-files', function(data) { const listContainer = $('#zipList'); data.forEach(function(zip) { const link = $('<a>') .attr('href', `/zip-preview/${zip.key}`) .attr('target', '_blank') .text(`${zip.name} (${formatSize(zip.size)})`); listContainer.append($('<div>').append(link).css('margin', '5px 0')); }); }); // 文件大小格式化辅助函数 function formatSize(bytes) { if (bytes < 1024) return `${bytes} B`; else if (bytes < 1048576) return `${(bytes / 1024).toFixed(2)} KB`; else return `${(bytes / 1048576).toFixed(2)} MB`; } }); </script>
2. 创建Zip内容预览页面
用户点击Zip链接后,后端解析Zip内的目录结构,返回给前端展示;点击Zip内的文件时,再请求后端读取对应文件内容并展示。
后端路由
Route::get('/zip-preview/{fileKey}', 'ZipPreviewController@show')->name('zip.preview'); Route::get('/zip-file-content/{fileKey}/{fileName}', 'ZipPreviewController@showFileContent')->name('zip.file.content');
控制器方法
use Illuminate\Support\Facades\Storage; use ZipArchive; class ZipPreviewController extends Controller { public function show($fileKey) { $disk = Storage::disk('s3'); if (!$disk->exists($fileKey)) abort(404, 'Zip文件不存在'); // 将S3文件流写入临时文件,避免直接下载大文件 $tempPath = tempnam(sys_get_temp_dir(), 'aws_zip'); $fileStream = $disk->readStream($fileKey); file_put_contents($tempPath, stream_get_contents($fileStream)); fclose($fileStream); $zip = new ZipArchive(); if ($zip->open($tempPath) !== true) { unlink($tempPath); abort(400, '无法解析Zip文件'); } // 遍历Zip内的所有文件/目录 $contents = []; for ($i = 0; $i < $zip->numFiles; $i++) { $stat = $zip->statIndex($i); $contents[] = [ 'name' => $stat['name'], 'size' => $stat['size'], 'is_dir' => substr($stat['name'], -1) === '/' ]; } $zip->close(); unlink($tempPath); // 及时清理临时文件 return view('zip-preview', [ 'contents' => $contents, 'fileKey' => $fileKey, 'zipName' => basename($fileKey) ]); } public function showFileContent($fileKey, $fileName) { $disk = Storage::disk('s3'); if (!$disk->exists($fileKey)) abort(404, 'Zip文件不存在'); $tempPath = tempnam(sys_get_temp_dir(), 'aws_zip'); $fileStream = $disk->readStream($fileKey); file_put_contents($tempPath, stream_get_contents($fileStream)); fclose($fileStream); $zip = new ZipArchive(); if ($zip->open($tempPath) !== true) { unlink($tempPath); abort(400, '无法解析Zip文件'); } // 获取指定文件的内容 $content = $zip->getFromName($fileName); if ($content === false) { $zip->close(); unlink($tempPath); abort(404, '该文件不存在于Zip中'); } $zip->close(); unlink($tempPath); // 根据文件类型返回对应响应 $mimeType = mime_content_type($fileName); return response($content)->header('Content-Type', $mimeType); } }
预览页面视图(zip-preview.blade.php)
<h1>预览: {{ $zipName }}</h1> <div id="zipContents" style="margin-bottom: 20px;"> @foreach($contents as $item) <div class="zip-item" style="margin: 5px 0;"> @if($item['is_dir']) <span>📁</span> {{ $item['name'] }} @else <span>📄</span> <a href="javascript:void(0)" class="file-link" data-file="{{ $item['name'] }}"> {{ $item['name'] }} ({{ formatSize($item['size']) }}) </a> @endif </div> @endforeach </div> <div id="fileContent" style="padding: 10px; border: 1px solid #ccc; display: none;"></div> <script> $(document).ready(function() { $('.file-link').click(function() { const fileName = $(this).data('file'); const fileKey = '{{ $fileKey }}'; $.get(`/zip-file-content/${fileKey}/${encodeURIComponent(fileName)}`, function(data) { const contentDiv = $('#fileContent'); contentDiv.show(); // 根据MIME类型展示内容 const mimeType = this.getResponseHeader('Content-Type'); if (mimeType.startsWith('text/')) { contentDiv.html(`<pre>${data}</pre>`); } else if (mimeType.startsWith('image/')) { const blob = new Blob([data], { type: mimeType }); const url = URL.createObjectURL(blob); contentDiv.html(`<img src="${url}" style="max-width: 100%;">`); } else { contentDiv.text('暂不支持该文件类型预览'); } }).fail(function() { alert('无法读取文件内容'); }); }); }); function formatSize(bytes) { if (bytes < 1024) return `${bytes} B`; else if (bytes < 1048576) return `${(bytes / 1024).toFixed(2)} KB`; else return `${(bytes / 1048576).toFixed(2)} MB`; } </script>
核心注意点
- 临时文件必须及时删除,避免服务器磁盘被占用
- 超大Zip文件建议用
ZipArchive::getStream流式读取单个文件,避免内存溢出 - 可根据业务需求添加权限验证,限制未授权用户访问
二、在线打开本地Zip文件夹的方案
浏览器受安全策略限制,无法直接读取本地文件系统的Zip,有两种可行方案:
1. 前端JS直接解析(无需上传)
使用jszip库在浏览器端读取用户上传的Zip文件,直接解析展示:
<input type="file" id="localZipInput" accept=".zip"> <div id="localZipContent" style="margin-top: 20px;"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> <script> $('#localZipInput').change(function(e) { const file = e.target.files[0]; if (!file) return; JSZip.loadAsync(file).then(function(zip) { const contentDiv = $('#localZipContent'); contentDiv.empty(); zip.forEach(function(relativePath, zipEntry) { const item = $('<div>').css('margin', '5px 0'); if (zipEntry.dir) { item.html(`<span>📁</span> ${relativePath}`); } else { item.html(`<span>📄</span> ${relativePath}`); item.click(function() { // 根据文件类型选择读取方式 if (relativePath.match(/\.(txt|md|json)$/i)) { zipEntry.async('text').then(function(text) { $('<pre>').text(text).appendTo(contentDiv); }); } else if (relativePath.match(/\.(jpg|jpeg|png|gif)$/i)) { zipEntry.async('blob').then(function(blob) { const url = URL.createObjectURL(blob); $('<img>').attr('src', url).css('max-width', '100%').appendTo(contentDiv); }); } else { alert('暂不支持该文件类型预览'); } }); } contentDiv.append(item); }); }).catch(function(err) { alert(`无法打开Zip文件:${err.message}`); }); }); </script>
2. 上传到服务器后预览
让用户将本地Zip上传到AWS S3或服务器,之后复用第一部分的Laravel预览逻辑即可。
内容的提问来源于stack exchange,提问作者Eka Hanif
相关产品推荐
相关产品推荐

