PHP中使用Session配合Javascript实现文件上传进度条功能


Posted in PHP onOctober 15, 2014

Web应用中常需要提供文件上传的功能。典型的场景包括用户头像上传、相册图片上传等。当需要上传的文件比较大的时候,提供一个显示上传进度的进度条就很有必要了。

在PHP 5.4以前,实现这样的进度条并不容易,主要有三种方法:

1.使用Flash, Java, ActiveX
2.使用PHP的APC扩展
3.使用HTML5的File API

第一种方法依赖第三方的浏览器插件,通用性不足,且易带来安全隐患。不过由于Flash的使用比较广泛,因此还是有很多网站使用Flash作为解决方案。

第二种方法的不足在于,它需要安装PHP的APC扩展库,要求用户能够控制服务器端的配置。另外,如果安装APC仅仅是为了实现一个上传进度条,那么显然有点杀鸡用牛刀的意思。

第三种方法应该是最为理想的方法,不需要服务器端的支持,仅在浏览器端使用Javascript即可。但是由于HTML5标准尚未确立,各浏览器厂商的支持也不相同,所以暂时这种方法还难以普及。

PHP 5.4中引入的基于session的上传进度监视功能(session.upload_progress),它提供了一个服务器端的上传进度监视解决方案。升级到PHP 5.4之后,可以不必安装APC扩展,仅使用原生PHP和前端的Javascript即可实现上传进度条。

下面我们就详细介绍一下 PHP 5.4 的这个 session.upload_progress 新特性。

原理介绍

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

那么,文件上传信息具体是如何存储的?我们要如何访问它呢?下面我们来详细说明。

PHP 5.4 中引入了一些配置项(在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的相关信息,默认开启。

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]这个变量的结构是这样的:
$_SESSION["upload_progress_test"] = array(

 "start_time" => 1234567890,   // 开始时间

 "content_length" => 57343257, // POST请求的总数据长度

 "bytes_processed" => 453489,  // 已收到的数据长度

 "done" => false,              // 请求是否完成 true表示完成,false未完成
 // 单个文件的信息

 "files" => array(

  0 => array( ... ),

  // 同一请求中可包含多个文件

  1 => array( ... ),

 )

);

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

程序示例

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

上传表单

首先,来编写我们的上传表单页面 index.php,代码如下:

<form id="upload-form"

    action="upload.php" method="POST" enctype="multipart/form-data"

    style="margin:15px 0" target="hidden_iframe">
        <input type="hidden" name="" value="test" />

        <p><input type="file" name="file1" /></p> 

        <p><input type="submit" value="Upload" /></p>

</form>    
<iframe id="hidden_iframe" name="hidden_iframe" src="about:blank" style="display:none;"></iframe>
<div id="progress" class="progress" style="margin-bottom:15px;display:none;">

        <div class="bar" style="width:0%;"></div>

        <div class="label">0%</div>

</div>

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

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

#progress 这个div是用来显示进度条的。

注意 别忘了在index.php的最开始加上session_start()。

处理上传的文件

表单的action指向upload.php,我们在upload.php中处理上传的文件,将它转存到当前目录。这里与通常情况下的上传处理没有区别。

if(is_uploaded_file($_FILES['file1']['tmp_name'])){

        move_uploaded_file($_FILES['file1']['tmp_name'], "./{$_FILES['file1']['name']}");

}

?>

Ajax获取进度信息

这一步是关键,我们需要建立一个 progress.php 文件,用来读取session中的进度信息; 然后我们在 index.php 中增加Javascript代码,向 progress.php 发起Ajax请求,然后根据获得的进度信息更新进度条。

progress.php 的代码如下:

session_start();
$i = ini_get('session.upload_progress.name');
$key = ini_get("session.upload_progress.prefix") . $_GET[$i];
if (!empty($_SESSION[$key])) {

        $current = $_SESSION[$key]["bytes_processed"];

        $total = $_SESSION[$key]["content_length"];

        echo $current < $total ? ceil($current / $total * 100) : 100;

}else{

        echo 100;

}

?>

在这里我们获得$_SESSION变量中的进度信息,然后输出一个进度百分比。

在 index.php 中,我们将如下代码添加到页面底部 (为简便,这里使用jQuery):

function fetch_progress(){

        $.get('progress.php',{ '' : 'test'}, function(data){

                var progress = parseInt(data);
                $('#progress .label').html(progress + '%');

                $('#progress .bar').css('width', progress + '%');
                if(progress < 100){

                        setTimeout('fetch_progress()', 100);

                }else{

            $('#progress .label').html('完成!');

        }

        }, 'html');

}
$('#upload-form').submit(function(){

        $('#progress').show();

        setTimeout('fetch_progress()', 100);

});

