当前位置:首页 > 文章列表 > 文章 > php教程 > PHP 表单上传大文件怎么做断点续传:分片校验、临时文件与合并

PHP 表单上传大文件怎么做断点续传:分片校验、临时文件与合并

来源:17golang原创 2026-08-25 01:13:05 0浏览 收藏

大文件上传最麻烦的地方不是把一个 $_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,服务端负责验证每片和最终结果。只要先把协议和失败边界定下来,后续替换前端组件或存储介质,也不会重新陷入“断了就从头传”的问题。

PHP 大文件断点续传中分片编号、校验和服务端临时目录的对应关系

PHP 断点续传合并前检查缺片、合并后整文件校验与失败清理流程

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go 1.27 uuid 包怎么选:从自定义实现迁移时要核对什么Go 1.27 uuid 包怎么选:从自定义实现迁移时要核对什么
上一篇
Go 1.27 uuid 包怎么选:从自定义实现迁移时要核对什么
Java StringJoiner 怎么拼带前后缀的列表:空集合、嵌套分隔和流式收集
下一篇
Java StringJoiner 怎么拼带前后缀的列表:空集合、嵌套分隔和流式收集
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    393次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    472次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    478次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    421次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    248次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码