一、场景及方案介绍
Web 应用中常常需要提供文件上传功能,典型的应用场景有用户头像、相册照片上传等。当要上传的文件比较大的时候,为了更好的用户体验,显示文件上传进度是必要。
在 PHP5.4 之前,php 实现文件上传进度条,主要有三种方法:
使用 flash、java、activeX
使用 PHP 的 APC 扩展
使用 HTML5 的 FILE API
第一种方法依赖第三方的浏览器插件,通用性不好,并且存在安全隐患。不过由于 flash 的使用范围较广,因此很多网站使用 flash 作为解决方案。
第二种方法不足在于需要安装 PHP 的 PAC 扩展库,要求用户能够控制服务器的配置。
第三中方法应该是最理想的方法,不需要服务器的支持,仅仅在浏览器使用 JavaScript 即可。但由于 HTML5 标准尚未完全确立,各个浏览器的支持不同,所以这种方法不能大规模使用。
在 PHP5.4 版本中引入基于 session 的上传进度监视功能(session.upload.progress),是一种服务器端文件上传进度解决方案。可以不用安装 APC 扩展,使用原生的 PHP 加 ajax 即可实现上传进度条。这里使用的是 session+jQuery ajax 实现。
二、原理介绍
当浏览器向服务器上传一个文件时,PHP 会吧此文件上传的详细信息(上传时间、进度)存储在 session 中。跟随上传的进行,周期的更新 session 中的信息。浏览器可以使用 ajax 周期的请求服务器端脚本,取得 session 中的进度信息,从而客户端显示进度条。
文件上传信息要存储在 session 中,需要在 php.ini 的配置文件中进行以下设置:
1
2
3
4
5
6
7
8
9
10
11
session.upload_progress.enabled = On
session.upload_progress.cleanup = On
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,两项用来设置服务器对进度的更新频率。合理的配置可以减轻服务器的负载。
另外在文件上传表单中,需要为该上传设置一个标识符,并在接下来的过程中使用该标识符来引用进度信息。具体操作,在上传表单中添加一个隐藏域,它的 name 属性为 php.ini 中的 session.upload_progress.name 的值。该隐藏域的值是一个你自己定义的标识符,代码如下:
1
2
”
value=”test” />
接着文件上传的表单后,PHP 会在 $_SESSION 变量中新建键,键名是一个将 session.upload_progress.prifix 的值与上面你所定义的标识符连接后的字符串,可以通过以下代码得到:
1
2
3
$i = ini_get(‘session.upload_progress.name’);
$key = ini_get(‘session.upload_progress.prefix’).$_POST[$i];
$_SESSION[$key];
SESSION[SESSION[key] 的变量结构如下:
1
2
3
4
5
6
7
8
9
10
$_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(…), //同一请求中包含多个文件
),
);
通过 $_SEESION 中的 conten_length 和 bytes_processed 可以求出文件上传百分比。
三、具体实现
index.php 表单内容如下:(注意在文件最前面使用 session_start() 开启 session)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
此处表单中 session.upload_progress.name 隐藏域的值设为了 test。表单中可以添加多个文件上传表单,如果需要的话。还有就是,表单的 target 属性,指向了当前页面的一个隐藏 iframe,作用是防止提交表单后页面跳转。
id 为 progress 这个 div 是用来你显示进度条的。
处理文件上传的文件时 upload.php,与通常文件上传操作没有什么不同:
1
2
3
4
if (is_uploaded_file($_FILES['file1']['tmp_name'])) {
move_uploaded_file($_FILES['file1']['tmp_name'], "./{ $_FILES['file1']['name'] }");
}
使用 ajax 获取进度信息:这是最为关键的一步,需要建立 progress.php 文件,读取 session 中的信息;然后在 index.php 增加 js 代码,向 progress.php 发起 ajax 请求,根据获取的进度信息更新进度条。progress.php 的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
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;
}
最后在 index.php 中加入如下 js 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$(function(){
function (){
$.get('progress.php', { '<?php echo ini_get("session.upload_progress.name");?>': 'test'}, function(data){
var progress = parseInt(data);
$('.handle').show().css('left', 2 * progress + 'px');
$('.label').html(progress + '%');
$('#progress .bar').css('width', 2 * progress + 'px');
if (progress < 100) {
setTimeout('fetch_progress()', 1);
} else {
$('.label').html('上传完成');
}
});
}
$('#upload-form').submit(function(){
$('#progress').show();
setTimeout('fetch_progress()', 1);
});
});