Laravel后台实现文件管理器:支持多选storage/app文件流式打包下载(中文名转GBK防乱码),及上传ZIP包安全解压(过滤路径穿越、自动创建目录、不依赖临时文件)。

需要在Laravel后台提供一个文件管理器界面,让用户能勾选多个存储在storage/app下的文件,一键打包下载;同时支持上传ZIP包并自动解压其中的文件到指定目录,不依赖第三方包、不落地临时文件、中文名不乱码。
前端文件列表与多选表单构建
在视图中用Storage::files()列出storage/app下所有文件,每行渲染一个带name="files[]"的复选框,值为basename($path);表单enctype必须设为multipart/form-data,否则上传ZIP时会丢失二进制数据。
添加隐藏字段<input type="hidden" name="_token" value="{{ csrf_token() }}">,Laravel 10+ 默认启用CSRF保护,漏掉这行会导致419错误。
解压上传表单单独写一个<form>块,<input type="file" name="zip_file" accept=".zip" required>,【accept=".zip"可防止用户误选非压缩包,减少后端校验负担】。
后端接收多选文件并流式打包响应
控制器方法接收$request->input('files', []),遍历每个文件名,拼接真实路径:$realPath = storage_path('app/' . $filename)。
对每个$realPath执行file_exists($realPath) && is_readable($realPath)检查,跳过不存在或不可读的项——否则ZipArchive::addFile()会静默失败,压缩包缺文件却无报错。
实例化ZipArchive,调用$zip->open('php://output', ZipArchive::CREATE)直连响应体;【必须用php://output,不能用临时文件路径,否则并发下载时可能被其他请求覆盖或删除】。
逐个调用$zip->addFile($realPath, iconv('UTF-8', 'GBK', $filename)),中文名转GBK是Windows解压器默认识别编码,不转则显示乱码;macOS/Linux用户不受影响。
执行ob_end_clean()清空输出缓冲区,再输出Header:header('Content-Type: application/zip')和header('Content-Disposition: attachment; filename="selected_files.zip"');最后调用$zip->close()封包,缺这步ZIP头不完整,浏览器提示“已损坏”。
接收ZIP上传并安全解压到指定目录
第一步:验证上传文件是否为真实ZIP——$request->file('zip_file')->isValid()且$request->file('zip_file')->getClientOriginalExtension() === 'zip'。
第二步:用$zip = new ZipArchive()打开上传文件临时路径:$zip->open($request->file('zip_file')->getRealPath()),返回值非ZIPARCHIVE::ER_OK则中止。
第三步:创建目标解压目录$destDir = storage_path('app/unzipped/'),执行File::ensureDirectoryExists($destDir),确保父目录存在且可写。
第四步:遍历压缩包内所有条目:for ($i = 0; $i numFiles; $i++),获取条目名$zip->getNameIndex($i),过滤掉以/结尾的目录项和含..的路径(防路径穿越),再用iconv('GBK', 'UTF-8', $entryName)还原中文名。
第五步:拼接安全路径$safePath = $destDir . basename($entryName),调用$zip->extractTo($destDir, [$entryName])单条解压;【严禁传整个$zip->statIndex($i)['name']直接解压,未过滤的路径可能写入系统任意位置】。
第六步:关闭ZIP资源$zip->close(),返回成功响应,不删除临时上传文件(PHP会自动清理)。


















