PHP使用Session实现上传进度功能详解


Posted in PHP onAugust 06, 2019

本文实例讲述了PHP使用Session实现上传进度功能。分享给大家供大家参考,具体如下:

实现文件上传进度条基本是依靠JS插件或HTML5的File API来完成,其实PHP配合ajax也能实现此功能。

PHP手册对于session上传进度是这么介绍的:

session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态

当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefixsession.upload_progress.name连接在一起的值。 通常这些键值可以通过读取INI设置来获得,例如

<?php
$key = ini_get("session.upload_progress.prefix") . ini_get("session.upload-progress.name");
var_dump($_SESSION[$key]);
?>

通过将$_SESSION[$key]["cancel_upload"]设置为TRUE,还可以取消一个正在处理中的文件上传。 当在同一个请求中上传多个文件,它仅会取消当前正在处理的文件上传和未处理的文件上传,但是不会移除那些已经完成的上传。 当一个上传请求被这么取消时,$_FILES中的error将会被设置为 UPLOAD_ERR_EXTENSION

session.upload_progress.freqsession.upload_progress.min_freq INI选项控制了上传进度信息应该多久被重新计算一次。 通过合理设置这两个选项的值,这个功能的开销几乎可以忽略不计。

注意:为了使这个正常工作,web服务器的请求缓冲区需要禁用,否则 PHP可能仅当文件完全上传完成时才能收到文件上传请求。 已知会缓冲这种大请求的程序有Nginx。

下面原理介绍:

当浏览器向服务器端上传一个文件时,PHP将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中。然后,随着上传的进行,周期性的更新session中的信息。这样,浏览器端就可以使用Ajax周期性的请求一个服务器端脚本,由该脚本返回session中的进度信息;浏览器端的Javascript即可根据这些信息显示/更新进度条了。

php.ini需配置以下选项

session.upload_progress.enabled = "1"
session.upload_progress.cleanup = "1"
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"

其中enabled控制upload_progress功能的开启与否,默认开启;

cleanup 则设置当文件上传的请求提交完成后,是否清除session的相关信息,默认开启,如果需要调试$_SESSION,则应该设为Off。

prefix 和 name 两项用来设置进度信息在session中存储的变量名/键名。

freq 和 min_freq 两项用来设置服务器端对进度信息的更新频率。合理的设置这两项可以减轻服务器的负担。

在上传文件的表单中,需要为该次上传设置一个标识符,并在接下来的过程中使用该标识符来引用进度信息。

具体的,在上传表单中需要有一个隐藏的input,它的name属性为php.ini中 session.upload_progress.name 的值;它的值为一个由你自己定义的标识符。如下:
 代码如下:

<input type="hidden" name="<?php echo ini_get('session.upload_progress.name'); ?>" value="test" />

接到文件上传的表单后,PHP会在$_SESSION变量中新建键,键名是一个将session.upload_progress.prefix的值与上面自定义的标识符连接后得到的字符串,可以这样得到:

代码如下:

$name = ini_get('session.upload_progress.name');
$key = ini_get('session.upload_progress.prefix') . $_POST[$name];
$_SESSION[$key]; // 这里就是此次文件上传的进度信息了
$_SESSION[$key]这个变量的结构是这样的:

array (
  'upload_progress_test' => array (
    'start_time' => 1491494993,  // 开始时间
    'content_length' => 1410397, // POST请求的总数据长度
    'bytes_processed' => 1410397, // 已收到的数据长度
    'done' => true,        // 请求是否完成 true表示完成,false未完成
    'files' => array (
      0 => array (
        'field_name' => 'file1',
        'name' => 'test.jpg',
        'tmp_name' => 'D:\\wamp\\tmp\\phpE181.tmp',
        'error' => 0,
        'done' => true,
        'start_time' => 1491494993,
        'bytes_processed' => 1410096,
      ),
    ),
  ),
);

这样,我们就可以使用其中的 content_length bytes_processed 两项来得到进度百分比。

原理介绍完了,下面我们来完整的实现一个基于PHP和Javascript的文件上传进度条。

上传表单index.php

