您所在的位置:小祥子 » 编程 » PHP » 正文

我所经历的大文件数据导出(后台执行,自动生成)

时间:2015-08-10 编辑:糖糖果 来源:CnBlogs

一、前言

    记录一下以前做的后台excel格式导出统计信息的功能,也是最近同事问到了相关东西,一时之间竟忘了具体的细节,因此记录一下;

    大家知道,excel导出数据的功能,后台几乎是必须功能,一般都是点击后,生成文件然后自动下载,

    如果是数据量小的话,一下子便可请求完成,从而下载到本地;

    但是,如果数据量特别大的时候,页面就必须一直在等待,直到写入excel成功,

    这样便影响了后台使用者无法操作其他页面,为此,对excel导出做了以下功能优化:

  1. excel导出分成两部分内容:生成excel文件和下载excel文件
  2. excel的文件生成在程序后台执行,前端不必等待,可进行其他后台操作
  3. 增加下载文件页面,显示excel文件生成的进度,完成后,方可下载生成的excel文件
  4. 文件生成后,点击下载方可下载相应的文件

 二、生成excel文件

    生成excel文件的方法有很多,暂不一一记录,只是记录本次的方法;

    这里用到了table的html格式,以及相应的excel的声明

    (隐约记得其他的方法用office07打开的时候好像是乱码,后面尝试用csv格式文件,可还是乱码,所以用了table的形式)

    文件的开头:

     $struserdata = <<<Eof
         <html xmlns:o="urn:schemas-microsoft-com:office:office"
         xmlns:x="urn:schemas-microsoft-com:office:excel"
         xmlns="http://www.w3.org/TR/REC-html40">
     
         <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
         <html>
         <head>
             <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
         <style id="Classeur1_16681_Styles">
         </style>
         </head>
         <body>
         <div id="Classeur1_16681" align=center x:publishsource="Excel">
     
         <table x:str border=1 cellpadding=0 cellspacing=0 width=100% style='border-collapse: collapse'>
 Eof;
View Code

    文件的结尾:

 $struserdata = <<<Eof
         </table>
         </div>
         </body>
         </html>
 Eof;
View Code

    当然,文件中间就是一些tr td 标签了。

 三、让程序在后台执行

    场景:

        用户点击 生成excel后,跳转到下载页面,程序在后台执行,用户可不必等待生成完成,可执行其他操作;

        下载页面可看到文件生成的进度以及是否可下载状态

    思路:

        点击 生成excel,显示下载页面  ---> show_download方法

        生成excel ---> create_excel 方法

    show_download方法中调用 create_excel方法,而show_download 方法中,自己用了一下命令行执行程序的方式,

    利用PHP命令行的方式,把参数传递给 create_excel方法

  // $cmd = "/usr/bin/php  /home/xxx/xxx.php " . $strjoin . "  >/dev/null & ";
  // $a=exec($cmd, $out, $returndata);
  
  
  $command = "/usr/bin/php ".STATISTIC_EXPORT_SCRIPT_DIR."xxx.php " . "'" .$strjoin ."'". " " . $uid . " ". $action ."  & ";
  $process = ($command, array(),$pipes); 
  $var = ($process); 
  ($process);
  $pid = ($var['pid'])+1;

    而在create_excel方法中:

    需填写以下代码:

 (0); //取消脚本运行时间的超时上限
 
 (TRUE); //后台运行,不受用户关闭浏览器的影响

    调用相关的api得到数据:

 $statistic = (array('shellscript','get_result'),$url,$params);
 if(!($statistic) || !isset($statistic->data->items)){
     (400000);//停止400毫秒
     $statistic = (array('shellscript','get_result'),$url,$params);
 }

 四、显示文件生成进度

    但是怎么显示相应的文件生成进度呢,怎么知道文件到底生成好了没有呢?

    这里,我用到的方法是,在写入数据文件的时候data.xsl,每个数据文件都生成一个对应的文件进度文件,暂且称为flag_data.xsl;

    思路:

  1. 第一次请求api的时候,根据返回的total总数,以及pagesize,确定要请求的次数count;
  2. 这样便可知道要请求api的次数(分页请求api),在写入数据文件的同时,同时写入进度文件flag_data.xsl;   
    数据格式大约是(以逗号分割)
        1,5
        2,5
        ...
  3. 然后显示文件进度的时候,读取进度文件,这样变可知道数据文件大体的进度
  4. 前端JS处理时,几秒读取一次相应的方法(如果都100%进度,可停止请求方法),从而实现动态查看文件的生成进度

    查看文件的进度方法:

     public function execscript_process(){
         $this->load->library('smarty');
         $file_arr_str = array();
         $file_arr_process = array();
         $file_arr_name = array();
         $file_arr = array();
         $refresh_flag = 'yes';
         $uid = $_REQUEST['uid'];
         $url_dir = STATISTIC_EXPORT_FILE_DIR.$uid .'/';//@todo
         if(!($url_dir)){
             @($url_dir,0777);
         }
         $files = ($url_dir);
 
         if(!empty($files)){
             foreach ($files as $key => $value) {
                 if($value!='.' && $value!='..'){
                     if(($value, 0 , 5)=="flag_"){
                         $file_size = ($url_dir . $value);
                         if(!empty($file_size)){
                             $fhandle = ($url_dir . $value, 'rb+');
                             ($fhandle, -1, SEEK_END);
                             $fstr = '';
                             while(($c = ($fhandle)) !== false) {
                               if($c == "\n" && $fstr) break;
                               $fstr = $c . $fstr;
                               ($fhandle, -2, SEEK_CUR);
                             }
                             ($fhandle);
                             $fstr = ($fstr);
                             $fstr_arr_str = (',', $fstr);
                             $file_arr_process[] = 100 * ($fstr_arr_str[0]/$fstr_arr_str[1],2).'%';
                             $file_arr_name[] = ($value,5);
                          }
                     }
                 }
             }
             
             foreach ($file_arr_process as $key => $value) {
                 if($value != '100%'){
                     $refresh_flag = 'no';
                     break;
                 }
             }
         }
 
         $file_arr = array(
             'process' => $file_arr_process,
             'name' => $file_arr_name,
             'refresh_flag' => $refresh_flag
             );
         $file_arr_json = json_encode($file_arr);
         echo $file_arr_json;
     }
