不能直接用move_uploaded_file处理大文件,因为PHP和Web服务器的默认上传限制会在脚本执行前拦截大文件,导致$_FILES为空或报413错误;分片上传需前端切片、后端逐片调用move_uploaded_file暂存,再按序合并。

为什么不能直接用 move_uploaded_file 处理大文件
因为 PHP 默认的上传限制(upload_max_filesize、post_max_size、内存限制)会让大文件在到达 PHP 脚本前就被 Web 服务器(如 Nginx/Apache)或 PHP 自身拦截,move_uploaded_file 根本没机会执行。你看到的常是空 $_FILES、UPLOAD_ERR_NO_FILE 或直接 413 Request Entity Too Large 错误。
分片上传的本质是「前端把一个大文件切小块、逐个 POST、后端拼接」,move_uploaded_file 只负责单次接收后的安全落地,它不处理分片逻辑,也不合并文件——那是你得自己写的部分。
- Web 服务器需放宽请求体限制(Nginx:调整
client_max_body_size;Apache:LimitRequestBody) - PHP 配置中
upload_max_filesize和post_max_size建议设为 2M–8M(够单片即可),而非几十 MB -
max_execution_time和max_input_time也要适当调高,避免单片上传超时中断
分片上传后端接收的关键三步(含 move_uploaded_file 调用时机)
每一片上传请求应携带唯一标识(如 file_id)、分片序号(chunk_index)、总片数(total_chunks),后端据此决定是暂存还是合并。
move_uploaded_file 必须用在「单片临时文件已存在且校验通过后」,不能跳过临时文件直接操作原始流——PHP 不允许。
立即学习“PHP免费学习笔记(深入)”;
- 检查
$_FILES['file']['error'] === UPLOAD_ERR_OK,否则跳过move_uploaded_file - 生成安全的临时路径,例如:
/tmp/uploads/{$file_id}/chunk_{$chunk_index},确保目录可写且隔离 - 调用
move_uploaded_file($_FILES['file']['tmp_name'], $target_path)落地当前分片 - 记录该分片状态(如写入 Redis 或数据库),用于后续合并判断和断点续传
如何安全合并所有分片(不用 move_uploaded_file,但依赖它前期结果)
合并不是靠 move_uploaded_file,而是用 fopen/fwrite 或 file_put_contents(..., FILE_APPEND) 按序读取各分片并写入目标文件。关键在于顺序和完整性校验。
别在合并时再用 move_uploaded_file —— 它只接受 $_FILES 提供的临时文件路径,而分片已移出临时目录,不再是“上传临时文件”了。
- 先确认所有分片已存在且大小匹配(比对
$_POST['chunk_size']或计算 MD5) - 按
chunk_index升序打开每个分片(fopen($chunk_path, 'rb')),用stream_copy_to_stream追加到目标文件 - 合并完成后,用
unlink批量清理分片目录,防止磁盘占满 - 若需原子性,先写到
.part后缀文件,合并完成再rename成正式名
容易被忽略的坑:分片重复、并发冲突与清理失效
前端可能因网络重试重复提交同一片,或多个标签页同时上传同名文件,导致分片覆盖或错乱。这些不会报错,但最终文件损坏。
move_uploaded_file 本身不防重,你要靠业务层控制。
- 用
file_id+chunk_index做唯一键,入库前查重,重复则直接返回成功(幂等) - 写分片前加文件锁(
flock)或 Redis 分布式锁,避免多请求同时写同一分片 - 设置分片目录自动过期机制(如用
filemtime清理 2 小时未完成的上传) - 不要依赖
$_FILES['file']['name']构建路径——它不可信,必须用服务端生成的file_id
分片上传真正难的不是 move_uploaded_file 怎么调,而是怎么让几十个 HTTP 请求协作无误地还原出原始字节流。函数只是工具,状态管理、并发控制、失败回滚才是核心。



