<?php session_start(); ?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <title>PHP(5.4) Session 上传进度 Demo</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="keywords" content=""/>
  <meta name="description" content=""/>
  <meta name="author" content="">
  <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.2/css/bootstrap.min.css" rel="external nofollow" rel="stylesheet">
  <style type="text/css">
    body{
      font-size:1em;
      color:#333;
      font-family: "宋体", Arial, sans-serif;
    }
    h1, h2, h3, h4, h5, h6{
      font-family: "宋体", Georgia, serif;
      color:#000;
      line-height:1.8em;
      margin:0;
    }
    h1{ font-size:1.8em; }
    #wrap{
      margin-top:15px;
      margin-bottom:50px;
      background:#fff;
      border-radius:5px;
      box-shadow:inset 0 0 3px #000,
      0 0 3px #eee;
    }
    #header{
      border-radius:5px 5px 0 0;
      box-shadow:inset 0 0 3px #000;
      padding:0 15px;
      color:#fff;
      background: #333333;
    }
    #header h1{
      color:#fff;
    }
    #article{
      padding:0 15px;
    }
    #footer{
      text-align:center;
      border-top:1px solid #ccc;
      border-radius:0 0 5px 5px;
    }
    .progress {
      width: 100%;
      border: 1px solid #4da8fe;
      border-radius: 40px;
      height: 20px;
      position: relative;
    }
    .progress .labels {
      position: relative;
      text-align: center;
    }
    .progress .bar {
      position: absolute;
      left: 0;
      top: 0;
      background: #4D90FE;
      height: 20px;
      line-height:20px;
      border-radius: 40px;
      min-width: 20px;
    }
    .report-file {
      display: block;
      position: relative;
      width: 120px;
      height: 28px;
      overflow: hidden;
      border: 1px solid #428bca;
      background: none repeat scroll 0 0 #428bca;
      color: #fff;
      cursor: pointer;
      text-align: center;
      float: left;
      margin-right:5px;
    }
    .report-file span {
      cursor: pointer;
      display: block;
      line-height: 28px;
    }
    .file-prew {
      cursor: pointer;
      position: absolute;
      top: 0;
      left:0;
      width: 120px;
      height: 30px;
      font-size: 100px;
      opacity: 0;
      filter: alpha(opacity=0);
    }
    .container{
      padding-left:0;
      padding-right:0;
      margin:0 auto;
    }
  </style>
</head>
<body>
<div id="wrap" class="container">
  <div id="header">
    <h1>Session上传进度 Demo</h1>
  </div>
  <div id="article">
    <form id="upload-form" action="upload.php" method="POST" enctype="multipart/form-data" style="margin:15px 0"
       target="hidden_iframe">
      <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="test"/>
      <div class="report-file">
        <span>上传文件…</span><input tabindex="3" size="3" name="file1" class="file-prew" type="file" onchange="document.getElementById('textName').value=this.value">
      </div>
      <input type="text" id="textName" style="height: 28px;border:1px solid #f1f1f1" />
      <p>
        <input type="submit" class="btn btn-default" value="上传"/>
      </p>
    </form>
    <div id="progress" class="progress" style="margin-bottom:15px;display:none;">
      <div class="bar" style="width:0%;"></div>
      <div class="labels">0%</div>
    </div>
  </div> <!-- #article -->
  <div id="footer">
    <p> </p>
  </div>
</div><!-- #wrap -->
<iframe id="hidden_iframe" name="hidden_iframe" src="about:blank" style="display:none;"></iframe>
<script src="https://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
  function fetch_progress() {
    $.get('progress.php', {'<?php echo ini_get("session.upload_progress.name"); ?>': 'test'}, function (data) {
      var progress = parseInt(data);
      $('#progress .labels').html(progress + '%');
      $('#progress .bar').css('width', progress + '%');
      if (progress < 100) {
        setTimeout('fetch_progress()', 500);
      } else {
        $('#progress .labels').html('100%');
      }
    }, 'html');
  }
  $('#upload-form').submit(function () {
    $('#progress').show();
    //图片比较小,看不出进度条加载效果,初始设33%
    $('#progress .labels').html('33%');
    $('#progress .bar').css('width', '33%');
    setTimeout('fetch_progress()', 500);
  });
</script>
</body>
</html>

注意表单中的session.upload_progress.name隐藏项,值设置为了test。表单中仅有一个文件上传input,如果需要,你可以添加多个。

这里需要特别注意一下表单的target属性,这里设置指向了一个当前页面中的iframe。这一点很关键,通过设置target属性,让表单提交后的页面显示在iframe中,从而避免当前的页面跳转。因为我们还得在当前页面显示进度条呢。

上传文件upload.php

<?php
/**
 * 上传文件
 */
if(is_uploaded_file($_FILES['file1']['tmp_name'])){
  //unlink($_FILES['file1']['tmp_name']);
  $fileName = 'pic_' . date('YmdHis') . mt_rand(10000,99999);
  $ext = substr($_FILES['file1']['name'], strrpos($_FILES['file1']['name'], '.'));
  move_uploaded_file($_FILES['file1']['tmp_name'], $fileName . $ext);
}

ajax获取上传进度progress.php

<?php
/**
 * AJAX获取上传文件进度
 */
session_start();
$i = ini_get('session.upload_progress.name');
//session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
$key = ini_get("session.upload_progress.prefix") . $_GET[$i];
//session.upload_progress.prefix = "upload_progress_" . 'test'
if (!empty($_SESSION[$key])) {
  $current = $_SESSION[$key]["bytes_processed"]; // 已收到的数据长度
  $total  = $_SESSION[$key]["content_length"]; // POST请求的总数据长度
  echo $current < $total ? ceil($current / $total * 100) : 100;
}else{
  echo 100;
}

注意事项:

