由於業務需要,需要在後臺新增匯出Excel的功能。所以很自然的想到了PHPExcel,於是就開始動手了。
由於之前沒有做過,所以基本都是借鑑網上大佬的。
-
檢查是否有PHPExcel包,如果沒有,請在網上下好。
2.開始寫程式碼了。需要注意得是,一般匯出資料,資料量都會較大。所以需要設定記憶體使用量和,超時時間。並且需要注意匯出的資料會被Excel改寫,如身份證號,手機號,時間戳等長數字會被顯示為科學計數法,時間格式也顯示為Excel的預設顯示格式。脾氣是大得很吶....惹不起惹不起。所以經過查資料尋得PHPExcel中有個方法(setCellValueExplicit)是可以設定匯出單元格格式為文字。還有種方法是獲取資料時做處理,後面會講到。/**\ * 匯出Excel檔案 速度慢 * @param $fileName 匯出的檔名 * @param $headArr 資料頭 * @param $data 匯出資料 */ function getExcel($fileName,$headArr,$data){ //設定PHP最大單執行緒的獨立記憶體使用量 ini_set('memory_limit','1024M'); //程式超時設定設為不限時 ini_set('max_execution_time ','0'); //匯入PHPExcel類庫,因為PHPExcel沒有用名稱空間,所以使用vendor匯入 vendor("PHPExcel.PHPExcel.IOFactory"); vendor("Excel.PHPExcel"); vendor("Excel.PHPExcel.Writer.Excel5"); vendor("Excel.PHPExcel.IOFactory.php"); //對資料進行檢驗 if(empty($data) || !is_array($data)){ die("data must be a array"); } //檢查檔名 if(empty($fileName)){ exit; } $date = date("Y_m_d",time()); $fileName .= "_{$date}.xls"; //建立PHPExcel物件 $objPHPExcel = new \PHPExcel(); //設定表頭 $key = ord("A"); foreach($headArr as $hkey => $v){ $colum = chr($key); $objPHPExcel->setActiveSheetIndex(0) ->setCellValue($colum.'1', $v); $key += 1; unset($headArr[$hkey]); } $column = 2; $objActSheet = $objPHPExcel->getActiveSheet(); foreach($data as $key => $rows){ //行寫入 $span = ord("A"); foreach($rows as $keyName=>$value){// 列寫入 $j = chr($span); //設定匯出單元格格式為文字,避免身份證號的資料被Excel改寫 $objActSheet->setCellValueExplicit($j.$column, $value); $span++; unset($rows[$keyName]); } $column++; unset($data[$key]); } $fileName = iconv("utf-8", "gb2312", $fileName); //重新命名錶 // $objPHPExcel->getActiveSheet()->setTitle('test'); //設定活動單指數到第一個表,所以Excel開啟這是第一個表 $objPHPExcel->setActiveSheetIndex(0); ob_end_clean(); ob_start(); header('Content-Type: application/vnd.ms-excel');//定義輸出的檔案型別為excel檔案 header("Content-Disposition: attachment;filename=\"$fileName\"");//定義輸出的檔名 header('Cache-Control: max-age=0');//強制每次請求直接傳送給源伺服器,而不經過本地快取版本的校驗。 $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007'); $objWriter->save('php://output'); //檔案通過瀏覽器下載 exit; }
3.呼叫實現,此處用的是thinkPHP。如框架不同,請自行修改語法。
public function exportexcel(){ $sqlWhere = session('H5OPERATIONLOG_TIME_WHERE'); $data = M(self::T_TABLE)->where('create_time'.$sqlWhere)->select();//匯出資料 $filename="XXXXX";//匯出檔名 $headArr=array("XXX","XXX1","XXX2");//資料頭 getExcel($filename,$headArr,$data); } <td><a href="你的exportexcel訪問路徑"><input type="button" value="匯出Excel"></input></a></td>
4.原本以為這樣完事了。(事實上這樣也算完事了)但是後來覺得,貌似匯出速度有點慢。就找了時間再深入研究了一下。於是找到了另一種比較好的方法。-------匯出CSV--------
5.CSV概念
CSV是一種通用的、相對簡單的檔案格式,被使用者、商業和科學廣泛應用。最廣泛的應用是在程式之間轉移表格資料,而這些程式本身是在不相容的格式上進行操作的(往往是私有的和/或無規範的格式)。因為大量程式都支援某種CSV變體,至少是作為一種可選擇的輸入/輸出格式。
6.因為CSV通常也是使用Excel檢視。所以不可避免的,還是需要注意一些Excel的限制。
表資料限制:
Excel 2003及以下的版本。一張表最大支援65536行資料,256列。
Excel 2007-2010版本。一張表最大支援1048576行,16384列。
7.PHPExcel於CSV需要注意的地方
PHPexcel記憶體溢位:
既然資料限制在104W,那麼資料分割就資料分割唄,於是你嘗試50W一次匯入表,然而PHPexcel內部有函式報記憶體溢位錯誤,然後你就不斷的調小資料量,直到5W一次匯入你都會發現有記憶體溢位錯誤。這是為什麼呢,雖然你分割資料來匯入多個資料表,但是最後PHPexcel內部還是一次性把所有表資料放進一個變數中來建立檔案……額,這幾百萬資料一個變數儲存,你想記憶體不溢位,還真有點困難。
(後來看了一些文章發現PHPExcel也有解決方案,PHPExcel_Settings::setCacheStorageMethod方法更改緩衝方式來減小記憶體的使用)CSV輸出buffer過多:
當你用PHP原生函式putcsv()其實就使用到了輸出快取buffer,如果你把幾百萬的資料一直用這個函式輸出,會導致輸出快取太大而報錯的,因此我們每隔一定量的時候,必須進行將輸出快取中的內容取出來,設定為等待輸出狀態。具體操作是:
ob_flush();
flush();然後需要解決的就是,當資料量達到百萬以上是。不可避免的就需要面對Excel的顯示資料問題以及大量資料匯出的問題。這個問題也是我在目前為止並沒有解決的。參考文章中有說道一個方案就是使用yield生成器以及分段生成CSV檔案然後再統一打包下載。如果有大神有這方面經驗,還請不吝賜教。
8.具體實現程式碼如下,需要注意的是。匯出CSV的方法並不像PHPExcel一樣提供了轉文字格式的方法。所以這裡需要在傳入資料是做資料處理。具體方法為:在需要處理的資料後面拼接製表符:"/t"。一定要雙引號。
/**
* @param array $head 資料頭
* @param string $sql 物件
* @param string $mark csv檔名
* @param string $fileName 壓縮檔名
* @param array $dateCountArr
*/
function putCsv($head, $sql, $mark, $fileName, $dateCountArr)
{
//處理匯出Csv需要轉換為文字格式的資料
$textArr = array(
//mem_loan_excel鍵名 = $fileName
"mem_loan_excel" => array(
//對應欄位
'phone',
'id',
'time',
),
);
$Model = new \Think\Model();
//設定PHP最大單執行緒的獨立記憶體使用量
ini_set('memory_limit','128M');
set_time_limit(0);
// 輸出Excel檔案頭
header('Content-Type: application/vnd.ms-excel;charset=utf-8');
header('Content-Disposition: attachment;filename="' . $fileName . '.zip"');
header('Cache-Control: max-age=0');
// 每隔$limit行,重新整理一下輸出buffer
$limit = 2000;
// buffer計數器
$sqlLimitCount = 0;
$cnt = 0;
$fileNameArr = array();
// 逐行取出資料,不浪費記憶體
foreach ($dateCountArr as $key => $value){
$fp = fopen($mark . '_' . $value['date'] . '.csv', 'w'); //生成臨時檔案
fwrite($fp, chr(0xEF).chr(0xBB).chr(0xBF));//轉碼 防止亂碼(比如微信暱稱(亂七八糟的))
$fileNameArr[] = $mark . '_' . $value['date'] . '.csv';
// 將資料通過fputcsv寫到檔案控制程式碼
fputcsv($fp, $head);
$sqlLimitCount += $value['count'];
$sqlLimit = $sqlLimitCount - $value['count'];//每次計數開始的位置,所有資料相加 - 當前資料
$dataArr = $Model->table($sql . ' a')->limit($sqlLimit,$value['count'])->select();
foreach ($dataArr as $a) {
//加"\t"形成文字格式,原樣輸出
if($textArr[$fileName]){
foreach ($a as $key => $val){
if(in_array($key,$textArr[$fileName])){
$a[$key] = $val."\t";
}
}
}
$cnt++;
if ($limit == $cnt) {
//重新整理一下輸出buffer,防止由於資料過多造成問題
ob_flush();
flush();
$cnt = 0;
}
fputcsv($fp, $a);
}
fclose($fp); //每生成一個檔案關閉
}
//進行多個檔案壓縮
$zip = new ZipArchive();
$ZipFilename = $fileName . ".zip";
$openFile = $zip->open($ZipFilename, ZipArchive::CREATE); //開啟壓縮包
if($openFile!=true || empty($fileNameArr)){
exit("file Error");
}
foreach ($fileNameArr as $file) {
$zip->addFile($file, basename($file)); //向壓縮包中新增檔案
}
$zip->close(); //關閉壓縮包
foreach ($fileNameArr as $file) {
unlink($file); //刪除csv臨時檔案
}
//輸出壓縮檔案提供下載
header("Cache-Control: public");
header("Content-Description: File Transfer");
header('Content-disposition: attachment; filename='.basename($ZipFilename)); //檔名
header("Content-Type: application/zip"); //zip格式的
header("Content-Transfer-Encoding: binary"); //告訴瀏覽器,這是二進位制檔案
header('Content-Length: '. filesize($ZipFilename)); //告訴瀏覽器,檔案大小
ob_clean();
flush();
@readfile($ZipFilename);
unlink($ZipFilename);
exit;
}
/**
** 匯出資訊
**/
public function exportexcel(){
$sqlWhere = session('H5OPERATIONLOG_TIME_WHERE');//這裡是我這裡的條件,需要修改
$sql = M(self::T_TABLE)->where('create_time'.$sqlWhere)->buildSql();//獲取到查詢資料的sql
$Model = new \Think\Model();
//得到每一天的資料總數
$dateCountArr = $Model->table($sql . ' a')
->field('count(*) as count, DATE_FORMAT(a.create_time,"%Y-%m-%d") as date')
->group('DATE_FORMAT(a.create_time,"%Y-%m-%d")')
->select();
$filename="h5_operation_log_excel".time();
$mark="h5_operation_log_info";
$headArr=array("id","使用者編號","資料唯一編號","資料名稱","操作","操作者IP","操作批次","操作時間");
putCsv($headArr,$sql,$mark,$filename,$dateCountArr);
}
<td><a href="你的exportexcel訪問路徑"><input type="button" value="匯出Excel"></input></a></td>
//此地方的出處找不到了,希望原作看到不要揍我。狗頭保命ing......
9.CSV在Excel中展示會有點小問題
解決方案:SYLK 檔案時一個文字檔案,開頭的為“ID”或“ID_XXXX”(其中XXXX是文字字串)。
SYLK 檔案的第一個的記錄是在 ID_Number 記錄的。
Excel 將識別該文字在文字檔案開頭時, 它會將該檔案解釋為 SYLK 檔案。
Excel 將嘗試從該 SYLK 格式轉換該檔案,但不能這樣做,因為"ID"字元後不有任何有效的 SYLK 程式碼。
因為 Excel 不能轉換該檔案,您收到錯誤訊息。當您開啟一個文字檔案、 CSV 檔案和檔案的前兩個字元是大寫字母"I","D"時,會發生此問題。
例如文字檔案可能包含以下文字:
ID, STATUS 123, open 456, closed
如果前兩個字母小寫"i"和"d"不會發生此問題的 。參考:https://www.cnblogs.com/A2008A/archive/201...
至此也就完成了。原本匯出1W+條資料,PHPExcel需要8秒。現在換成CSV方法只需要兩秒,也算能滿足日常的運營需求了。當然,可以優化的地方還有很多。歡迎指正修改。