登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP session.upload_progress 怎么让另一个请求查询文件上传进度

来源:17golang原创

时间:2026-10-13 08:21:22 345浏览 收藏

PHP 的 session.upload_progress 适合解决“上传请求还没有结束,但页面需要显示已上传多少”的问题。上传请求负责提交 multipart 数据,另一个短请求负责读取同一会话里的进度数组;两个请求必须使用同一个 Session Cookie,查询接口不能自己生成一套新会话。

官方资料:https://www.php.net/manual/en/session.upload-progress.php

最小可用方案是:在文件字段之前提交一个由 session.upload_progress.name 指定的隐藏字段,值作为本次上传的标识;轮询接口读取 session.upload_progress.prefix . 标识值 对应的 $_SESSION 项,再根据 bytes_processed、content_length 和 done 计算状态。

先把上传请求和轮询请求拆成两条链路

进度数据不是上传接口返回的实时流,而是 PHP 在接收 POST 文件时维护的会话状态。上传接口可能持续几十秒甚至更久,所以页面不能等它返回后再显示进度,而要用第二个请求周期性读取状态。

这条链路有三个连接点:上传表单提交一个唯一标识;浏览器在两次请求中都带上同一个 Session Cookie;PHP 根据配置前缀和标识值写入同一个会话键。轮询请求只读取这个键,不需要重新上传文件。

上传请求和进度轮询请求通过同一 PHP Session 读取上传状态的结构说明图
图1:PHP 上传进度的两条请求链路与会话状态关系示意图,不是浏览器截图。

在表单中写入唯一的上传标识

PHP 默认使用 PHP_SESSION_UPLOAD_PROGRESS 作为字段名,默认使用 upload_progress_ 作为键名前缀。生产代码不要硬编码这两个默认值,而应通过 ini_get() 读取当前环境配置。

隐藏字段必须放在文件字段之前。这样 PHP 在处理 multipart 请求时才能先看到进度标识,并把后续文件接收过程关联到对应会话项。

标识值应该在一次上传任务内唯一。若同一个会话同时提交两次上传,使用不同标识可以避免两个进度数组互相覆盖;如果需要更强的访问控制,还应把标识和当前用户、业务任务绑定,不能只凭一个可猜字符串返回进度。

实现独立的进度查询接口

查询接口可以接收标识值,按当前 PHP 配置拼出会话键。下面的示例只返回页面真正需要的字段,并把不存在的键解释为“尚未开始、已经清理或会话不一致”,避免直接把整个 $_SESSION 暴露给客户端。

 '缺少上传标识'], JSON_UNESCAPED_UNICODE);
    exit;
}

$key = $progressPrefix . $_SESSION[$progressField];
$progress = $_SESSION[$key] ?? null;

header('Content-Type: application/json; charset=utf-8');
if (!is_array($progress)) {
    // 进度可能尚未写入,或已被 cleanup 清理,客户端稍后再问。
    echo json_encode(['state' => 'unavailable'], JSON_UNESCAPED_UNICODE);
    session_write_close();
    exit;
}

$total = (int)($progress['content_length'] ?? 0);
$processed = (int)($progress['bytes_processed'] ?? 0);
$percent = $total > 0 ? round($processed / $total * 100, 2) : 0;

echo json_encode([
    'state' => !empty($progress['done']) ? 'done' : 'uploading',
    'bytes_processed' => $processed,
    'content_length' => $total,
    'percent' => min(100, $percent),
], JSON_UNESCAPED_UNICODE);

// 读完后尽快释放会话,避免轮询请求彼此排队。
session_write_close();

示例中的 $_SESSION[$progressField] 是上传请求提交的标识值,最终键名是 session.upload_progress.prefix 与这个值的拼接。PHP 官方文档给出的默认结构就是类似 $_SESSION["upload_progress_123"] 的数组。

