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 相关文章推荐
IIS6的PHP最佳配置方法
Mar 19 PHP
php 需要掌握的东西 不做浮躁的人
Dec 28 PHP
如何获知PHP程序占用多少内存(memory_get_usage)
Sep 23 PHP
php获取通过http协议post提交过来xml数据及解析xml
Dec 16 PHP
学习php设计模式 php实现享元模式(flyweight)
Dec 07 PHP
隐藏Nginx或Apache以及PHP的版本号的方法
Jan 03 PHP
thinkPHP商城公告功能开发问题分析
Dec 01 PHP
php实现简单加入购物车功能
Mar 07 PHP
PHP编程获取音频文件时长的方法【基于getid3类】
Apr 20 PHP
PHP简单获取上月、本月、近15天、近30天的方法示例
Jul 03 PHP
如何通过Apache在本地配置多个虚拟主机
Jul 29 PHP
phpquery中文手册
Mar 18 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
PHP伪造referer实例代码
2008/09/20 PHP
实用PHP会员权限控制实现原理分析
2011/05/29 PHP
php连接函数implode与分割explode的深入解析
2013/06/26 PHP
PHP mkdir()无写权限的问题解决方法
2014/06/19 PHP
基于php(Thinkphp)+jquery 实现ajax多选反选不选删除数据功能
2017/02/24 PHP
jQuery侧边栏随窗口滚动实现方法
2013/03/04 Javascript
JS/jQuery实现默认显示部分文字点击按钮显示全部内容
2013/05/13 Javascript
jquery ajax对特殊字符进行转义防止js注入使用示例
2013/11/21 Javascript
js与jQuery 获取父窗、子窗的iframe
2013/12/20 Javascript
Jquery+asp.net后台数据传到前台js进行解析的方法
2014/05/11 Javascript
jQuery切换网页皮肤并保存到Cookie示例代码
2014/06/16 Javascript
Jquery仿IGoogle实现可拖动窗口示例代码
2014/08/22 Javascript
简述JavaScript中正则表达式的使用方法
2015/06/15 Javascript
jQuery实现灰蓝风格标准二级下拉菜单效果代码
2015/08/31 Javascript
JS实现的N多简单无缝滚动代码(包含图文效果)
2015/11/06 Javascript
jquery插件jquery.LightBox.js实现点击放大图片并左右点击切换效果(附demo源码下载)
2016/02/25 Javascript
JavaScript 浏览器兼容性总结及常用浏览器兼容性分析
2016/03/30 Javascript
jquery分页插件jquery.pagination.js使用方法解析
2016/04/01 Javascript
浅谈JS正则表达式的RegExp对象和括号的使用
2016/07/28 Javascript
axios基本入门用法教程
2017/03/25 Javascript
vue.js todolist实现代码
2017/10/29 Javascript
微信小程序使用wxParse解析html的方法教程
2018/07/06 Javascript
在vue中更换字体,本地存储字体非引用在线字体库的方法
2018/09/28 Javascript
解决JS表单验证只有第一个IF起作用的问题
2018/12/04 Javascript
python中 ? : 三元表达式的使用介绍
2013/10/09 Python
Saltstack快速入门简单汇总
2016/03/01 Python
Python实现单词翻译功能
2017/06/06 Python
python中requests和https使用简单示例
2018/01/18 Python
pandas数值计算与排序方法
2018/04/12 Python
Python直接赋值、浅拷贝与深度拷贝实例分析
2019/06/18 Python
python numpy之np.random的随机数函数使用介绍
2019/10/06 Python
Python 3.6打包成EXE可执行程序的实现
2019/10/18 Python
Python实现平行坐标图的绘制(plotly)方式
2019/11/22 Python
水电站项目建议书
2014/05/12 职场文书
乡镇党的群众路线教育实践活动剖析材料
2014/10/09 职场文书
餐饮店长岗位职责
2015/04/14 职场文书