PHP 表单上传大文件怎么做断点续传:分片校验、临时文件与合并
大文件上传最麻烦的地方不是把一个 $_FILES 写进磁盘,而是用户传到 80% 时网络断了,下一次打开页面还得从头来。更稳妥的做法是把文件拆成固定大小的分片,每片单独校验并落到任务目录,服务端只在所有分片齐全且顺序正确时合并成最终文件。
断点续传的核心不是“记住上传进度”,而是让客户端能查询已完成分片,并让服务端用文件哈希、分片编号和临时目录把一次上传任务区分开。
- 用 upload_id 隔离每一次上传任务。
- 分片写入临时目录并在改名后返回成功。
- 合并前检查分片总数,完成后核对整文件哈希。
- 失败任务保留状态并按规则清理临时文件。
先把上传协议定清楚
建议把一次上传抽象成三个接口:创建任务、上传分片、查询与合并。客户端第一次提交文件名、文件总大小、总分片数和整文件哈希,服务端返回不可猜测的 upload_id。后续请求只携带这个 ID、分片编号、分片总数和当前二进制内容。
分片编号从 0 开始,服务端文件名统一使用 000000.part 这样的格式,不直接相信客户端传来的原始文件名。原始文件名只作为展示字段,最终保存名应由服务端生成。
POST /upload/init
{
"name": "backup.zip",
"size": 524288000,
"parts": 50,
"sha256": "整文件哈希"
}
PHP 接收分片时先做边界校验
上传接口收到请求后,第一件事是检查任务是否存在、分片编号是否在范围内、分片大小是否超过上限。不要把用户传来的文件名直接拼进路径,也不要用“文件存在就算成功”的判断,因为上次中断可能留下半截文件。
= $total) {
http_response_code(422);
exit('invalid part metadata');
}
$root = __DIR__ . '/runtime/uploads/' . basename($uploadId);
if (!is_dir($root) && !mkdir($root, 0700, true)) {
throw new RuntimeException('cannot create upload directory');
}
$target = $root . '/' . sprintf('%06d.part', $part);
$input = fopen('php://input', 'rb');
$output = fopen($target . '.tmp', 'wb');
stream_copy_to_stream($input, $output);
fclose($input);
fclose($output);
rename($target . '.tmp', $target);
echo json_encode(['ok' => true, 'part' => $part]);
示例中的临时文件和改名动作很重要:合并逻辑永远只读取已经改名完成的 .part 文件,不会碰到正在写入的临时文件。生产环境还应限制任务目录的属主和访问权限,并把上传 ID 与登录用户绑定。
查询接口要返回“已完成哪些片”
客户端重试前先调用查询接口。服务端扫描当前任务文件夹,只认符合六位数字编号的分片,并返回编号数组、总分片数和任务状态。不要只返回一个百分比,百分比无法告诉客户端缺的是哪一片。
$done = [];
foreach (glob($root . '/*.part') as $file) {
if (preg_match('/\/(\d{6})\.part$/', $file, $m)) {
$done[] = (int) $m[1];
}
}
sort($done);
echo json_encode(['parts' => $done, 'status' => 'uploading']);
如果查询结果中已经有当前分片,客户端可以跳过它;如果只有临时文件,没有对应的 .part 文件,就继续上传。这样即使浏览器刷新,也不会把“正在写入”误当成成功。
合并前后分别做一次检查
合并接口不能只检查目录里有多少文件。它应该按 0 到 total - 1 逐片打开,确认每个分片存在,再使用二进制方式追加到一个新的临时目标文件。任何一片缺失都返回可重试的状态,而不是生成一个看似完整的坏文件。
$mergedTmp = $root . '/result.tmp';
$out = fopen($mergedTmp, 'wb');
for ($i = 0; $i
合并完成后再计算最终文件的 SHA-256,并与初始化时记录的值比较。哈希不一致时,删除最终文件并把任务标记为失败,保留缺片信息供客户端重新上传。不要因为 HTTP 200 就认为文件可用。
错误码要让调用方知道下一步
上传接口可以用 422 表示元数据错误,用 404 表示任务不存在,用 409 表示分片缺失或任务状态冲突,用 507 表示磁盘空间不足。响应体再补一个稳定的业务字段,例如 part_missing、checksum_mismatch 或 uploading,前端就能区分“重新传这一片”和“重新创建任务”。
重试也要有边界:同一分片可以重传,合并请求可以带幂等键,但不要在服务端无限重试文件操作。磁盘写满、权限错误和哈希不一致都需要留下日志,方便定位。
清理策略决定了断点续传能否长期运行
临时目录不能永久保留。初始化任务时记录 updated_at,每次成功写片或查询时更新它;定时清理脚本只删除超过保留时间、且不在合并状态的任务。清理前再次读取状态,避免和合并请求同时操作同一目录。
最终文件写入完成后,删除分片和任务元数据,只保留结果文件、整文件哈希和必要的审计信息。用户取消上传时也走同一套清理逻辑,不要把删除路径交给客户端拼接。
上线前用四种中断场景验收
先传完前 30% 后断网,刷新页面,确认查询接口能返回已完成分片;再重复上传一片,确认不会产生重复内容;随后人为删除一片再调用合并,确认返回 409 且没有残留结果;最后修改一个分片内容,确认整文件哈希校验失败。
还要检查 PHP 的 upload_max_filesize、post_max_size、反向代理请求体限制和磁盘配额。分片大小即使低于 PHP 限制,也可能被 Nginx 或网关拦截,验收不能只在本机完成。
相关问题
分片大小应该固定吗?
第一版建议固定在 5MB 到 20MB 之间,并根据移动网络和网关限制调整。固定大小更容易计算编号、查询缺片和重试,最后一片可以小于标准大小。
为什么不直接用原始文件名保存?
原始文件名可能包含路径片段、特殊字符或同名冲突。使用服务端生成的 upload_id 和最终文件名,能把展示信息与存储路径分开。
合并接口需要加锁吗?
需要保证同一任务不会同时合并。可以用数据库状态从 uploading 原子切换到 merging,并让重复请求读取已有结果;不要只依靠目录里是否存在 result.tmp。
把“能续传”落实成可验证的状态机
一套可靠的 PHP 断点续传流程,至少要让创建、分片写入、查询、合并、校验和清理这些动作都有明确状态。客户端负责记录 upload_id,服务端负责验证每片和最终结果。只要先把协议和失败边界定下来,后续替换前端组件或存储介质,也不会重新陷入“断了就从头传”的问题。


Go 1.27 uuid 包怎么选:从自定义实现迁移时要核对什么
- 上一篇
- Go 1.27 uuid 包怎么选:从自定义实现迁移时要核对什么
- 下一篇
- Java StringJoiner 怎么拼带前后缀的列表:空集合、嵌套分隔和流式收集
-
- 文章 · php教程 | 2小时前 |
- PHP match 表达式怎样覆盖枚举分支并保持穷尽
- 377浏览 收藏
-
- 文章 · php教程 | 5小时前 | php教程 · PHP生成器 yield from Generator send getReturn
- PHP 生成器如何双向传值并接收最终返回值
- 208浏览 收藏
-
- 文章 · php教程 | 7小时前 |
- PHP readonly 类继承时有哪些属性限制
- 223浏览 收藏
-
- 文章 · php教程 | 9小时前 |
- PHP ReflectionReference 如何判断数组元素是否共享引用
- 376浏览 收藏
-
- 文章 · php教程 | 11小时前 | php教程 · PHP 8.4 · php ReflectionClass newLazyGhost newLazyProxy lazy object 重量级服务
- PHP lazy object 如何延迟创建重量级服务
- 202浏览 收藏
-
- 文章 · php教程 | 13小时前 | 面向对象 · PHP · PHP 8.4 · PHP非对称属性可见性 private(set) protected(set) PHP 8.4属性 PHP对象封装
- PHP 非对称属性可见性如何限制对象外部写入
- 216浏览 收藏
-
- 文章 · php教程 | 15小时前 | 内存管理 · php教程 · 弱引用 PHP 8 SplObjectStorage PHP WeakMap 对象元数据
- PHP WeakMap 为什么适合保存对象附加元数据
- 227浏览 收藏
-
- 文章 · php教程 | 19小时前 | PHP · 异步编程 · php教程 · 异步回调 事件循环 PHP Fiber Fiber suspend Fiber resume
- PHP Fiber 如何让同步接口适配事件循环
- 272浏览 收藏
-
- 文章 · php教程 | 21小时前 |
- PHP readonly 对象适合配置值还是领域实体
- 178浏览 收藏
-
- 文章 · php教程 | 23小时前 | PHP · php-fpm · PHP OPcache opcache_reset validate_timestamps revalidate_freq opcache_invalidate
- OPcache 更新代码后仍命中旧脚本,该检查哪些配置
- 382浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 393次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 472次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 478次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 421次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 248次使用
-
- PHP JSON_THROW_ON_ERROR 抛错后怎么保留原始字段位置
- 2026-09-09 501浏览
-
- PHP 8.5 array_last() 怎么处理空数组:从 null 结果到兼容旧版本的 Polyfill
- 2026-08-16 501浏览
-
- 宝塔配置Ruby环境:RVM+Nginx反代教程
- 2026-05-29 501浏览
-
- unset函数作用范围详解
- 2026-05-29 501浏览
-
- VS Code配置Xdebug教程:PHP调试技巧全解析
- 2026-05-13 501浏览

