ThinkPHP 6 文件上传需显式指定磁盘驱动并使用 store() 方法,禁用 move();验证规则改用 $file->getOriginalExtension() 和 $file->getSize();文件名必须用 hashName() 生成,且须配置 filesystem.php 中磁盘的 url 项并单独捕获 UploadException。

ThinkPHP 6 文件上传默认不自动移动到 public 目录
新版 ThinkPHP 6 默认使用 think\File 读取上传文件后,不会自动把文件存到 public 下可访问路径,而是暂存在系统临时目录(如 /tmp)。如果你还按 TP5 的习惯直接用 $file->move() 写死 public/uploads,大概率会失败或出现权限错误。
原因在于 TP6 引入了更严格的上传策略:上传对象本身只代表客户端提交的原始文件句柄,移动动作必须显式指定目标驱动(比如本地磁盘)和可见路径映射。否则 move() 只是复制到临时位置,不解决 Web 可访问问题。
- 务必检查
config/filesystem.php中是否已配置uploads磁盘,且root指向public/uploads - 上传代码应改用
$file->store('uploads', 'uploads')(第二个参数是磁盘名),而非$file->move() - 若仍用
move(),需确保目标路径是绝对路径,且 Web 服务器用户(如 www-data)有写权限
ThinkPHP 6.1+ 上传验证规则变更:fileExt 和 fileSize 不再内置支持
升级到 6.1 后,fileExt、fileSize 这类验证规则被移出核心验证器,直接在 validate 规则里写会报错:Validate rule fileExt not exists。
这不是 bug,是框架把文件校验逻辑下沉到 think\facade\Filesystem 和上传对象自身方法中,避免验证器耦合 I/O 操作。
- 改用
$file->getOriginalExtension()手动判断扩展名,例如:in_array($file->getOriginalExtension(), ['jpg', 'png']) - 文件大小用
$file->getSize()(单位字节),别再依赖fileSize:2048K这种字符串规则 - 如果坚持用验证器,需自定义规则:在
app/validate/UploadValidate.php中添加extend方法,调用$file->isValid()+ 手动校验
上传路径拼接时,DS 和 str_replace 处理原始文件名仍不安全
很多老项目还在用 $filename = str_replace(['..', '/'], '', $file->getOriginalName()) 来防路径穿越,这在 TP6 下依然危险——getOriginalName() 可能含 Unicode 零宽字符、多字节点号、Windows 驱动器标识(如 C:\),str_replace 根本过滤不干净。
TP6 自带的 $file->hashName() 是唯一推荐方式,它生成的是不含路径、无扩展名冲突、不可预测的随机文件名,配合磁盘配置里的 url 选项才能真正隔离风险。
- 禁止拼接用户传来的原始文件名到路径中,哪怕加了
DS或basename() - 启用
hashName()后,用Filesystem::disk('uploads')->url($savedPath)获取可访问 URL,不要自己拼https://.../uploads/ - 如果业务强依赖原始文件名(如下载时保留),必须用
mb_basename()+pathinfo(..., PATHINFO_EXTENSION)提取,并对名称做 Unicode 正规化(Normalizer::normalize())
上传回调与异常处理中,think\Exception 被细化,UploadException 需手动捕获
TP6 把上传失败拆成了多个具体异常类,比如 think\exception\UploadException、think\exception\FileException,但很多人还在 try-catch \Exception 或漏掉 UploadException,导致上传失败时返回 500 而不是 400,前端无法区分是网络中断还是类型不符。
尤其在 API 场景下,没捕获 UploadException 就等于放弃错误码和提示语的控制权。
- 必须显式 catch
think\exception\UploadException,它包含getErrorCode()(如UPLOAD_INVALID_FILE)和getMessage() -
UploadException不继承HttpException,所以不能靠全局HttpExceptionHandler统一转 JSON;需在控制器里单独处理 - 上传前建议先调用
$file->isValid()做轻量预检,避免走到store()才抛异常
事情说清了就结束。最常被跳过的其实是磁盘配置里的 url 项和 UploadException 的独立捕获——这两处不补上,其他所有安全措施都可能被绕过。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











