游客发表

thinkphp5实现多文件等候上传

发帖时间:2026-10-17 04:44:43

thinkphp5实现多文件等候上传

多文件上传最烦人的实现不是代码量,而是多文用户点完提交按钮之后那个干瞪眼的等待过程。thinkphp5默认的候上upload类一次只能处理一个文件,你要是实现直接循环调用,前端得等所有文件传完才拿到响应——这体验跟看进度条卡在99%没区别。多文

我去年做个内部培训系统,候上讲师一次性拖二十个PDF进来,实现服务器带宽又只有5M。多文最初版本用同步循环,候上用户看着浏览器转圈,实现转完一圈又一圈,多文最后超时。候上后来改成队列逐个上传,实现配合前端分批请求,多文问题直接解决。候上核心思路就一句话:把"一次全传"拆成"一次传一个,传完再传下一个"。

服务端:别用move_uploaded_file硬扛

thinkphp5的Request对象拿到的是$_FILES数组,多文件时结构是二维的。你直接foreach然后调move_uploaded_file,遇到大文件或慢网络会卡死PHP进程。正确姿势是写个独立方法,每次只处理一个文件,返回JSON状态给前端,前端收到成功信号后再发起下一个请求。

public function uploadOne() {  $file = request()->file('file'); if (!$file) return json(['code' => 0, 'msg' => '无文件']); $info = $file->validate(['size' => 10485760, 'ext' => 'pdf,doc,zip']) ->move(ROOT_PATH . 'public/uploads'); if ($info) return json(['code' => 1, 'url' => '/uploads/' . $info->getSaveName()]); return json(['code' => 0, 'msg' => $file->getError()]); }

这段代码跟普通单文件上传没区别,但关键在于——前端每次只传一个文件。服务端始终保持轻载,每个请求处理完立即释放内存,二十个文件就是二十个独立请求,互不干扰。

前端:循环发请求,别用Promise.all

很多人写多文件上传喜欢用FormData把文件全塞进去,然后一次post。这招在小文件还行,碰上大文件直接GG。我用的方案是:

async function queueUpload(files) {  for (let i = 0; i < files.length; i++) {  const fd = new FormData(); fd.append('file', files[i]); const res = await fetch('/upload/uploadOne', {  method: 'POST', body: fd }); const data = await res.json(); if (data.code === 1) {  // 更新进度条,显示已传文件名 updateProgress(i + 1, files.length); } else {  // 记录失败,继续传下一个 failedList.push(files[i].name); } } }

注意循环里的await,它让请求一个接一个排队,而不是同时发五个把服务器打爆。实际测试下来,十个10MB的PDF用这个方式,总耗时比同步循环快40%——因为PHP进程不用等所有文件都move完才返回,每个文件传完就释放资源。

中断恢复:用户关页面怎么办

真实场景里用户传一半去开会了,或者手机锁屏断网。thinkphp5这边文件已经move到临时目录,但前端没收到成功响应。我的做法是加个临时标记:

// 上传前生成唯一token $token = md5(uniqid() . time()); // move成功后写入文件记录表 Db::name('upload_queue')->insert([ 'token' => $token, 'path' => $info->getSaveName(), 'status' => 1, 'create_time' => time() ]);

前端重新打开页面时,先请求一个检查接口,把status=1但未确认的文件列出来,用户点"继续上传"就直接跳过这些,只传剩下的。这个功能花了我半小时,但用户满意度直接拉满——没人愿意重传二十个文件。

进度条要真实,别骗人

我最烦那种假进度条,跳到90%然后卡十分钟。thinkphp5的ajax上传没有原生的上传进度事件,得靠前端自己模拟。我的做法是:每个文件上传前记录时间戳,上传完成后计算耗时,用移动平均法预测剩余时间。

let avgTime = 0; let count = 0; // 每次上传完成后 const cost = Date.now() - startTime; avgTime = (avgTime * count + cost) / (count + 1); count++; const remain = (files.length - i - 1) * avgTime;

这玩意儿不精确,但比假进度条强多了。用户看到"预计还需2分钟"心里有底,就算慢也不会狂按F5。

坑点备忘

thinkphp5的validate规则里,ext参数如果包含大写后缀,得先转小写。另外move方法第二个参数如果留空,默认用日期分目录,但并发高时可能生成重复文件名。建议用uniqid()拼上原始文件名。

最后说一句,多文件上传的核心不是代码技巧,是资源调度。你让服务器同时处理十个大文件,不如让它一个一个来,每个都处理得干干净净。等保测评那些要求高并发的场景另说,但企业内部系统,排队上传完全够用。

    热门排行

    友情链接