View Code

 五、下载文件

    文件的下载就好说了,既然已经都生成成功,下载的方法如下:

     public function execscript_download(){
         $filename = $_REQUEST['filename'];
         $uid = $_REQUEST['uid'];
         $file_dir = STATISTIC_EXPORT_FILE_DIR.$uid.'/'.$filename;
         if (!($file_dir)){
             ("Content-type: text/html; charset=utf-8");
             echo "File not found!";
             exit; 
         } else {
             ("memory_limit","500M"); 
             ('Content-Description: File Transfer');
             ('Content-Type: application/octet-stream');
             ('Content-Disposition: attachment; filename='.($file_dir));
             ('Content-Transfer-Encoding: binary');
             ('Expires: ' . ('D, d M Y H:i:s') . ' GMT');
             ('Cache-Control: must-revalidate,post-check=0, pre-check=0');
             ('Pragma: public');
             ('Content-Length: ' . ($file_dir));
             ($file_dir);
         }
 
     }

 六、上线后出现的问题

    本地本来已经测试完毕,可上线后,却出现了奇怪的问题;

    现象描述:

        当在后台点击生成文件,跳转到下载页的时候,因为下载页是显示文件进度的页面,
        竟然出现有时候有刚刚点击的文件进度,有时候没有,就感觉没有生成相应的文件一样;

    解决方法:

        因为数据文件和进度文件都是生成在程序的某个文件夹file中,所以读取的时候都是读取的文件夹下的文件,从而判断显示进度;

        后面才知道,由于后台程序有两台服务器,导致读取以及下载的时候找不到相应的文件夹,两个服务器相应的文件夹弄个共享目录就可以了

 七、相应的后续优化

    由于下载的文件多了,导致文件夹下的文件越来越多,而原来生成的文件是没有价值的,所以加了个定期删除文件的功能,只保留近七天的文件

    当然可以用crontab,只不过我比较懒,是在点击生成文件的时候,判断了一下文件夹中的过期文件,从而删除

     public function execscript_process_show(){
         $this->load->library('smarty');
         $uid = $_REQUEST['uid'];
         $url_dir = STATISTIC_EXPORT_FILE_DIR.$uid .'/';//@todo
         if(!($url_dir)){
             @($url_dir,0777);
         }        
         $files = ($url_dir);
         if(!empty($files)){
             foreach ($files as $key => $value) {
                 if($value!='.' && $value!='..'){
                     foreach ($files as $key => $value) {
                         if($value!='.' && $value!='..'){
                             if(($value, 0 , 5)!="flag_"){
                                 $filenamedate = ($value, 0,10);
                                 $today = ('Y-m-d',());
                                 $filenamedate = ('Y-m-d',($filenamedate)+(STATISTIC_FILE_EXPIRE_DAY-1)*24*3600);
                                 if($today>$filenamedate){//文件过期
                                     @($url_dir . $value);
                                     @($url_dir . 'flag_' . $value);
                                 }
                             }
                         }
                     }                    
                 }
             }
         }
 
         $this->smarty->assign('uid',$uid);
         $this->smarty->display('interact/statistic/execscript.tpl');
     }

 八、后记

    大文件的导出大体就是这个样子,欢迎大家吐槽,共同交流;

    当时在用命令行执行方法的时候,也参考了一下相应的资料,记录一下;

http://blog.csdn.net/yysdsyl/article/details/4636457

http://www.codesky.net/article/201202/163385.html

http://www.cnblogs.com/zdz8207/p/3765567.html

http://blog.163.com/mojian20040228@126/blog/static/4112219320097300922992/

http://php.net/manual/en/features.commandline.php

http://blog.csdn.net/yangjun07167/article/details/5603425

http://blog.csdn.net/yunsongice/article/details/5445448

http://www.cppblog.com/amazon/archive/2011/12/01/161281.ASPx

http://blog.51yip.com/tag/proc_open

http://www.justwinit.cn/post/1418/

http://limboy.me/tech/2010/12/05/php-async.html