Plupload 2.3.6分块视频上传完成后无法存入目标目录
问题现象
前端可正常展示文件或文件分块的上传进度,进度达到100%后提示上传完成,但上传结束后,无论配置的目标目录还是项目其他目录下,都找不到已上传的文件,Plupload 2.3.6版本、3.x版本均存在该问题。
核心问题原因
Plupload的上传进度是根据客户端已发送的字节数计算的,只要前端把文件全部分片发送完成就会显示100%进度,不会校验后端是否真正处理、保存成功。你的后端PHP代码存在多处语法、逻辑错误,且所有错误都被@错误抑制符掩盖,导致脚本运行失败但前端无感知。
具体错误点如下:
- 目标目录路径配置错误
代码中配置的$targetDir = '/uploads';是操作系统根目录下的绝对路径,并非网站项目内的uploads目录。绝大多数Web服务运行用户(如www-data、nginx)没有系统根目录的写入权限,文件根本无法写入;就算写入成功,在项目目录下也无法找到对应文件。 - 存在两处PHP语法致命错误
- 获取文件名逻辑中,
$fileName = $_REQUEST['name']行尾缺少分号,会直接触发PHP语法解析错误,脚本中断运行 - 清理临时文件逻辑中,将PHP内置函数
filemtime()错写为不存在的filetime(),触发致命错误导致脚本中断
- 获取文件名逻辑中,
- 上传合法性校验参数错误
校验上传文件合法性的逻辑中,is_uploaded_file()要求传入服务器端临时文件路径$_FILES["file"]["tmp_name"],但代码中传入的是客户端上传的原始文件名$_FILES["file"]["name"],该校验会永远判定为非法上传,直接返回错误。 - 目录创建逻辑缺陷
代码用@mkdir($targetDir);创建目录时,未指定目录权限、未开启递归创建参数,就算路径配置正确,默认创建的目录也可能因权限不足导致Web用户无法写入文件。
修复方案
- 修正目标目录路径,使用PHP魔术常量
__DIR__获取当前脚本所在目录,拼接成项目内的绝对路径,比如$targetDir = __DIR__ . '/uploads';(确保upload.php文件和uploads目录在同级) - 补上缺失的分号,将错误的
filetime()修正为filemtime() - 修正
is_uploaded_file()的传入参数为$_FILES["file"]["tmp_name"] - 修改目录创建逻辑,指定目录权限为0755,开启递归创建,去掉错误抑制符,方便排查问题:
if (!file_exists($targetDir)) { mkdir($targetDir, 0755, true); }
- 排查阶段暂时去掉代码中所有的
@错误抑制符,打开PHP错误提示,方便定位运行时问题。
修正后的PHP核心代码参考:
$targetDir = __DIR__ . '/uploads'; $cleanupTargetDir = true; $maxFileAge = 5 * 3600; // 创建目标目录 if (!file_exists($targetDir)) { mkdir($targetDir, 0755, true); } // 获取文件名 if (isset($_REQUEST['name'])) { $fileName = $_REQUEST['name']; } elseif (!empty($_FILES)) { $fileName = $_FILES["file"]["name"]; } else { $fileName = uniqid("file_"); } $filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName; $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0; $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0; // 清理旧临时文件 if ($cleanupTargetDir) { if (!is_dir($targetDir) || !$dir = opendir($targetDir)) { die('{"jsonrpc" : "2.0", "error" : {"code" : 100, "message" : "Failed to open temporary directory."}, "id" : "id"}'); } while (($file = readdir($dir)) !== false) { $tmpfilePath = $targetDir . DIRECTORY_SEPARATOR . $file; if ($tmpfilePath == "{$filePath}.part") { continue; } // 修正函数名 if (preg_match('/\.part$/', $file) && (filemtime($tmpfilePath) < time() - $maxFileAge)) { unlink($tmpfilePath); } } closedir($dir); } // 打开临时输出文件 if (!$out = fopen("{$filePath}.part", $chunks ? "ab" : "wb")) { die('{"jsonrpc" : "2.0", "error" : {"code" : 102, "message" : "Failed to open output stream."}, "id" : "id"}'); } if (!empty($_FILES)) { // 修正is_uploaded_file参数 if ($_FILES["file"]["error"] || !is_uploaded_file($_FILES["file"]["tmp_name"])) { die('{"jsonrpc" : "2.0", "error" : {"code" : 103, "message" : "Failed to move uploaded file."}, "id" : "id"}'); } if (!$in = fopen($_FILES["file"]["tmp_name"], "rb")) { die('{"jsonrpc" : "2.0", "error" : {"code" : 101, "message" : "Failed to open input stream."}, "id" : "id"}'); } } else { if (!$in = fopen("php://input", "rb")) { die('{"jsonrpc" : "2.0", "error" : {"code" : 101, "message" : "Failed to open input stream."}, "id" : "id"}'); } } while ($buff = fread($in, 4096)) { fwrite($out, $buff); } fclose($out); fclose($in); // 所有分片上传完成后重命名临时文件 if (!$chunks || $chunk == $chunks - 1) { rename("{$filePath}.part", $filePath); die('{"jsonrpc" : "2.0", "result" : {"status" : 200, "message" : "The file has been uploaded, successfully."}}'); }
验证步骤
- 手动在项目对应路径创建uploads目录,设置目录权限为0755,确保Web服务运行用户有读写权限
- 替换修正后的代码重新上传,上传完成后到
__DIR__ . '/uploads'对应的路径下即可找到上传的文件。
内容的提问来源于stack exchange,提问作者Yemi A.
相关产品推荐
相关产品推荐