当#upload-form被提交时,我们把进度条显示出来,然后反复调用 fetch_progress() 获得进度信息,并更新进度条,直到文件上传完毕,显示'完成!'。

Done!

完整代码下载:http://xiazai.3water.com/201410/tools/samples-master.rar

注意事项

input标签的位置

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

取消上传

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

setTimeout vs. setInterval

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

PHP 相关文章推荐
drupal 代码实现URL重写
May 04 PHP
php数组函数序列之array_splice() - 在数组任意位置插入元素
Nov 07 PHP
php日历制作代码分享
Jan 20 PHP
自己写了一个php检测文件编码的函数
Apr 21 PHP
php使用ZipArchive提示Fatal error: Class ZipArchive not found in的解决方法
Nov 04 PHP
php实现的ping端口函数实例
Nov 12 PHP
php中PDO方式实现数据库的增删改查
May 17 PHP
php判断用户是否手机访问代码
Jun 08 PHP
编写PHP程序检查字符串中的中文字符个数的实例分享
Mar 17 PHP
PHP单例模式简单用法示例
Jun 23 PHP
php设计模式之工厂模式用法经典实例分析
Sep 20 PHP
PHP isset()及empty()用法区别详解
Aug 29 PHP
jquery+php+ajax显示上传进度的多图片上传并生成缩略图代码
Oct 15 #PHP
PHP实现文件下载断点续传详解
Oct 15 #PHP
PHP多进程编程实例
Oct 15 #PHP
PHP实现采集中国天气网未来7天天气
Oct 15 #PHP
跟我学Laravel之视图 &amp; Response
Oct 15 #PHP
跟我学Laravel之请求与输入
Oct 15 #PHP
跟我学Laravel之路由
Oct 15 #PHP
You might like
理解PHP5中static和const关键字的区别
2007/03/19 PHP
php数据结构 算法(PHP描述) 简单选择排序 simple selection sort
2011/08/09 PHP
linux下使用crontab实现定时PHP计划任务失败的原因分析
2014/07/05 PHP
php查询相似度最高的字符串的方法
2015/03/12 PHP
PHP 使用位运算实现四则运算的代码
2021/03/09 PHP
利用Dojo和JSON建立无限级AJAX动态加载的功能模块树
2007/03/24 Javascript
JavaScript利用正则表达式去除日期中的“-”
2014/07/01 Javascript
javascript解析json实例详解
2014/11/05 Javascript
js使用onmousemove和onmouseout获取鼠标坐标的方法
2015/03/31 Javascript
javascript实现百度地图鼠标滑动事件显示、隐藏
2015/04/02 Javascript
jquery实现标题字体变换的滑动门菜单效果
2015/09/07 Javascript
Javascript typeof与instanceof的区别
2016/10/18 Javascript
HTML5 JS压缩图片并获取图片BASE64编码上传
2020/11/16 Javascript
nodejs读写json文件的简单方法(必看)
2017/03/09 NodeJs
Node.js+Express+Mysql 实现增删改查
2019/04/03 Javascript
用Vue.js方法创建模板并使用多个模板合成
2019/06/28 Javascript
js prototype和__proto__的关系是什么
2019/08/23 Javascript
js判断在哪个浏览器打开项目的方法
2020/01/21 Javascript
[00:29]2019完美世界全国高校联赛(秋季赛)总决赛海口落幕
2019/12/10 DOTA
python通过scapy获取局域网所有主机mac地址示例
2014/05/04 Python
python实现批量改文件名称的方法
2015/05/25 Python
python协程用法实例分析
2015/06/04 Python
Python的Scrapy爬虫框架简单学习笔记
2016/01/20 Python
Python绘制KS曲线的实现方法
2018/08/13 Python
python实现最大子序和(分治+动态规划)
2019/07/05 Python
Python : turtle色彩控制实例详解
2020/01/19 Python
使用 Python 处理3万多条数据只要几秒钟
2020/01/19 Python
Python变量格式化输出实现原理解析
2020/08/06 Python
python dict如何定义
2020/09/02 Python
初级会计求职信范文
2014/02/15 职场文书
文明寝室标语
2014/06/13 职场文书
HR求职自荐信范文
2014/06/21 职场文书
应届毕业生求职信范文
2014/07/07 职场文书
2014院党委领导班子对照检查材料思想汇报
2014/09/24 职场文书
如何用 Python 子进程关闭 Excel 自动化中的弹窗
2021/05/07 Python
uniapp开发打包多端应用完整方法指南
2022/12/24 Javascript