当前位置: 首页 > 后端技术 > PHP

phplaravel批量导出excel

时间:2023-03-29 20:57:41 PHP

前言由于php的语言特性,在laravel或其他框架中使用php生成大量excel数据一直是个难点;项目后台有导出几w条数据生成excel的功能,就像以前同事的方法直接报内存溢出错误,所以这次把优化过程放出来,希望能给同学们一些帮助需要。先看优化后的效果:异步生成数据和真实进度条反馈进度2.进度完成,前端js跳转到下载地址3.生成csv文件代替excel,3.5w条数据文件大小8M原始代码错误消息[2020-09-2709:21:13]local.ERROR:Allowedmemorysizeof536870912bytesexhausted(triedtoallocate8192bytes){"userId":1,"exception":"object](Symfony\\Component\\Debug\\Exception\\FatalErrorException(code:1):允许的内存大小为536870912字节耗尽(试图分配8192字节)在/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返回数据量为3.5w行数据...~~~~$list=$this->goodsRepository->batchGetFullGoodsScope($list);foreach($listas$item){$cell=[];.....//一共没有数组30个元素$cellData[]=$cell;}#内存溢出点2,生成需要的数据,当3w+条数据时,内存消耗约为110M+...Excel::create(...)#内存溢出点3、Maatwebsite/Laravel-Excel库大量生成也会导致内存溢出#和直观的代码处理流程,这段代码在数据量小的时候没有问题解决思路经过数据二次处理,当数据存储优化和导出到excel,导出优化后的所有后续代码功能都围绕这三个方向进行处理。方案一(异步生成数据)思路分析:前端ajax发送excel导出请求->后端请求结束并计算数据条总数,按照一定倍数拆分成多个job生成数据->后端多个进程异步执行job队列任务,批量生成数据(每次执行算一次,数据写入redis)->4.前端ajax轮询获取总次数和当前执行次数(计算进度条)->5.前端ajax轮询结果总次数=执行次数~~~~(进度100%),跳转到下载地址->6.后端redis取数据,渲染生成csv文件(下载完成)代码实现:前端代码:jquery+Bootstrap#进度条样式,可以在bootstrap数据导出...

0%
$(function(){$('.down-list').click(function(){letformName=$(this).attr('data-form')letdata=$('#'+formName).serialize();OE.params.url=$(this).attr('data-url');OE。handle(data);});})//商品导出JS控件letOE=window.OE||{}OE={params:{ifRun:0,url:'',cachePre:'',},#1.前端ajax发送excel导出请求句柄:function(formData){if(OE.params.ifRun){returnfalse;}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.showA警报(假,data.msg);}})},#4.ajax轮询缓存进度条init:function(){lett=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);//跳转下载excelwindow.location.href=OE.params.url+'?cache_pre='+OE.params.cachePre;return;}});},2000);},showAlert:function(success,msg){if(success){html=''+''+''+'导出成功数据下载'+'
';$('#export_loading_box').append(html);$('#export_loading_box').find('.panel-body').hide();},#进度条计算rateShow:function(total,run){letwidth=((run/total)*100).toFixed(0);$('#export_loading_box').find('.progress-bar').css('width',width+'%');$('#export_loading_box').find('.progress-bar').文字(宽度+'%');}}后端代码:2.后端总计入口//前端第一次请求触发代码实现$listOrm=self::getGoodOrm();//总计,初始化作业数据$total=$listOrm->count();for($page=0;$page<=($totalPage-1);$page++){//创建一个子队列CreateExportGoodData::dispatch($requestData,$page,$authAdmin->id,$cachePre)->onQueue(self::JOB_QUEUE);}3。后端异步子队列执??行任务(与前端无关)self::$requestData=$requestData;$listOrm=self::getGoodOrm();$list=$listOrm->offset($page*self::PAGE_NUM)->limit(self::PAGE_NUM)->orderByDesc('goods.id')->get();//数据清洗过程……//执行次数增加1Redis::incr(self::GE_RUN_KEY.$cachePre.':'.$adminId);//将清洗后的数据推入redis列表Redis::lpush(self::GE_DATA_KEY.$cachePre.':'.$adminId,serialize($data));4.后端实现前端ajax轮询执行进度反馈代码实现$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轮询获取同步运行队列Numberreturn['status'=>200,'total'=>$total,'run'=>$run];}6、后端实现前端excel下载代码实现$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(true){//核心1从redis列表中顺序取数据$data=Redis::rpop(self::GE_DATA_KEY.$缓存前。':'。$adminId);if(!$data){//redis列表数据为空,结束while循环break;}//核心2ob_flush();//取出$fb输出流并将其存储在缓冲区中的数据中flush();//直接渲染到http数据流到浏览器$data=unserialize($data);foreach($dataas$row){foreach($rowas$key=>$value){if(is_string($value)){$row[$key]=iconv('utf-8','gbk//忽略',$值);}}fputcsv($fp,$row);}}fclose($fp);//必须退出,防止框架继续输出exit();至此完成excel的异步导出总结:后端队列任务生产数据入口redis列表前端ajax轮询获取执行状态前端获取后端已执行完所有队列任务,跳转到下载地址下载地址取redis中的数据渲染成csv文件优点:异步多队列进程执行,效率高前端可以实时获取执行进度,用户体验好缺点:Ajax轮询占用正常用户请求资源,该方案仅适用于后台复杂代码实现,且施工人员需要有一定的laravel队列知识和前端知识储备;对自己不够了解的同学可以直接看方案二(同步生成数据)思路分析:设置php脚本时间set_time_limit(0);orm顺序获取数据,直接清洗获取到的数据直接写入输出流,输出到浏览器代码实现:set_time_limit(0)//直接输出header语句$fileName="CommodityExport"。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循环获取数据$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()){//没有数据时退出while循环break;}//数据清洗$data=.....//直接将清洗后的$data数据写入输出流foreach($dataas$row){foreach($rowas$key=>$value){if(is_string($value)){$row[$key]=iconv('utf-8','gbk//IGNORE',$value);}}fputcsv($fp,$row);}//输出到浏览器ob_flush();冲洗();}fclose($fp);出口();优点总结:代码流程简单,开发难度低缺点:前端体验差,(单进程获取数据,效率低)下载耗时长)不管是异步还是同步,实际思路都是获取数据通过分页,对分页得到的数据进行处理;核心方法很有用:fopen('php://output','a'),ob_flush(),flush();以上三种方法是实现大数据导出的关键端~~~~