move()前必须检查目标目录是否存在且可写,因php 7.4的move_uploaded_file()或thinkphp的move()不自动创建目录、不报错,仅静默返回false;需用is_dir()和is_writable()双重判断,并实测写入验证。

move()前必须检查目标目录是否存在且可写
PHP 7.4 的 move_uploaded_file() 或 ThinkPHP 的 move() 不会自动创建父级目录,也不会提示“目录不存在”——它直接返回 false,且不抛异常。你看到上传失败,大概率是 public/uploads 目录压根没建,或权限不对。
实操建议:
- 用
is_dir()+is_writable()双重判断:if (!is_dir($targetDir) || !is_writable($targetDir)) { throw new Exception("目标目录不可写:{$targetDir}"); } - 别依赖
mkdir($targetDir, 0755, true)一招鲜:Windows 下0755实际等效于0777,Linux 下若父目录无执行权限(x),true参数也无法递归创建 - ThinkPHP 中
$file->move()的目标路径默认基于ROOT_PATH . 'public' . DS,确保public/uploads目录真实存在且属 Web 进程用户(如 www-data、apache、nginx)所有
chmod 0755 是上传目录的底线权限
上传目录(如 public/uploads)必须有执行位(x),否则 Web 服务器无法进入该目录创建文件——这和“能不能读文件”无关,是 POSIX 文件系统对目录的基本要求。设成 0644 看似安全,实则导致 move() 静默失败。
实操建议:
- Linux 下执行:
chmod 0755 public/uploads
(所有者可读写执行,组和其他人可读可执行) - 别用
0777:虽能跑通,但等于向任意用户开放写入,攻击者可上传 Webshell - 确认 Web 进程用户拥有该目录:
chown -R www-data:www-data public/uploads(Debian/Ubuntu)或chown -R apache:apache public/uploads(CentOS) - Windows 不校验
x权限,但仍需确保 IIS/Apache 用户对该目录有“修改”权限,而非仅“读取”
路径穿越和非法字符必须在 move() 前过滤
$file->move($savePath) 中的 $savePath 若含 ../、..、空字节或控制字符,TP 会拦截并返回错误,但不会抛异常——getError() 才能拿到 "非法路径" 提示,而你若没主动调用,就以为“没报错所以成功了”。
实操建议:
- 禁止用户传入原始文件名拼接路径:不要写
$file->move('uploads/' . $_POST['filename']) - 用
pathinfo($file->getOriginalName(), PATHINFO_FILENAME)提取合法文件名,再加时间戳+随机串生成$savename - 显式过滤路径:
$savename = str_replace(['..', '\', "

