分片上传+后端安全合并是php处理大文件的唯一可行方案,因单纯调高upload_max_filesize会受nginx client_max_body_size、超时、内存及并发写入限制,且无法断点续传或校验。

PHP 处理大文件上传,不能靠调高 upload_max_filesize 硬扛——它只是第一道门,后面还卡着 Nginx、超时、内存、并发写入和校验逻辑。真正能落地的方案,是分片上传 + 服务端安全合并。
为什么改了 php.ini 还传不过 100MB 文件
常见现象:改完 upload_max_filesize = 200M 和 post_max_size = 200M,重启 PHP-FPM,但上传仍失败或卡在 60 秒后空白响应。
- Nginx 拦在最外层:
client_max_body_size默认是 1M,必须同步设为 ≥200M,否则请求根本进不了 PHP - 连接空闲超时不是 PHP 控的:
client_body_timeout和proxy_read_timeout(若走反代)必须设到 600+ 秒,否则 Nginx 主动断连 -
max_execution_time只管脚本执行,不管 HTTP 传输耗时;上传过程中脚本可能早于文件收完就被 kill,得配合set_time_limit(0)临时关掉 - 前端没切片,用原生
<input type="file">提交,浏览器会把整个文件塞进单次 POST —— 这种方式在 50MB+ 就极不稳定,且无法断点续传
PHP 接收分片时怎么避免 move_uploaded_file() 失败
失败主因不是“文件太大”,而是临时文件生命周期失控或路径权限错位。
- 别依赖系统默认
upload_tmp_dir(通常是/tmp),那里文件可能被 cron 清理或过期;显式在 php.ini 中设成可控路径,如upload_tmp_dir = /var/www/uploads/tmp,并确保 Web 用户(如 www-data)有读写权限 -
move_uploaded_file()要求源路径有效且未被销毁,而分片上传跨秒/跨请求,临时文件极易失效;更稳妥的是用file_get_contents($_FILES['file']['tmp_name'])读取后直接写入自定义命名的分片文件(如abc123_0.bin) - 接收时务必检查
$_FILES['file']['error'] === UPLOAD_ERR_OK,非此值说明上传中途被 Nginx 截断、客户端中断或磁盘满,不能继续后续逻辑 - 分片文件名必须含唯一标识(如
X-Upload-ID)和序号(如X-Chunk-Index),避免不同上传任务写混
合并分片前必须做的三重校验
收到全部分片 ≠ 文件完整。跳过校验直接拼接,线上容易出现“上传成功但解压报错”“视频播放花屏”等隐蔽故障。
- 数量校验:用
scandir()扫描该upload_id下所有*.bin文件,确认数量等于前端传的X-Total-Chunks - 内容校验:每个分片接收后立刻计算
md5_file($_FILES['file']['tmp_name']),与前端传的X-Chunk-Hash比对;不一致则拒绝,防止网络损坏或中间篡改 - 顺序与写入安全:合并时用
fopen($final, 'w')创建新文件,按序号逐个fread()+fwrite(),而非file_put_contents(..., FILE_APPEND)—— 后者在多进程并发触发合并时可能乱序或覆盖 - 合并目标文件需加
flock()锁,防止同一upload_id的多个请求同时进入合并逻辑
断点续传的关键不在 PHP,而在元数据持久化
PHP 本身不保存状态,断点能力靠外部存储记录进度。
- 每次分片上传成功后,必须将
upload_id、chunk_index、状态(success)、时间戳写入 MySQL 或 Redis;前端轮询时靠这个判断哪些片已传、哪些要重传 - 不要只存“已传片数”,要存具体索引列表(如
[0,1,2,4,5]),否则无法处理乱序到达或某片多次重试 - 合并完成后,及时删掉该
upload_id对应的所有元数据和临时分片,避免磁盘被占满 - 注意清理策略:对 24 小时未完成的上传任务,后台脚本应自动清除其临时文件和数据库记录,否则会堆积大量孤儿文件
最易被忽略的一点:分片合并后的最终文件,必须用 finfo_open(FILEINFO_MIME_TYPE) 再校验一次 MIME 类型,不能默认“拼完就可信”。有些攻击会利用分片边界构造畸形 payload,绕过单片校验。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











