用AJAX实现进度条(1.服务器端)

最近在写PHP,将后台数据导出为Excel文件,用的是PHPExcel。效果挺好,就是速度有点慢,对于目前的数据量,需要40多秒才能生成完一个Excel文件。为了让导出功能有较好的用户体验,必须在导出的时候做一个进度条。

在没加入进度条时,我有两个页面:

  • export.html
    这是导出功能的主页面,用户设置好参数之后,点击提交按钮,向export.php发起导出请求。
  • export.php
    这是生成Excel文件的PHP程序,它会生成一个Excel文件,然后将文件的地址返回给客户端。

因为很少写PHP和JavaScript,所以我难免会有些弱弱的问题。
1.我要在export.html中加入进度条,那么进度条的状态从哪里来,可以像写C程序一样,在PHP程序执行期间打印一些中间结果返回给客户端吗?
答案是NO,因为在export.php没有执行完毕之前,它中间的输出会缓存在server端,何时到达客户端是不可控的。

2.可以像写socket程序一样,在客户端监听,然后让服务器端主动把状态信息推送到客户端吗?
答案是暂不知道···从google的结果来看,一般都是用客户端主动请求的方式来获取状态信息。但确有server push technology,我还没来得及细看,就先用点简单直接的方法,在客户端做状态轮询吧。

那么,我要在server端增加一个返回当前状态的接口:status.php。那status.php收到用户请求之后,如何去查export.php的状态呢?
一个很直接的方法就是用文件传递状态:export.php在开始执行时生成一个临时文件,然后在运行过程中把当前的状态写入这个临时文件,status.php就可以通过读这个临时文件来获取export.php的运行状态了。临时文件的名称,是在export.html中生成的一个随机数,因为:

  1. 可以避免多个用户同时访问export.php时产生文件名冲突;
  2. 只有把文件名做为参数传给status.php,它才能知道去查询哪个临时文件。

我的目标就很明确了:

  • export.html
    向export.php发起AJAX请求,开始导出Excel。然后,每隔一秒向status.php发起一次AJAX请求,查询导出的状态,当导出结束之后,停止状态查询。
  • export.php
    程序开始时创建一个临时文件,在生成Excel的过程中,不断地把当前状态信息写入临时文件。
  • status.php
    从临时文件中读取状态,返回给client。

模拟export.php的代码
<?php
//用来当临时文件名的参数
$export_id = $_GET['export_id'];
$fp = fopen("/var/tmp/$export_id","w");

//当前步骤和总的步骤数,在进度条展示时有用。
$current_step=1;
$total_step = 13;

fwrite($fp,"[$current_step/$total_step] fetching data from MySQL\n");
fflush($fp);// 调用fflush让状态信息立刻写入临时文件。
$current_step++;
sleep(1);

for($i=1;$i<=10;$i++){
  fwrite($fp,"[$current_step/$total_step] exporting table $i\n");
  fflush($fp);
  $current_step++;
  sleep(1);
}

fwrite($fp,"[$current_step/$total_step] writing Excel file\n");
fflush($fp);
$current_step++;
sleep(1);

fwrite($fp,"[$current_step/$total_step] finished\n");
fclose($fp);

//导出完毕之后,返回生成的excel文件的地址。
$fake_excel_url = "infomation.xls";
echo $fake_excel_url;
?>

status.php的代码
<?php
$export_id = $_GET['export_id'];

//读取状态文件的最后一行
$file = escapeshellarg("/var/tmp/$export_id");
$line = `tail -n 1 $file`;

//解析出当前步骤数,总步骤数,和当前状态信息,并以json格式返回给client
$line = trim($line);
$a = strpos($line,'/');
$b = strpos($line,']');
$current_step = (int)substr($line,1,$a);
$total_step = (int)substr($line,$a+1,$b-$a);
$msg = substr($line,$b+1);
echo json_encode(array(
    "current_step"=>$current_step,
    "total_step"=>$total_step,
    "msg"=>$msg
));
?>

读取文件最后一行的方法是在stackoverflow上看到的,原来PHP里面可以直接调用shell命令···

用JavaScript在浏览器中展现进度条的方法,下篇继续。

Comments