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接口

复制代码

 use App\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方法,取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
  * 获取导出的数据
  * @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 导出表格方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * 导出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、获取下载链接

1
2
3
4
5
6
7
8
9
10
11
12
  /**
 * 获取下载链接
 *
 * @param Request $request
 * @return void
 */
public function getURL(Request $request)
{
    $key=$request->key;
    $data['url']=Redis::get($key);
    return success($data);
}

8、开启队列(推荐配置Supervisor)

1
php artisan queue:work --tries=3

 

posted @   郁冬  阅读(2836)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示