直接用$_FILES失败因PHP默认上传限制小且分片上传需状态维护;须校验chunkIndex与totalChunks、用Redis记录已收分片、以'ab'模式追加写入、校验分片哈希并确认齐全后合并。

为什么直接用 $_FILES 会失败
浏览器上传大音频文件时,PHP 默认的 post_max_size 和 upload_max_filesize 通常设为 2M 或 8M,远低于常见录音文件(如 10 分钟 MP3 ≈ 10–15MB)。更关键的是,分片上传本质是多次小请求,但若后端没做状态维护,每次 $_FILES 都当成独立新文件处理,最终无法拼接——这不是代码写错,而是架构没对齐。
$_POST['chunkIndex'] 和 $_POST['totalChunks'] 必须校验
前端传来的分片序号和总数不能直接信任。攻击者可能伪造 chunkIndex=999 或 totalChunks=1 来跳过校验,导致服务端误判完成状态或覆盖写入。
- 检查
$_POST['chunkIndex']是否为非负整数,且严格小于$_POST['totalChunks'] - 用
filter_var($_POST['chunkIndex'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]])替代强制类型转换 - 记录每个文件 ID 的已接收分片集合(建议用 Redis 的
SET或数据库chunk_received字段),避免仅靠计数器防重传
分片合并必须用 fopen(..., 'ab') 而非 'w'
用 'w' 模式打开目标文件再写入,会导致前一个分片被清空;而 'ab'(追加二进制)确保字节流严格按序拼接。但注意:如果某一分片上传失败或重复,顺序错乱就不可逆。
- 先用
md5_file()校验单个分片临时文件完整性(前端也应计算并传chunkHash) - 合并前检查所有分片是否齐全:
for ($i = 0; $i - 合并后立即用
getid3库解析头部,确认filesize()与预期总大小一致,且能识别出有效音频格式
NGINX 默认会拦截大请求体,client_max_body_size 不是唯一开关
即使 PHP 层面调大了限制,NGINX 仍可能在 413 Request Entity Too Large 阶段就拒绝请求。但只改 client_max_body_size 还不够——分片上传的每个请求虽小,但高频短连接容易触发 client_header_timeout 或 client_body_timeout 超时,尤其在弱网环境下。
立即学习“PHP免费学习笔记(深入)”;
- 在
http、server、location三级都显式设置client_max_body_size 100M(单位必须带 M/G) - 把
client_body_timeout提高到至少60s,避免分片因网络抖动被中断 - 禁用
fastcgi_buffering off(PHP-FPM 场景),否则 NGINX 可能缓存整个分片再转发,失去流式处理能力
真正麻烦的不是合并逻辑,而是如何让每个分片在超时、重试、并发上传下依然可追溯、可验证、不污染临时目录——这些细节漏掉一个,上线后就会出现“有时能传、有时报错、文件打不开”的问题。



