PHP session.upload_progress 会话键和 bytes_processed 等进度字段的关系说明图
图2:session.upload_progress 会话键与进度字段的关系示意图,不是运行截图。

让上传处理和轮询接口快速释放会话锁

进度轮询是短请求,但 Session 通常需要读取和写回存储。查询接口拿到需要的数据后应立即结束会话写入阶段,不要在持有会话期间做慢查询、远程调用或复杂文件处理。否则多个轮询请求可能排队,看起来就像进度不再更新。

上传完成后再执行业务逻辑时,也可以在确定不再修改会话后调用 session_write_close()。它不会让上传过程本身变成异步任务,但能缩短业务代码持有会话的时间。轮询间隔不宜过短,通常按几百毫秒到数秒设计,并在 done 或明确错误时停止。

还要注意 session.upload_progress.cleanup 默认开启。PHP 读取完 POST 数据后,进度项可能很快被清理,因此“最后一次轮询没读到数组”不一定代表上传失败。真正的完成结果应由上传接口自己的业务响应或任务状态确认,上传进度只负责展示接收过程。

配置与部署边界:为什么一直为零或查询为空

如果结构正确但进度一直是零,先按下面顺序检查。

  • 开关:session.upload_progress.enabled 必须启用;同时确认 session.upload_progress.name 与表单隐藏字段名称一致。
  • 字段顺序:隐藏进度字段放在文件字段之前,且请求确实是 multipart/form-data。
  • 会话:上传和轮询要使用同一域、同一 Cookie、同一会话存储;负载均衡多机部署时,文件会话必须共享或使用稳定会话粘性。
  • 请求缓冲:PHP 官方特别提醒,Web 服务器如果把大请求完整缓冲后才交给 PHP,PHP 只能在文件全部到达后看到上传,实时进度自然失去意义。Nginx 等反向代理的请求缓冲策略需要结合部署方式调整。
  • 会话名称:上传进度在脚本执行前就写入会话,运行时临时修改 session.name 或调用 session_name() 可能让上传进度落到另一套会话上下文。

不要把“查询为空”统一当成 PHP 计算错误。它可能表示上传还没被 PHP 接收、会话 Cookie 不同、进度刚被清理,或者前置代理尚未转发请求。先确认链路和配置,再决定是否需要替换为分片上传或队列任务方案。

规模化场景下的取舍

session.upload_progress 的优点是接入成本低:不需要单独的进度表,PHP 会维护接收字节数,轮询接口也容易实现。代价是进度和 Session 生命周期绑定,受会话锁、清理策略、负载均衡和代理缓冲影响。

如果只是单机站点或中等规模的后台文件上传,这个方案足够直接;如果需要断点续传、跨节点恢复、长时间异步处理或精细的任务审计,应把上传任务状态迁移到专门的共享存储、缓存或任务表中,并让进度接口读取业务任务,而不是把 Session 当作长期状态库。

可以用下面的判断表收尾:

现象优先检查处理方向
轮询接口总是 unavailableCookie、隐藏字段、cleanup确认同会话和键名拼接
上传结束前 bytes_processed 不变代理请求缓冲、会话排队调整缓冲并缩短会话占用
多机环境偶发查不到Session 存储和负载均衡使用共享会话或稳定路由
需要断点续传和长期任务Session 的生命周期边界升级为独立上传任务状态

常见问题

查询进度必须使用 POST 吗?

关键不是 HTTP 方法本身,而是请求必须携带同一会话,并提供能定位进度项的标识。示例使用 POST,是为了把轮询参数放在请求体中并与上传表单语义保持一致。

为什么上传完成后马上查不到进度?

进度数据只服务于上传期间,cleanup 开启时完成后被清理是正常边界。完成状态应由上传接口的业务结果记录,不要把进度数组当作永久凭证。

能不能直接返回整个 $_SESSION?

不建议。Session 可能包含登录态和其他业务数据,只提取当前上传需要的字段,并将上传标识与当前用户或任务绑定。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>