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 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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训练数据并当服务器共享给他人