ThinkPHP文件上传必须使用$request->file('name')获取实例,严禁new File或直接操作$_FILES;move()前必须validate()校验,否则安全机制失效;路径为相对public目录的子路径,推荐用Filesystem::disk('public')->putFile()替代原生move()。

ThinkPHP 文件上传不是调 new File(),入口必须是 $request->file('name');不校验就 move(),大概率静默失败或被绕过安全限制。
上传入口必须用 request()->file(),别 new File
常见错误:手写 new \think\File() 或直接操作 $_FILES 数组,结果 isValid() 永远 false、getRealPath() 为空、move() 报错找不到临时文件。
-
$file = request()->file('avatar')是唯一合法起点,返回的是已封装好上传元信息的think\File实例 - 该实例本身不负责接收文件,只描述“这个上传文件长什么样”,真正移动靠
move() - 如果表单没提交或字段名不对,
$file是null,不是空对象——记得先判空再调方法
move() 前必须加 validate() 校验,否则白名单形同虚设
TP 的 validate() 不是可选装饰,它是 move 的前置守门员。不加的话,超大 ZIP、PHP 脚本、伪造 MIME 的图片都可能直接落地,甚至触发解压攻击或代码执行。
- 规则必须传数组,如
['size' => 2097152, 'ext' => 'jpg,png,gif', 'type' => 'image/jpeg,image/png'] -
size单位是字节,不能写2 * 1024 * 1024,TP 不解析表达式,只认整数 -
ext比type更可靠,但仅靠后缀也不够,建议搭配Filesystem::disk('public')->validate(...)->putFile()自动做内容检测 - 校验失败时抛
ValidateException,不是返回 false,得用 try-catch 捕获
保存路径和 URL 构建最容易踩坑
move() 的第一个参数是相对路径,不是 URL;它只写磁盘,不生成可访问链接。很多人卡在这一步:文件明明存进去了,前端却 404。
立即学习“PHP免费学习笔记(深入)”;
-
$file->move('uploads')中的'uploads'是相对于ROOT_PATH . 'public/'的子目录,不是绝对路径,别加前导/ - 目标目录(如
public/uploads)必须手动创建且 PHP 进程有写权限(chmod -R 775或属组正确) -
move()不自动创建多级子目录,若指定'user/avatar',得提前mkdir -p public/uploads/user/avatar - 成功后用
$info->getSaveName()拼 URL,比如"/uploads/" . $info->getSaveName(),别用getPathname()(那是服务端绝对路径)
推荐用 Filesystem::disk('public')->putFile() 替代原生 move()
原生 move() 对路径、权限、子目录太敏感,而 Filesystem 封装更稳,自动处理重命名、目录创建、基础校验,还兼容本地/云存储切换。
- 写法简洁:
$savename = \think\facade\Filesystem::disk('public')->putFile('avatars', $file) - 自动在
public/storage/avatars下生成唯一文件名,无需手动拼时间戳+随机数 - 支持链式调用加校验:
->validate(['size' => 2048000, 'ext' => 'jpg,png']) - 返回的是相对路径字符串(如
avatars/xxx.jpg),配合 public 目录软链或路由即可直接访问
最常被忽略的一点:Nginx 或 Apache 的 client_max_body_size / LimitRequestBody 必须 ≥ php.ini 的 upload_max_filesize 和 post_max_size,否则请求根本到不了 TP 层,$file 就是 null,所有 validate 和 move 都无从谈起。



















