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教程 · 输入校验 · UTF-8 PHP preg_match PREG_OFFSET_CAPTURE 字符串偏移量
- PHP preg_match PREG_OFFSET_CAPTURE 的偏移量怎么用:UTF-8 字节位置与字符串截取校验
- 111浏览 收藏
-
- 文章 · php教程 | 2小时前 | 数据安全 · php教程 · 日志处理 · php JSON json_encode 日志脱敏 JSON_THROW_ON_ERROR
- PHP JSON 日志脱敏怎么避免破坏结构:嵌套字段、异常值与审计保留
- 462浏览 收藏
-
- 文章 · php教程 | 4小时前 |
- PHP preg_match 为什么要区分 0 和 false:匹配失败、正则错误与日志定位
- 489浏览 收藏
-
- 文章 · php教程 | 5小时前 |
- PHP 表单校验怎么把错误回显到对应字段:session flash 与 aria-describedby 的实现
- 455浏览 收藏
-
- 文章 · php教程 | 6小时前 | WEB开发 · 性能优化 · PHP · php session 并发请求 session_write_close
- PHP session 锁导致接口排队:同一会话并发请求的定位与处理
- 355浏览 收藏
-
- 文章 · php教程 | 7小时前 | JSON · 数据校验 · 异常处理 · PHP · 接口开发 · php json_decode JSON_THROW_ON_ERROR JSON_ERROR_DEPTH JSON_ERROR_SYNTAX
- PHP json_decode 为什么返回 null:JSON_THROW_ON_ERROR、深度限制与输入诊断
- 484浏览 收藏
-
- 文章 · php教程 | 9小时前 |
- PHP 8.5 #[\Deprecated] 怎么标记旧接口:迁移提示与反射验收
- 446浏览 收藏
-
- 文章 · php教程 | 9小时前 | 安全 · PHP · 输入校验 · hash_equals · API签名 · PHP hash_equals PHP 签名校验 时序攻击 令牌比较
- PHP hash_equals 怎么避免令牌比较泄露:长度判断、类型边界与签名校验
- 322浏览 收藏
-
- 文章 · php教程 | 9小时前 | JSON · 数组 · PHP · PHP array_filter PHP 数组键名 JSON 数组
- PHP array_filter 怎么保留键名:空值判断、回调签名与 JSON 数组陷阱
- 102浏览 收藏
-
- 文章 · php教程 | 10小时前 | 异常处理 · PHP · PHP 8 · PHP 8 PHP match UnhandledMatchError
- PHP match 表达式怎么处理默认分支:UnhandledMatchError 与输入兜底
- 107浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 5228次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4737次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4686次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4943次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4900次使用
-
- 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浏览
-
- PHPEnv安装PhpMyAdmin教程详解
- 2026-05-07 501浏览

