我所經歷的大文件數據導出(後臺執行,自動生成)

1、前言

    記錄一下之前作的後臺excel格式導出統計信息的功能,也是最近同事問到了相關東西,一時之間竟忘了具體的細節,所以記錄一下;php

    你們知道,excel導出數據的功能,後臺幾乎是必須功能,通常都是點擊後,生成文件而後自動下載,html

    若是是數據量小的話,一會兒即可請求完成,從而下載到本地;前端

    可是,若是數據量特別大的時候,頁面就必須一直在等待,直到寫入excel成功,shell

    這樣便影響了後臺使用者沒法操做其餘頁面,爲此,對excel導出作了如下功能優化:json

  1. excel導出分紅兩部份內容:生成excel文件和下載excel文件
  2. excel的文件生成在程序後臺執行,前端沒必要等待,可進行其餘後臺操做
  3. 增長下載文件頁面,顯示excel文件生成的進度,完成後,方可下載生成的excel文件
  4. 文件生成後,點擊下載方可下載相應的文件

 2、生成excel文件

    生成excel文件的方法有不少,暫不一一記錄,只是記錄本次的方法;api

    這裏用到了table的html格式,以及相應的excel的聲明瀏覽器

    (隱約記得其餘的方法用office07打開的時候好像是亂碼,後面嘗試用csv格式文件,可仍是亂碼,因此用了table的形式)bash

    文件的開頭:服務器

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

    文件的結尾:app

1 $struserdata = <<<Eof 2         </table>
3         </div>
4         </body>
5         </html>
6 Eof;
View Code

    固然,文件中間就是一些tr td 標籤了。

 3、讓程序在後臺執行

    場景:

        用戶點擊 生成excel後,跳轉到下載頁面,程序在後臺執行,用戶可沒必要等待生成完成,可執行其餘操做;

        下載頁面可看到文件生成的進度以及是否可下載狀態

    思路:

        點擊 生成excel,顯示下載頁面  ---> show_download方法

        生成excel ---> create_excel 方法

    show_download方法中調用 create_excel方法,而show_download 方法中,本身用了一下命令行執行程序的方式,

    利用php命令行的方式,把參數傳遞給 create_excel方法

1  // $cmd = "/usr/bin/php /home/xxx/xxx.php " . $strjoin . " >/dev/null & "; 2  // $a=exec($cmd, $out, $returndata);
3  
4  
5  $command = "/usr/bin/php ".STATISTIC_EXPORT_SCRIPT_DIR."xxx.php " . "'" .$strjoin ."'". " " . $uid . " ". $action ."  & "; 6  $process = proc_open($command, array(),$pipes); 7  $var = proc_get_status($process); 8  proc_close($process); 9  $pid = intval($var['pid'])+1;

   

其餘方法記錄:

$scriptPath = dirname(__FILE__) . '/../../crontab/updateRankData.php';
if ($rankId) {
    $cmd = "/usr/bin/php {$scriptPath} rank_id={$rankId} > /dev/null &";
} else {
    $cmd = "/usr/bin/php {$scriptPath} > /dev/null &";
}
$fp = @popen($cmd, "r");
if ($fp) @pclose($fp);
return true;

 

  而在create_excel方法中:

    需填寫如下代碼:

1 set_time_limit(0); //取消腳本運行時間的超時上限
2 
3 ignore_user_abort(TRUE); //後臺運行,不受用戶關閉瀏覽器的影響

    調用相關的api獲得數據:

