久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      php如何大批量導出excel數(shù)據(jù)

      在平時生活或其他時候,我們可能會需要大批量導出excel數(shù)據(jù),所以將這次的優(yōu)化過程發(fā)布出來,希望對有需要的同學啟到一定的幫助。

      php如何大批量導出excel數(shù)據(jù)

      項目后臺有導出幾 w 條數(shù)據(jù)生成 excel 的功能,剛好前同事的方法直接報內存溢出錯誤,
      所以將這次的優(yōu)化過程發(fā)布出來,希望對有需要的同學啟到一定的幫助

      先看優(yōu)化后效果:

      php如何大批量導出excel數(shù)據(jù)

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

      php如何大批量導出excel數(shù)據(jù)

      2.進度完成,前端 js 跳轉到下載地址

      php如何大批量導出excel數(shù)據(jù)

      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(); #內存溢出點 1, 該 orm 返回數(shù)據(jù)量為 3.5w 行數(shù)據(jù) ...... ~~~~ $list = $this->goodsRepository->batchGetFullGoodsScope($list); foreach ($list as $item) {    $cell = [];    .....    //沒條數(shù)組共 30 個元素       $cellData[] = $cell; } # 內存溢出點 2 ,生成需要的數(shù)據(jù),3w + 條數(shù)據(jù)時,內存消耗大概在 110M +  ..... Excel::create(...) #內存溢出點 3 , Maatwebsite/Laravel-Excel庫大批量生成也會內存溢出  # 和直觀的代碼處理流,該代碼在小數(shù)據(jù)量時無問題

      解決思路分析

      1. orm 取數(shù)據(jù)優(yōu)化 (mysql)

      2. 對已獲取的 orm 數(shù)據(jù)二次處理后 , 數(shù)據(jù)存儲優(yōu)化

      3. 導出 excel 時, 導出優(yōu)化

      后續(xù)所有的代碼功能都是圍繞該 3 個方向來處理


      方案 1 (異步生成數(shù)據(jù) )

      思路分析:

      前端 ajax 發(fā)送 excel 導出請求 ->后端結束請求且計算數(shù)據(jù)總條數(shù), 按一定倍數(shù)拆分成多次 job 生成數(shù)據(jù) ->后端多個進程異步執(zhí)行 job 隊列任務,按批次生成數(shù)據(jù) (每次執(zhí)行計數(shù)一次,數(shù)據(jù)寫入 redis) ->前端 ajax 輪詢獲取總次數(shù)和當前已執(zhí)行次數(shù) (計算出進度條 ) ->前端獲 ajax 輪詢結果總次數(shù) = 已執(zhí)行次數(shù) ~~~~(進度100%),跳轉到下載地址 ->后端 redis 取數(shù)據(jù),渲染生成 csv 文件(下載完成)

      代碼實現(xiàn):

      前端代碼: jquery + Bootstrap

      # 進度條樣式,可在 bootstrap 找到 <section class="panel" id="export_loading_box">     <p class="panel-body m-b-10" style="display: none">         <p class="text-muted">             數(shù)據(jù)導出中 .....         </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 發(fā)送 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> 數(shù)據(jù)下載中' +                 ' </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.后端總入口

      // 前端第一次請求觸發(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í)行任務 (和前端無關)

      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í)行進度反饋代碼實現(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 輪詢獲取同步已運行隊列數(shù)  return ['status' => 200, 'total' => $total, 'run' => $run]; }

      6.后端實現(xiàn)前端 excel 下載代碼實現(xiàn)

      $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');  //輸出商品列表數(shù)據(jù) while (true) {     //核心1 從 redis 列表里依次取數(shù)據(jù)     $data = Redis::rpop(self::GE_DATA_KEY . $cachePre . ':' . $adminId);     if (!$data) {         // redis 列表數(shù)據(jù)為空,結束 while 循環(huán)         break;     }      //核心2      ob_flush(); //取出 $fb 輸出流 存入 buffer 內數(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();

      至此,異步導出 excel 已完成

      總結:

      • 后端隊列任務生產(chǎn)數(shù)據(jù)錄入 redis list

      • 前端 ajax 輪詢獲取執(zhí)行情況

      • 前端 獲取后端已將所有 隊列任務執(zhí)行, 跳轉至下載地址

      • 下載地址取 redis 內數(shù)據(jù),渲染成 csv 文件

      優(yōu)點:

      • 異步多隊列進程執(zhí)行,效率高

      • 前端可實時獲取執(zhí)行進度, 用戶體驗好

      缺點:

      • ajax 輪詢占用正常用戶請求資源,該方案只適合后臺實現(xiàn)

      • 代碼復雜, 施工人員需一定的 laravel 隊列知識和 前端知識儲備;對自己把握不足的同學可直接看第二種解決方案


      方案 2 (同步生成數(shù)據(jù) )

      思路分析:
      • 設置 php 腳本時間 set_time_limit(0);

      • orm 依次獲取數(shù)據(jù),對獲取的數(shù)據(jù)直接清洗后直接寫入 輸出流, 輸出至瀏覽器

      代碼實現(xiàn):

      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 循環(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();

      總結

      • 優(yōu)點: 代碼流程簡單,開發(fā)難度低

      • 缺點: 前端體驗差, ( 數(shù)據(jù)單進程獲取,效率低) 下載等待耗時長 )


      不管是異步還是同步, 實際思路都是分頁獲取數(shù)據(jù), 對分頁獲取的數(shù)據(jù)進行處理; 都有用到核心方法:

      fopen('php://output', 'a') ,  ob_flush(),  flush();

      推薦學習:php視頻教程

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號