1. 程式人生 > 實用技巧 >PHPExecl匯出大量資料卡頓問題解決(Laravel實現)

PHPExecl匯出大量資料卡頓問題解決(Laravel實現)

PHPExecl匯出資料遇到複雜查詢+幾萬條資料的情況瀏覽器就容易卡死,下面分享一下解決思路和過程:

先說解決思路:

1、優化查詢、資料分批寫入檔案

2、先將表格下載到伺服器,瀏覽器輪詢伺服器,表格生成完成再下載

3、表格使用佇列匯出

上程式碼

瀏覽器端:

1、點選匯出按鈕,請求exportUser介面,將檔案加入佇列,返回唯一key

2、使用key輪詢請求getURL介面,檔案生成成功則返回檔案路徑

3、下載檔案

 1  <div class="form-group col-sm-1" >
 2       <a class="btn btn-block btn-success btn-bg importExcel" href="javascript:void(0);">匯出</a>
 3  
</div> 4 5 6 <script src="{{ asset("/js/layer/layer.js")}}"></script> 7 8 $('.importExcel').on('click', function () { 9 var data = { 10 'status': $("#status_val option:selected").val(), 11 'start_time': $("input[name='start_time']").val(), 12
'user_name': $("input[name='user_name']").val(), 13 'end_time': $("input[name='end_time']").val() 14 }; 15 16 $.post('/admin/user/exportUser', data, function (res) { 17 if (res.code == 200) { 18
var params={ 19 'key':res.data 20 } 21 var ii = layer.load(); 22 var aaa = setInterval(() => { 23 $.post('/admin/user/getURL', params, function (reslut) { 24 var url=reslut.data.url 25 if(url){ 26 clearInterval(aaa) 27 layer.close(ii); 28 window.open(url); 29 } 30 }) 31 }, 1000) 32 } 33 }); 34 35 })

伺服器端:

1、exportUser介面



useApp\Jobs\exportList;



/*
* * 匯出列表 */ public function exportUser(Request $request) { $query = $request->all(); $str=str_random(10).time(); Redis::set($str,''); $arr=[ 'query'=>$query, 'type'=>1, 'key'=>$str ]; $this->dispatch(new exportList($arr)); return success($str); }

2、生成佇列任務

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Services\UserService;

use Illuminate\Support\Facades\Log;


class exportList implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected  $data;
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($data)
    {
        $this->data = $data;
    
    }

    /**
     * Execute the job.   處理列隊資料
     *
     * @return void
     */
    public function handle(UserService,$userService)
    {
        if($this->data['type']==1){  //使用者匯出
           $userService->exportUser($this->data['key'],$this->data['query']); 
      }
elseif($this->data['type']==2){ //訂單匯出 xxxxxx
     }
    }
   }

3、分批匯出,其中getExportList方法取資料,newExportList渲染資料並匯出

  /**
     * 批量匯出
     *
     * @param [type] $key
     * @param [type] $query
     * @return void
     */
    public function exportUser($key,$query)
    {
        $i = 0;
        $file_name = rand(1000, 9999);
        while (true) {
            $list = $this->getExportList($query, $i); //分頁取資料
            if(is_null($list)){
                $list=[];
            }
            $res=$this->newExportList($key,$list, $file_name, ($i * env('EXPORT_NUM')) + 1);
            if (count($list) < env('EXPORT_NUM')) {
                return $res;
            }
            $i += 1;
        }
    }

4、getExportList方法,取資料

   /**
     * 獲取匯出的資料
     * @param $params
     * @return mixed
     */
    public function getExportList($query, $page)
    {
        $list = $this->UserRepositories->getListAll($query, $page);
        if (!$list->isEmpty()) {
            $list = $list->toArray();
            $data = [];
            foreach ($list as $key => $value) {
                $data[$key]['A'] = $value['user_name'];
                $data[$key]['B'] = "\t" . $value['phone'];
                $data[$key]['C'] = $value['created_at'];
            }
            return $data;
        }
        return [];
    }

5、newExportList 渲染資料方法

  /**
     * 匯出資料
     * @param $list
     */
    public function newExportList($key,$list, $file_name, $total)
    {
        $arr = ['A' => '使用者名稱稱', 'B' => '手機號', 'C' => '註冊時間'];
        if ($total == 1) {
            array_unshift($list, $arr); //插入表頭
        } else {
            $total += 1;
        }
        $title = date('Y-m-d', time()) . '_' . $file_name . '.csv';
        $width = array('A' => 45, 'B' => 35, 'C' => 35);//列寬
        $res=exportExcelForURL($title, $list, $width, $total);
        if($res){
            Redis::set($key,$res);
            Redis::EXPIRE($key,600);
        }
        return ['code' => 200,'url'=>$res]; 
    }

6、exportExcelForURL匯出表格方法

/**
 * 匯出excel 分批儲存
 * @param $title
 * @param $list
 * @param $width
 */
function exportExcelForURL($title, $list, $width, $total = 0)
{
	$file_name = env('EXPORT_DIR') . '/' . $title;
	$name=config('config.EXCEL_ROUTE'). $title;
    if (!file_exists($file_name)) {
        $myfile = fopen($file_name, "w");
        fclose($myfile);
    }
    $excel = \Excel::load($file_name);
    $sheet = $excel->getSheet(0);
    foreach ($list as $row_key => $row) {
        foreach ($row as $key => $value) {
            \Log::info($key . ($row_key + $total));
            $res = $sheet->setCellValue($key . ($row_key + $total), $value);
        }
	}
	
	$excel->store('csv', env('EXPORT_DIR'));
    if (count($list) < env('EXPORT_NUM')) {
        return $name;
    }
	return false;
}

7、獲取下載連結

      /**
	 * 獲取下載連結
	 *
	 * @param Request $request
	 * @return void
	 */
	public function getURL(Request $request)
	{
		$key=$request->key;
		$data['url']=Redis::get($key);
		return success($data);
	}    

8、開啟佇列(推薦配置Supervisor)

php artisan queue:work --tries=3