1 $statistic = call_user_func(array('shellscript','get_result'),$url,$params); 2 if(!is_object($statistic) || !isset($statistic->data->items)){ 3     usleep(400000);//中止400毫秒
4     $statistic = call_user_func(array('shellscript','get_result'),$url,$params); 5 }

 4、顯示文件生成進度

    可是怎麼顯示相應的文件生成進度呢,怎麼知道文件到底生成好了沒有呢?

    這裏,我用到的方法是,在寫入數據文件的時候data.xsl,每一個數據文件都生成一個對應的文件進度文件,暫且稱爲flag_data.xsl;

    思路:

  1. 第一次請求api的時候,根據返回的total總數,以及pagesize,肯定要請求的次數count;
  2. 這樣即可知道要請求api的次數(分頁請求api),在寫入數據文件的同時,同時寫入進度文件flag_data.xsl;   
    數據格式大約是(以逗號分割)
        1,5
        2,5
        ...
  3. 而後顯示文件進度的時候,讀取進度文件,這樣變可知道數據文件大致的進度
  4. 前端js處理時,幾秒讀取一次相應的方法(若是都100%進度,可中止請求方法),從而實現動態查看文件的生成進度

    查看文件的進度方法:

 1     public function execscript_process(){  2         $this->load->library('smarty');  3         $file_arr_str = array();  4         $file_arr_process = array();  5         $file_arr_name = array();  6         $file_arr = array();  7         $refresh_flag = 'yes';  8         $uid = $_REQUEST['uid'];  9         $url_dir = STATISTIC_EXPORT_FILE_DIR.$uid .'/';//@todo
10         if(!is_dir($url_dir)){ 11             @mkdir($url_dir,0777); 12  } 13         $files = scandir($url_dir); 14 
15         if(!empty($files)){ 16             foreach ($files as $key => $value) { 17                 if($value!='.' && $value!='..'){ 18                     if(substr($value, 0 , 5)=="flag_"){ 19                         $file_size = filesize($url_dir . $value); 20                         if(!empty($file_size)){ 21                             $fhandle = fopen($url_dir . $value, 'rb+'); 22                             fseek($fhandle, -1, SEEK_END); 23                             $fstr = ''; 24                             while(($c = fgetc($fhandle)) !== false) { 25                               if($c == "\n" && $fstr) break; 26                               $fstr = $c . $fstr; 27                               fseek($fhandle, -2, SEEK_CUR); 28  } 29                             fclose($fhandle); 30                             $fstr = trim($fstr); 31                             $fstr_arr_str = explode(',', $fstr); 32                             $file_arr_process[] = 100 * number_format($fstr_arr_str[0]/$fstr_arr_str[1],2).'%'; 33                             $file_arr_name[] = substr($value,5); 34  } 35  } 36  } 37  } 38             
39             foreach ($file_arr_process as $key => $value) { 40                 if($value != '100%'){ 41                     $refresh_flag = 'no'; 42                     break; 43  } 44  } 45  } 46 
47         $file_arr = array( 48             'process' => $file_arr_process,
49             'name' => $file_arr_name,
50             'refresh_flag' => $refresh_flag
51  ); 52         $file_arr_json = json_encode($file_arr); 53         echo $file_arr_json; 54     }
View Code

 5、下載文件

    文件的下載就好說了,既然已經都生成成功,下載的方法以下:

 1     public function execscript_download(){  2         $filename = $_REQUEST['filename'];  3         $uid = $_REQUEST['uid'];  4         $file_dir = STATISTIC_EXPORT_FILE_DIR.$uid.'/'.$filename;  5         if (!file_exists($file_dir)){  6             header("Content-type: text/html; charset=utf-8");  7             echo "File not found!";  8             exit;  9         } else { 10             ini_set("memory_limit","500M"); 11             header('Content-Description: File Transfer'); 12             header('Content-Type: application/octet-stream'); 13             header('Content-Disposition: attachment; filename='.basename($file_dir)); 14             header('Content-Transfer-Encoding: binary'); 15             header('Expires: ' . gmdate('D, d M Y H:i:s') . ' GMT'); 16             header('Cache-Control: must-revalidate,post-check=0, pre-check=0'); 17             header('Pragma: public'); 18             header('Content-Length: ' . filesize($file_dir)); 19             readfile($file_dir); 20  } 21 
22     }

 6、上線後出現的問題

    本地原本已經測試完畢,可上線後,卻出現了奇怪的問題;

    現象描述:

        當在後臺點擊生成文件,跳轉到下載頁的時候,由於下載頁是顯示文件進度的頁面,
        居然出現有時候有剛剛點擊的文件進度,有時候沒有,就感受沒有生成相應的文件同樣;

    解決方法:

        由於數據文件和進度文件都是生成在程序的某個文件夾file中,因此讀取的時候都是讀取的文件夾下的文件,從而判斷顯示進度;

        後面才知道,因爲後臺程序有兩臺服務器,致使讀取以及下載的時候找不到相應的文件夾,兩個服務器相應的文件夾弄個共享目錄就能夠了

 7、相應的後續優化

    因爲下載的文件多了,致使文件夾下的文件愈來愈多,而原來生成的文件是沒有價值的,因此加了個按期刪除文件的功能,只保留近七天的文件

    固然能夠用crontab,只不過我比較懶,是在點擊生成文件的時候,判斷了一下文件夾中的過時文件,從而刪除

 1     public function execscript_process_show(){  2         $this->load->library('smarty');  3         $uid = $_REQUEST['uid'];  4         $url_dir = STATISTIC_EXPORT_FILE_DIR.$uid .'/';//@todo
 5         if(!is_dir($url_dir)){  6             @mkdir($url_dir,0777);  7  }  8         $files = scandir($url_dir);  9         if(!empty($files)){ 10             foreach ($files as $key => $value) { 11                 if($value!='.' && $value!='..'){ 12                     foreach ($files as $key => $value) { 13                         if($value!='.' && $value!='..'){ 14                             if(substr($value, 0 , 5)!="flag_"){ 15                                 $filenamedate = substr($value, 0,10); 16                                 $today = date('Y-m-d',time()); 17                                 $filenamedate = date('Y-m-d',strtotime($filenamedate)+(STATISTIC_FILE_EXPIRE_DAY-1)*24*3600); 18                                 if($today>$filenamedate){//文件過時
19                                     @unlink($url_dir . $value); 20                                     @unlink($url_dir . 'flag_' . $value); 21  } 22  } 23  } 24  } 25  } 26  } 27  } 28 
29         $this->smarty->assign('uid',$uid); 30         $this->smarty->display('interact/statistic/execscript.tpl'); 31     }

 8、後記

    大文件的導出大致就是這個樣子,歡迎你們吐槽,共同交流;

    當時在用命令行執行方法的時候,也參考了一下相應的資料,記錄一下;

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
相關文章
相關標籤/搜索