
Laravel 原生不支持浏览器端直接上传完整文件夹(含嵌套子目录),需结合 webkitdirectory 属性、JavaScript 递归遍历 FileList 及后端路径解析,才能实现文件夹结构的完整还原与存储。
laravel 原生不支持浏览器端直接上传完整文件夹(含嵌套子目录),需结合 `webkitdirectory` 属性、javascript 递归遍历 filelist 及后端路径解析,才能实现文件夹结构的完整还原与存储。
现代浏览器(Chrome、Edge、Opera)支持通过 触发文件夹选择,但其返回的 FileList 并非真实目录树,而是扁平化列表——所有文件(含子目录中的文件)按绝对路径(如 my-folder/sub/1.png)暴露为 File 对象,且 File.webkitRelativePath 属性携带相对路径信息。这是实现结构保留的关键。
✅ 正确前端写法(HTML + JavaScript)
<script> document.getElementById('folderUploadForm').addEventListener('submit', async (e) => { e.preventDefault(); const input = document.getElementById('folder'); const files = Array.from(input.files); const formData = new FormData(); files.forEach(file => { // 关键:使用 webkitRelativePath 构建带层级的键名,如 "my-folder/sub/1.png" const path = file.webkitRelativePath || file.name; formData.append('files[]', file, path); // 第三个参数指定服务端接收的文件名(含路径) }); const res = await fetch('/api/upload-folder', { method: 'POST', body: formData, headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') } }); console.log(await res.json()); }); </script>✅ 后端控制器:解析路径并重建目录结构
<?php namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class FolderUploadController extends Controller
{
public function uploadFolder(Request $request)
{
$request->validate([
'files.*' => 'required|file|max:102400', // 单文件最大100MB
]);
$uploadedPaths = [];
foreach ($request->file('files') as $file) {
// 获取客户端提交的完整相对路径(如 "project/src/index.js")
$originalPath = $file->getClientOriginalName();
// 安全校验:禁止路径遍历(如 ../)
if (Str::contains($originalPath, ['..', "\0"])) {
return response()->json([
'status' => 'error',
'message' => 'Invalid file path detected'
], 400);
}
// 拼接 Laravel Public 存储路径(自动创建子目录)
$disk = Storage::disk('public'); // 确保 config/filesystems.php 中已配置 'public'
$fullPath = 'uploads/folder/' . $originalPath; // 例如:uploads/folder/my-folder/sub/1.png
try {
// 自动创建中间目录,写入文件
$disk->put($fullPath, file_get_contents($file));
$uploadedPaths[] = $fullPath;
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'Failed to save file: ' . $e->getMessage()
], 500);
}
}
return response()->json([
'status' => 'success',
'message' => 'Folder uploaded successfully',
'uploaded_files' => $uploadedPaths,
'public_url' => url('storage/uploads/folder') // 可直接访问的根 URL
]);
}
}
⚠️ 关键注意事项
- 浏览器兼容性:webkitdirectory 仅支持 Chromium 内核浏览器(Chrome ≥ 21, Edge ≥ 79),Firefox 和 Safari 不支持。
- 路径安全性:务必校验 getClientOriginalName() 是否含 .. 或空字节,防止目录穿越攻击。
- Laravel 配置:确保 config/filesystems.php 中 'public' 磁盘已正确配置,并执行 php artisan storage:link 创建软链接。
- 大文件/大量文件:建议添加 max_input_time、post_max_size、upload_max_filesize 等 PHP 配置调优;生产环境推荐使用分片上传或队列异步处理。
- 无原生“文件夹对象”:浏览器不会发送 Directory 实体,所有操作均基于 File + webkitRelativePath 模拟,因此无法获取空目录或元数据(如修改时间)。
通过上述方案,你不仅能上传整个文件夹,还能在 storage/app/public/uploads/folder/ 下1:1 还原原始目录结构,并通过 /storage/uploads/folder/xxx 直接访问任意子文件,真正实现专业级文件夹托管能力。











