您好,登錄后才能下訂單哦!
本篇內容介紹了“php怎么大批量導出excel數據”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
在平時生活或其他時候,我們可能會需要大批量導出excel數據,所以將這次的優化過程發布出來,希望對有需要的同學啟到一定的幫助。
項目后臺有導出幾 w 條數據生成 excel 的功能,剛好前同事的方法直接報內存溢出錯誤,
所以將這次的優化過程發布出來,希望對有需要的同學啟到一定的幫助
異步生成數據且真實進度條反饋進度
2.進度完成,前端 js 跳轉到下載地址
3.生成 csv 文件代替 excel , 3.5w 條數據文件大小 8M
[2020-09-27 09:21:13] local.ERROR: Allowed memory size of 536870912 bytes exhausted (tried to allocate 8192 bytes) {"userId":1,"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalErrorException(code: 1): Allowed memory size of 536870912 bytes exhausted (tried to allocate 8192 bytes) at /Users/****/WebRoot/ValeSite/****/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php:879)
$list = Good::with(['good_standard', 'good_standard.default_picture', 'good_standard.brand']) ...... ->selectRaw('goods.*')~~~~ ->get(); #內存溢出點 1, 該 orm 返回數據量為 3.5w 行數據 ...... ~~~~ $list = $this->goodsRepository->batchGetFullGoodsScope($list); foreach ($list as $item) { $cell = []; ..... //沒條數組共 30 個元素 $cellData[] = $cell; } # 內存溢出點 2 ,生成需要的數據,3w + 條數據時,內存消耗大概在 110M + ..... Excel::create(...) #內存溢出點 3 , Maatwebsite/Laravel-Excel庫大批量生成也會內存溢出 # 和直觀的代碼處理流,該代碼在小數據量時無問題
orm 取數據優化 (mysql)
對已獲取的 orm 數據二次處理后 , 數據存儲優化
導出 excel 時, 導出優化
后續所有的代碼功能都是圍繞該 3 個方向來處理
前端 ajax 發送 excel 導出請求 ->后端結束請求且計算數據總條數, 按一定倍數拆分成多次 job 生成數據 ->后端多個進程異步執行 job 隊列任務,按批次生成數據 (每次執行計數一次,數據寫入 redis) ->前端 ajax 輪詢獲取總次數和當前已執行次數 (計算出進度條 ) ->前端獲 ajax 輪詢結果總次數 = 已執行次數 ~~~~(進度100%),跳轉到下載地址 ->后端 redis 取數據,渲染生成 csv 文件(下載完成)
前端代碼: jquery + Bootstrap
# 進度條樣式,可在 bootstrap 找到 <section class="panel" id="export_loading_box"> <p class="panel-body m-b-10" style="display: none"> <p class="text-muted"> 數據導出中 ..... </p> <p class="progress progress-striped active"> <p class="progress-bar progress-bar-info" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width: 0%"> 0% </p> </p> </p> </section>
$(function () { $('.down-list').click(function () { let formName = $(this).attr('data-form') let data = $('#' + formName).serialize(); OE.params.url = $(this).attr('data-url'); OE.handle(data); }); }) //商品導出 JS 控件 let OE = window.OE || {} OE = { params: { ifRun: 0, url: '', cachePre: '', }, # 1. 前端 ajax 發送 excel導出請求 handle: function (formData) { if (OE.params.ifRun) { return false; } OE.params.ifRun = 1; OE.rateShow(100, 0); $('#export_loading_box').find('.panel-body').show(); $.getJSON(OE.params.url, formData + '&run=1', function (data) { if (200 == data.status) { OE.params.cachePre = data.cachePre; //請求成功, 渲染進度條 OE.init(); } else { OE.showAlert(false, data.msg); } }) }, # 4. ajax 輪詢渲染進度條 init: function () { let t = setInterval(function () { $.getJSON(OE.params.url, "get_run=1&cache_pre="+OE.params.cachePre, function (data) { OE.rateShow(data.total, data.run); if (200 == data.status && data.total == data.run) { clearInterval(t); OE.params.ifRun = 0; OE.showAlert(true); //跳轉下載 excel window.location.href = OE.params.url+'?cache_pre='+OE.params.cachePre; return; } }); }, 2000); }, showAlert: function (success, msg) { if (success) { html = '<p class="alert alert-success fade in">' + ' <button data-dismiss="alert" class="close close-sm" type="button">' + ' <i class="fa fa-times"></i>' + ' </button>' + ' <strong>導出成功</strong> 數據下載中' + ' </p>'; } $('#export_loading_box').append(html); $('#export_loading_box').find('.panel-body').hide(); }, # 進度條計算 rateShow: function (total, run) { let width = ((run / total) * 100).toFixed(0); $('#export_loading_box').find('.progress-bar').css('width', width + '%'); $('#export_loading_box').find('.progress-bar').text(width + '% '); } }
后端代碼 :
2.后端總入口
// 前端第一次請求觸發代碼實現 $listOrm = self::getGoodOrm(); //求總,初始化 job 數據 $total = $listOrm->count(); for ($page = 0; $page <= ($totalPage - 1); $page++) { //創建子隊列 CreateExportGoodData::dispatch($requestData, $page, $authAdmin->id, $cachePre) ->onQueue(self::JOB_QUEUE); }
3.后端異步子隊列執行任務 (和前端無關)
self::$requestData = $requestData; $listOrm = self::getGoodOrm(); $list = $listOrm->offset($page * self::PAGE_NUM) ->limit(self::PAGE_NUM) ->orderByDesc('goods.id') ->get(); //數據清洗過程 ...... //執行次數遞增 1 Redis::incr(self::GE_RUN_KEY . $cachePre . ':' . $adminId); //清洗后的數據壓入 redis 列表 Redis::lpush(self::GE_DATA_KEY . $cachePre . ':' . $adminId, serialize($data));
4.后端實現前端 ajax 輪詢執行進度反饋代碼實現
$total = Redis::get(GoodsExportRepository::GE_TOTAL_KEY. $cachePre. ':'. $authAdmin->id); $run = Redis::get(GoodsExportRepository::GE_RUN_KEY. $cachePre. ':'. $authAdmin->id); if ($request->input('get_run')) { //前端 ajax 輪詢獲取同步已運行隊列數 return ['status' => 200, 'total' => $total, 'run' => $run]; }
6.后端實現前端 excel 下載代碼實現
$fileName = "商品導出" . date('Y-m-d'); header('Content-Type: application/vnd.ms-excel'); header('Content-Disposition: attachment;filename="' . $fileName . '.csv"'); header('Cache-Control: max-age=0'); //開啟預輸出流 $fp = fopen('php://output', 'a'); //輸出商品列表數據 while (true) { //核心1 從 redis 列表里依次取數據 $data = Redis::rpop(self::GE_DATA_KEY . $cachePre . ':' . $adminId); if (!$data) { // redis 列表數據為空,結束 while 循環 break; } //核心2 ob_flush(); //取出 $fb 輸出流 存入 buffer 內數據 flush(); //直接渲染至 http 數據流至瀏覽器 $data = unserialize($data); foreach ($data as $row) { foreach ($row as $key => $value) { if (is_string($value)) { $row[$key] = iconv('utf-8', 'gbk//IGNORE', $value); } } fputcsv($fp, $row); } } fclose($fp); //必須 exit 阻止框架繼續輸出 exit();
總結:
后端隊列任務生產數據錄入 redis list
前端 ajax 輪詢獲取執行情況
前端 獲取后端已將所有 隊列任務執行, 跳轉至下載地址
下載地址取 redis 內數據,渲染成 csv 文件
優點:
異步多隊列進程執行,效率高
前端可實時獲取執行進度, 用戶體驗好
缺點:
ajax 輪詢占用正常用戶請求資源,該方案只適合后臺實現
代碼復雜, 施工人員需一定的 laravel 隊列知識和 前端知識儲備;對自己把握不足的同學可直接看第二種解決方案
設置 php 腳本時間 set_time_limit(0);
orm 依次獲取數據,對獲取的數據直接清洗后直接寫入 輸出流, 輸出至瀏覽器
代碼實現:
set_time_limit(0) //直接輸出頭部聲明 $fileName = "商品導出" . date('Y-m-d'); header('Content-Type: application/vnd.ms-excel'); header('Content-Disposition: attachment;filename="' . $fileName . '.csv"'); header('Cache-Control: max-age=0'); //開啟輸出流 $fp = fopen('php://output', 'a'); // while 循環取數據 $page = 0; while (true) { $listOrm = self::getGoodOrm(); $list = $listOrm->offset($page * self::PAGE_NUM) ->limit(self::PAGE_NUM) ->orderByDesc('goods.id') ->get(); if ($list->isEmpty()) { //無數據時退出 while 循環 break; } //數據清洗 $data = ..... //直接將清洗后的 $data 數據寫入輸出流 foreach ($data as $row) { foreach ($row as $key => $value) { if (is_string($value)) { $row[$key] = iconv('utf-8', 'gbk//IGNORE', $value); } } fputcsv($fp, $row); } //輸出至瀏覽器 ob_flush(); flush(); } fclose($fp); exit();
總結
優點: 代碼流程簡單,開發難度低
缺點: 前端體驗差, ( 數據單進程獲取,效率低) 下載等待耗時長 )
不管是異步還是同步, 實際思路都是分頁獲取數據, 對分頁獲取的數據進行處理; 都有用到核心方法:
fopen('php://output', 'a') , ob_flush(), flush();
“php怎么大批量導出excel數據”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。