本篇內(nèi)容介紹了“php怎么大批量導(dǎo)出excel數(shù)據(jù)”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)建站主營伊州網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,成都App制作,伊州h5重慶小程序開發(fā)搭建,伊州網(wǎng)站營銷推廣歡迎伊州等地區(qū)企業(yè)咨詢
在平時生活或其他時候,我們可能會需要大批量導(dǎo)出excel數(shù)據(jù),所以將這次的優(yōu)化過程發(fā)布出來,希望對有需要的同學(xué)啟到一定的幫助。
項目后臺有導(dǎo)出幾 w 條數(shù)據(jù)生成 excel 的功能,剛好前同事的方法直接報內(nèi)存溢出錯誤,
所以將這次的優(yōu)化過程發(fā)布出來,希望對有需要的同學(xué)啟到一定的幫助

異步生成數(shù)據(jù)且真實進(jìn)度條反饋進(jìn)度

2.進(jìn)度完成,前端 js 跳轉(zhuǎn)到下載地址

3.生成 csv 文件代替 excel , 3.5w 條數(shù)據(jù)文件大小 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();
#內(nèi)存溢出點(diǎn) 1, 該 orm 返回數(shù)據(jù)量為 3.5w 行數(shù)據(jù)
...... ~~~~
$list = $this->goodsRepository->batchGetFullGoodsScope($list);
foreach ($list as $item) {
$cell = [];
.....
//沒條數(shù)組共 30 個元素
$cellData[] = $cell;
}
# 內(nèi)存溢出點(diǎn) 2 ,生成需要的數(shù)據(jù),3w + 條數(shù)據(jù)時,內(nèi)存消耗大概在 110M +
.....
Excel::create(...)
#內(nèi)存溢出點(diǎn) 3 , Maatwebsite/Laravel-Excel庫大批量生成也會內(nèi)存溢出
# 和直觀的代碼處理流,該代碼在小數(shù)據(jù)量時無問題orm 取數(shù)據(jù)優(yōu)化 (MySQL)
對已獲取的 orm 數(shù)據(jù)二次處理后 , 數(shù)據(jù)存儲優(yōu)化
導(dǎo)出 excel 時, 導(dǎo)出優(yōu)化
后續(xù)所有的代碼功能都是圍繞該 3 個方向來處理
前端 ajax 發(fā)送 excel 導(dǎo)出請求 ->后端結(jié)束請求且計算數(shù)據(jù)總條數(shù), 按一定倍數(shù)拆分成多次 job 生成數(shù)據(jù) ->后端多個進(jìn)程異步執(zhí)行 job 隊列任務(wù),按批次生成數(shù)據(jù) (每次執(zhí)行計數(shù)一次,數(shù)據(jù)寫入 redis) ->前端 ajax 輪詢獲取總次數(shù)和當(dāng)前已執(zhí)行次數(shù) (計算出進(jìn)度條 ) ->前端獲 ajax 輪詢結(jié)果總次數(shù) = 已執(zhí)行次數(shù) ~~~~(進(jìn)度100%),跳轉(zhuǎn)到下載地址 ->后端 redis 取數(shù)據(jù),渲染生成 csv 文件(下載完成)
前端代碼: jquery + Bootstrap
# 進(jìn)度條樣式,可在 bootstrap 找到 <section class="panel" id="export_loading_box"> <p class="panel-body m-b-10" style="display: none"> <p class="text-muted"> 數(shù)據(jù)導(dǎo)出中 ..... </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);
});
})
//商品導(dǎo)出 JS 控件
let OE = window.OE || {}
OE = {
params: {
ifRun: 0,
url: '',
cachePre: '',
},
# 1. 前端 ajax 發(fā)送 excel導(dǎo)出請求
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;
//請求成功, 渲染進(jìn)度條
OE.init();
} else {
OE.showAlert(false, data.msg);
}
})
},
# 4. ajax 輪詢渲染進(jìn)度條
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);
//跳轉(zhuǎn)下載 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>導(dǎo)出成功</strong> 數(shù)據(jù)下載中' +
' </p>';
}
$('#export_loading_box').append(html);
$('#export_loading_box').find('.panel-body').hide();
},
# 進(jìn)度條計算
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.后端總?cè)肟?/p>
// 前端第一次請求觸發(fā)代碼實現(xiàn)
$listOrm = self::getGoodOrm();
//求總,初始化 job 數(shù)據(jù)
$total = $listOrm->count();
for ($page = 0; $page <= ($totalPage - 1); $page++) {
//創(chuàng)建子隊列
CreateExportGoodData::dispatch($requestData, $page, $authAdmin->id, $cachePre)
->onQueue(self::JOB_QUEUE);
}3.后端異步子隊列執(zhí)行任務(wù) (和前端無關(guān))
self::$requestData = $requestData;
$listOrm = self::getGoodOrm();
$list = $listOrm->offset($page * self::PAGE_NUM)
->limit(self::PAGE_NUM)
->orderByDesc('goods.id')
->get();
//數(shù)據(jù)清洗過程
......
//執(zhí)行次數(shù)遞增 1
Redis::incr(self::GE_RUN_KEY . $cachePre . ':' . $adminId);
//清洗后的數(shù)據(jù)壓入 redis 列表
Redis::lpush(self::GE_DATA_KEY . $cachePre . ':' . $adminId, serialize($data));4.后端實現(xiàn)前端 ajax 輪詢執(zhí)行進(jìn)度反饋代碼實現(xiàn)
$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 輪詢獲取同步已運(yùn)行隊列數(shù)
return ['status' => 200, 'total' => $total, 'run' => $run];
}6.后端實現(xiàn)前端 excel 下載代碼實現(xiàn)
$fileName = "商品導(dǎo)出" . date('Y-m-d');
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="' . $fileName . '.csv"');
header('Cache-Control: max-age=0');
//開啟預(yù)輸出流
$fp = fopen('php://output', 'a');
//輸出商品列表數(shù)據(jù)
while (true) {
//核心1 從 redis 列表里依次取數(shù)據(jù)
$data = Redis::rpop(self::GE_DATA_KEY . $cachePre . ':' . $adminId);
if (!$data) {
// redis 列表數(shù)據(jù)為空,結(jié)束 while 循環(huán)
break;
}
//核心2
ob_flush(); //取出 $fb 輸出流 存入 buffer 內(nèi)數(shù)據(jù)
flush(); //直接渲染至 http 數(shù)據(jù)流至瀏覽器
$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 阻止框架繼續(xù)輸出
exit();總結(jié):
后端隊列任務(wù)生產(chǎn)數(shù)據(jù)錄入 redis list
前端 ajax 輪詢獲取執(zhí)行情況
前端 獲取后端已將所有 隊列任務(wù)執(zhí)行, 跳轉(zhuǎn)至下載地址
下載地址取 redis 內(nèi)數(shù)據(jù),渲染成 csv 文件
優(yōu)點(diǎn):
異步多隊列進(jìn)程執(zhí)行,效率高
前端可實時獲取執(zhí)行進(jìn)度, 用戶體驗好
缺點(diǎn):
ajax 輪詢占用正常用戶請求資源,該方案只適合后臺實現(xiàn)
代碼復(fù)雜, 施工人員需一定的 laravel 隊列知識和 前端知識儲備;對自己把握不足的同學(xué)可直接看第二種解決方案
設(shè)置 php 腳本時間 set_time_limit(0);
orm 依次獲取數(shù)據(jù),對獲取的數(shù)據(jù)直接清洗后直接寫入 輸出流, 輸出至瀏覽器
代碼實現(xiàn):
set_time_limit(0)
//直接輸出頭部聲明
$fileName = "商品導(dǎo)出" . 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 循環(huán)取數(shù)據(jù)
$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()) {
//無數(shù)據(jù)時退出 while 循環(huán)
break;
}
//數(shù)據(jù)清洗
$data = .....
//直接將清洗后的 $data 數(shù)據(jù)寫入輸出流
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();總結(jié)
優(yōu)點(diǎn): 代碼流程簡單,開發(fā)難度低
缺點(diǎn): 前端體驗差, ( 數(shù)據(jù)單進(jìn)程獲取,效率低) 下載等待耗時長 )
不管是異步還是同步, 實際思路都是分頁獲取數(shù)據(jù), 對分頁獲取的數(shù)據(jù)進(jìn)行處理; 都有用到核心方法:
fopen('php://output', 'a') ,
ob_flush(),
flush();“php怎么大批量導(dǎo)出excel數(shù)據(jù)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
分享名稱:php怎么大批量導(dǎo)出excel數(shù)據(jù)
文章起源:http://www.chinadenli.net/article22/igjojc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、網(wǎng)站設(shè)計公司、定制網(wǎng)站、移動網(wǎng)站建設(shè)、網(wǎng)站營銷、搜索引擎優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)