PHP实现断点续传需响应Range请求返回206状态及Content-Range头,分片上传须按upload_id隔离存储并校验哈希,合并应流式写入+原子校验+队列异步处理。

PHP 如何正确处理 HTTP Range 请求实现断点续传
断点续传不是靠前端“记住传到哪”就行,后端必须严格响应 Range 请求头,并返回 206 Partial Content、Content-Range 和正确字节流。否则浏览器或下载工具会直接重置连接,前端所谓“续传”只是假象。
关键点在于:PHP 脚本不能用 readfile() 或 file_get_contents() 全量读取大文件——会爆内存;也不能忽略 If-Range 头导致缓存不一致。
- 先用
$_SERVER['HTTP_RANGE']判断是否为范围请求(格式如bytes=1024-2047) - 用
fopen($file, 'rb')+fseek()定位起始偏移,再用fread()分段输出,避免加载整文件 - 务必设置:
header('HTTP/1.1 206 Partial Content')、header("Content-Range: bytes $start-$end/$size")、header("Accept-Ranges: bytes") - 若
$_SERVER['HTTP_IF_RANGE']存在且与文件ETag或Last-Modified不匹配,应退化为200 OK全量响应
前端分片上传时 PHP 后端如何安全接收并暂存分片
分片上传的核心矛盾是:不能让不同请求写入同一临时文件造成竞争,也不能把所有分片都堆在 /tmp 里等合并——没合并前的碎片文件必须可识别、可清理、不可被误读。
推荐做法是把分片按唯一标识隔离存储,例如使用上传 ID + 分片序号命名:
立即学习“PHP免费学习笔记(深入)”;
- 前端上传时携带
upload_id(如 UUID)、chunk_index、total_chunks、filename - 后端接收后存为
/uploads/{upload_id}/{chunk_index}.part,而非直接追加到一个文件 - 每个分片接收完立刻校验
Content-MD5或sha256_file($_FILES['file']['tmp_name']),失败则返回400 Bad Request - 注意
upload_max_filesize和post_max_size必须 ≥ 单个分片大小,而非总文件大小
PHP 合并分片时怎么避免磁盘 I/O 阻塞和文件损坏
用 file_put_contents($target, file_get_contents($chunk), FILE_APPEND) 是最常见错误——每片都全量读入内存再追加,100MB × 100 片 = 10GB 内存占用,PHP 直接崩溃。
真正可行的是流式合并 + 原子性保障:
- 用
fopen($target, 'wb')创建空目标文件,再对每个分片fopen($chunk, 'rb')→fread()→fwrite()流式写入 - 合并前先检查所有分片是否齐全:
count(glob("/uploads/{$upload_id}/*.part")) === $total_chunks - 合并完成后立即计算最终文件
sha256_file($target),与前端传来的总文件 hash 对比,不一致则删掉目标文件并返回错误 - 不要在 Web 请求中直接执行合并——超时风险高,应写入队列(如 Redis List),由后台 worker 处理
为什么不能直接用 move_uploaded_file() 做分片落地
因为 move_uploaded_file() 只能移动一次,且源路径是 PHP 临时目录下的随机名,无法保证多个请求的分片落到同一逻辑组下。更严重的是:它不提供文件内容校验钩子,也无法控制写入位置权限(比如你想把分片存在 /data/uploads 而非 /tmp)。
正确姿势是手动读取 $_FILES['file']['tmp_name'] 并用 fopen()+fread()+fwrite() 搬运,好处有三:
- 可中途校验、限速、记录日志
- 可统一设置 umask 或
chmod()确保分片文件权限为600(防止未授权访问) - 搬运失败时能精确知道哪一片出错,而不是整个上传流程静默失败
分片合并最易被忽略的点是时间窗口:用户可能上传完所有分片但迟迟不触发合并,或重复触发合并。必须用状态文件(如 /uploads/{upload_id}/.status)或数据库记录来锁住合并动作,否则并发请求会导致文件写乱或覆盖。



















