PHP原生不支持文件夹上传,需前端用webkitdirectory触发多文件选择并手动构造FormData,后端通过$_FILES二维数组逐个处理,同时严格校验路径防止遍历攻击。
浏览器的 默认不提供文件夹选择能力,HTML5 虽引入了 webkitdirectory 和 directory 属性,但仅限 Chromium 系(Chrome、Edge)和 Firefox 部分版本,Safari 完全不支持。PHP 本身没有任何机制能“接收一个文件夹”,它只处理 HTTP POST 中的 $_FILES 数组——每个条目对应一个独立文件上传项。
webkitdirectory 触发多文件递归读取要让用户选中文件夹并上传其全部内容(含子目录),前端必须:
change 事件,遍历 event.target.files,注意:此时 FileList 是扁平化的,**不保留目录结构**;若需还原路径,必须用 file.webkitRelativePath(仅 Chromium 支持)File 构造 FormData,手动添加路径前缀(如 formData.append('files[]', file, file.webkitRelativePath || file.name))$_FILES['files']['name'] 和 $_FILES['files']['tmp_name'] 数组逐个处理$_FILES 多文件上传时的数组结构容易误读当用 multiple 上传 3 个文件时,$_FILES['files'] 不是普通数组,而是按字段维度组织的二维数组:
Array
(
[name] => Array
(
[0] => a.txt
[1] => sub/b.txt
[2] => c.jpg
)
[tmp_name] => Array
(
[0] => /tmp/phpabc123
[1] => /tmp/phpdef456
[2] => /tmp/phpghi789
)
)
正确遍历方式是:
for ($i = 0; $i < count($_FILES['files']['name']); $i++) {
$originalName = $_FILES['files']['name'][$i];
$tmpPath = $_FILES['files']['tmp_name'][$i];
// 注意:$originalName 可能含路径(如 'sub/b.txt'),需用 basename() 提取文件名
// 若需还原目录结构,应先解析 $originalName 得到相对路径,再拼到目标目录
}
直接用 move_uploaded_file($tmpPath, '/var/www/uploads/' . basename($originalName)) 会丢失层级,所有文件都挤在根目录。更危险的是,若用户伪造 originalName 为 ../../etc/passwd,可能触发路径遍历。安全做法是:
pathinfo($originalName, PATHINFO_DIRNAME) 解析相对路径,但**必须校验该路径不含 ..**str_replace(['\\', '..'], '', $relativeDir) 或正则清除非法字符(更推荐 realpath() +
/var/www/uploads/20250520_abc123/sub/b.txt
.htaccess 或 Nginx location ~ \.php$ { deny all; })真正难的不是上传动作本身,而是路径解析的健壮性、跨浏览器兼容性补丁、以及大文件夹下成百上千文件的并发写入稳定性——这些细节没压测过,上线就容易出问题。
来电咨询