
Laravel 原生不支持浏览器端直接上传整个文件夹(含嵌套目录结构),需借助 webkitdirectory 属性配合后端递归解析文件路径,手动重建目录层级后存储。本文详解实现方案、关键限制及安全实践。
laravel 原生不支持浏览器端直接上传整个文件夹(含嵌套目录结构),需借助 `webkitdirectory` 属性配合后端递归解析文件路径,手动重建目录层级后存储。本文详解实现方案、关键限制及安全实践。
在现代 Web 开发中,用户常需批量上传带层级结构的资源(如设计稿文件夹、静态站点源码等)。虽然 可触发浏览器选择整个文件夹,并在 FileList 中返回所有文件(含相对路径),但 Laravel 的 $request->file() 仅能处理单个上传文件对象——它无法自动识别或还原原始文件夹结构。你遇到的 .tmp 文件现象,正是浏览器将每个文件独立上传、丢失路径信息所致。
✅ 正确实现思路:利用 $_FILES 原生数组 + 路径解析
Laravel 的 $request->file() 对 webkitdirectory 上传存在局限,推荐改用 PHP 原生 $_FILES 获取完整文件元数据,并结合 $_POST['file_path'](需前端补充)或文件名中的路径信息还原层级。以下是经过验证的完整方案:
1. 前端表单(增强路径感知)
<script> function handleFolderSelect(files) { // 可选:向隐藏字段注入路径前缀(更可靠) const pathInput = document.createElement('input'); pathInput.type = 'hidden'; pathInput.name = 'base_path'; pathInput.value = files.length ? files[0].webkitRelativePath.split('/')[0] : ''; document.querySelector('form').appendChild(pathInput); } </script>2. 后端控制器(递归创建目录并保存)
<?php namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class FolderUploadController extends Controller
{
public function uploadFolder(Request $request)
{
$baseDir = 'public/uploads'; // 存储根目录(对应 public/storage/uploads)
// 确保使用 public disk(已配置为链接到 public/storage)
if (!$request->hasFile('folder')) {
return response()->json(['error' => '未检测到文件夹'], 400);
}
$files = $request->file('folder');
$basePath = $request->input('base_path', '');
foreach ($files as $file) {
$relativePath = $file->getClientOriginalName(); // 注意:Chrome 返回完整相对路径(如 "assets/img/logo.png")
// 安全过滤:移除路径遍历字符
$safePath = preg_replace('/\.\.\//', '', $relativePath);
$fullPath = $baseDir . '/' . ($basePath ? $basePath . '/' : '') . $safePath;
// 自动创建多级目录
$dir = dirname($fullPath);
if (!is_dir(public_path($dir))) {
mkdir(public_path($dir), 0755, true);
}
// 保存文件(使用原生 move() 避免 Storage facade 的路径限制)
if ($file->move(public_path($fullPath))) {
\Log::info("已保存: {$fullPath}");
} else {
return response()->json(['error' => "保存失败: {$relativePath}"], 500);
}
}
return response()->json([
'message' => '文件夹上传成功',
'count' => count($files),
'url_prefix' => url('storage/uploads')
]);
}
}
3. 路由注册(routes/web.php)
Route::post('/upload/folder', [FolderUploadController::class, 'uploadFolder'])
->name('folder.upload');
4. 关键注意事项
- ? 安全性强制要求:
必须对 $file->getClientOriginalName() 进行严格过滤(如 preg_replace('/[^a-zA-Z0-9._\-\/]/', '', $path)),防止路径遍历(../../../etc/passwd)。 - ? 浏览器兼容性:
webkitdirectory 仅支持 Chrome、Edge、Opera,Firefox 使用 mozdirectory(需额外适配),Safari 不支持。生产环境建议提供降级方案(如 ZIP 上传 + 后端解压)。 - ? Laravel Storage 注意:
Storage::disk('public') 默认写入 storage/app/public/,需确保已执行 php artisan storage:link,且文件实际存于 public/storage/ 下才可被 Web 访问。 - ⚠️ 大文件与超时:
在 php.ini 中调高 max_file_uploads, upload_max_filesize, post_max_size, max_execution_time。
总结
上传文件夹的本质是「批量上传 + 路径重建」。Laravel 不提供开箱即用的文件夹上传支持,但通过结合 webkitdirectory、原生 $_FILES 处理、安全路径解析和递归目录创建,即可稳健实现。始终将路径过滤和权限控制放在首位,避免因便利性牺牲系统安全。