1.input标签的位置name为session.upload_progress.name的input标签一定要放在文件input <input type="file" /> 的前面。

2.通过设置 $_SESSION[$key]['cancel_upload'] = true 可取消当次上传。但仅能取消正在上传的文件和尚未开始的文件。已经上传成功的文件不会被删除。

3.应该通过 setTimeout() 来调用 fetch_progress(),这样可以确保一次请求返回之后才开始下一次请求。如果使用 setInterval() 则不能保证这一点,有可能导致进度条出现'不进反退'。

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
很实用的一个完整email发送程序
Oct 09 PHP
php数字转汉字代码(算法)
Oct 08 PHP
解析Win7 XAMPP apache无法启动的问题
Jun 26 PHP
PHP与javascript实现变量交互的示例代码
Jul 23 PHP
php中的filesystem文件系统函数介绍及使用示例
Feb 13 PHP
ThinkPHP使用心得分享-ThinkPHP + Ajax 实现2级联动下拉菜单
May 15 PHP
ThinkPHP3.2.1图片验证码实现方法
Aug 19 PHP
PHP判断json格式是否正确的实现代码
Sep 20 PHP
PHP开发之归档格式phar文件概念与用法详解【创建,使用,解包还原提取】
Nov 17 PHP
php单元测试phpunit入门实例教程
Nov 17 PHP
php中目录操作opendir()、readdir()及scandir()用法示例
Jun 08 PHP
如何用PHP实现分布算法之一致性哈希算法
May 26 PHP
PHP使用ajax的post方式下载excel文件简单示例
Aug 06 #PHP
PHP中的自动加载操作实现方法详解
Aug 06 #PHP
Thinkphp自定义生成缩略图尺寸的方法
Aug 05 #PHP
thinkphp5.1框架中容器(Container)和门面(Facade)的实现方法分析
Aug 05 #PHP
RSA实现JS前端加密与PHP后端解密功能示例
Aug 05 #PHP
thinkPHP5框架接口写法简单示例
Aug 05 #PHP
ThinkPHP5+UEditor图片上传到阿里云对象存储OSS功能示例
Aug 05 #PHP
You might like
跟我学Laravel之请求与输入
2014/10/15 PHP
将PHP从5.3.28升级到5.3.29时Nginx出现502错误
2015/05/09 PHP
php如何连接sql server
2015/10/16 PHP
php-fpm添加service服务的例子
2018/04/27 PHP
js 关键词高亮(根据ID/tag高亮关键字)案例介绍
2013/01/21 Javascript
NodeJS制作爬虫全过程(续)
2014/12/22 NodeJs
在父页面得到zTree已选中的节点的方法
2015/02/12 Javascript
Node.js环境下JavaScript实现单链表与双链表结构
2016/06/12 Javascript
nodejs搭建本地服务器并访问文件的方法
2017/03/03 NodeJs
ionic 自定义弹框效果
2017/06/27 Javascript
Vue2.0基于vue-cli+webpack同级组件之间的通信教程(推荐)
2017/09/14 Javascript
JS实现手写parseInt的方法示例
2017/09/24 Javascript
详解VUE 数组更新
2017/12/16 Javascript
javascript中关于类型判断的一些疑惑小结
2018/10/14 Javascript
js使用swiper实现层叠轮播效果实例代码
2018/12/12 Javascript
解决vue项目获取dom元素宽高总是不准确问题
2020/07/29 Javascript
如何在Vue.JS中使用图标组件
2020/08/04 Javascript
微信小程序实现点击导航条切换页面
2020/11/19 Javascript
[53:23]Secret vs Liquid 2018国际邀请赛淘汰赛BO3 第二场 8.25
2018/08/29 DOTA
Python3爬虫之urllib携带cookie爬取网页的方法
2018/12/28 Python
手把手教你使用Python创建微信机器人
2019/04/29 Python
Django后端接收嵌套Json数据及解析详解
2019/07/17 Python
Selenium执行完毕未关闭chromedriver/geckodriver进程的解决办法(java版+python版)
2020/12/07 Python
CSS3绘制有活力的链接下划线
2016/07/14 HTML / CSS
html5 迷宫游戏(碰撞检测)实例一
2013/07/25 HTML / CSS
Alba Moda瑞士网上商店:独家意大利时尚女装销售
2016/11/28 全球购物
英国殿堂级有机护肤品牌:Rodial
2017/04/17 全球购物
儿科主治医生个人求职信
2013/09/23 职场文书
中学生运动会入场词
2014/02/12 职场文书
cf搞笑广告词
2014/03/14 职场文书
社区消防工作实施方案
2014/03/21 职场文书
学习保证书
2015/01/17 职场文书
2015年安全生产管理工作总结
2015/05/25 职场文书
大学生自我鉴定怎么写
2019/05/07 职场文书
遇事可以测出您的见识与格局
2019/09/16 职场文书
Python简易开发之制作计算器
2022/04/28 Python