ThinkPHP上传附件需前后端协同:表单必须设enctype="multipart/form-data",input name与request()->file()参数一致;Ajax需用FormData提交;后端先查$_FILES、PHP及Web服务器上传配置,再验证文件;保存路径应为public下可访问目录,推荐用Filesystem组件自动处理存储与校验。

ThinkPHP 上传附件,核心是让文件从浏览器正确到达服务器并安全落盘。关键不在“怎么写代码”,而在于前后端配合、配置到位、路径可控——漏掉任一环都可能报 $_FILES 为空、request()->file() 返回 null 或保存后无法访问。
表单和前端必须配对正确
这是最常被忽略的起点:
-
form 标签一定要加
enctype="multipart/form-data",缺了这句,浏览器根本不会把文件当二进制数据发,$_FILES必然为空 -
<input type="file" name="avatar">的name值,必须和后端request()->file('avatar')中的字段名完全一致(区分大小写) - 如果用 Ajax 提交,不能直接
JSON.stringify()发送,得用FormData构造,并用fetch或XMLHttpRequest发送;不要手动设置Content-Type,否则浏览器会覆盖为text/plain,导致服务端收不到文件
后端接收与验证要分清阶段
ThinkPHP 的验证(validate())只作用于已成功接收到的文件,它不解决“根本没收到”的问题:
- 先确认
$_FILES是否有内容:dump($_FILES)是最快定位“空文件”原因的方式 - 再检查 PHP 配置:
file_uploads = On、upload_max_filesize和post_max_size(后者必须 ≥ 前者) - Nginx 用户还要核对
client_max_body_size,Apache 则看LimitRequestBody - 验证规则示例:
->validate(['size' => 2048000, 'ext' => 'pdf,doc,docx,xls,xlsx']),只对已上传成功的文件生效
保存路径和访问路径必须分开理解
很多人以为 $file->move('uploads') 后就能用 /uploads/xxx.pdf 直接访问,其实不是:
立即学习“PHP免费学习笔记(深入)”;
-
move()默认存到runtime/upload/(不可通过 URL 访问),如需 Web 可达,目标目录应设为public/uploads/等 Web 根目录下的子目录 - 确保该目录存在且 Web 服务器有写权限(Linux 下常用
chmod -R 755 public/uploads) - 保存成功后,返回的是服务端绝对路径(如
/var/www/tp/public/uploads/20260921/abc.pdf),前端能访问的是相对 URL,例如/uploads/20260921/abc.pdf - 推荐做法:保存时指定目录
$file->move(public_path() . '/uploads'),再拼出可访问路径'/uploads/' . $info->getSaveName()
更现代的做法:用 Filesystem 组件
ThinkPHP 6 推荐使用 Filesystem 替代原始 move(),更简洁也更安全:
- 自动重命名(防覆盖)、自动创建子目录(如按日期)、自动校验 MIME 类型(防伪造)
- 示例:
\think\facade\Filesystem::disk('public')->putFile('documents', $file),文件将存入public/storage/documents/ - 需提前运行
php think storage:link创建软链接,之后可通过/storage/documents/xxx.pdf访问 - 支持链式验证:
->validate(['size' => 5 * 1024 * 1024, 'ext' => 'zip,rar,7z']),超限直接抛ValidateException



















