thinkphp6 + redis实现大数据导出excel超时或内存溢出问题解决方案

redis下载安装(window版本)
参考地址:https://blog.csdn.net/Ci1693840306/article/details/144214215

php安装redis扩展
参考链接:https://blog.csdn.net/jianchenn/article/details/106144313

解决思路:(分批处理,最后合并)
业务逻辑:本项目由于涉及到多张数据表,导出业务逻辑为:先查询主表,查询出数据后通过foreach遍历数据,并在遍历循环中根据与主表关联的字段查询另外几张表对应数据。
解决方案:
后端:

  1. 先将字典表、需要在循环中查询的所有数据表存储到redis中(如果数据过多,可以将其分为多个方法)
  2. 将导出数据接口中的数据进行分页,根据页码数导出相应的数据条数,并存放至临时excel文件中。等待全部执行完毕后将这些临时文件合并成一个excel文件,并返回。

前端:

  1. 首先请求字典等数据表存入redis的接口
  2. 请求导出数据接口,每次传入当前需导出数据的页码数,在没有全部完成之前页数++,直到完成后执行下载文件操作

PHP-Xlswriter扩展安装:
官网:https://xlswriter-docs.viest.me/zh-cn/an-zhuang/windows
在这里插入图片描述

在thinkphp6项目中打开config/cache.php,加上redis配置参数:

<?php// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------return [// 默认缓存驱动'default' => env('cache.driver', 'file'),// 缓存连接方式配置'stores'  => ['file' => [// 驱动方式'type'       => 'File',// 缓存保存目录'path'       => '',// 缓存前缀'prefix'     => '',// 缓存有效期 0表示永久缓存'expire'     => 0,// 缓存标签前缀'tag_prefix' => 'tag:',// 序列化机制 例如 ['serialize', 'unserialize']'serialize'  => [],],// 更多的缓存连接'redis' => ['type'   => 'redis',// 缓存主机'host'       => '127.0.0.1',// 缓存端口'port'     => '6379',// 缓存密码'password'     => '',// 缓存数据库'select'   => 0,// 缓存有效期 0表示永久缓存'timeout'   => 0,// 缓存前缀'prefix'   => '']],];

后端路由route/app.php

Route::post('export_data/:code/:id', 'Basicinfo/export_data'); // 根据指定条件和字段导出数据
Route::post('export_save_redis/:code/:id', 'Basicinfo/export_save_redis'); // 导出之前将部分数据存入redis
Route::post('export_save_redis2/:code/:id', 'Basicinfo/export_save_redis2'); // 导出之前将部分数据存入redis

控制器 BasicinfoController.php

<?php
namespace app\controller;
use app\service\BasicinfoService;
class BasicinfoController {public function export_save_redis(){$data = input('post.');$service = new BasicinfoService;$res = $service->export_basicinfo_save_redis($data);return show(true, '', $res);}public function export_save_redis2(){$data = input('post.');$service = new BasicinfoService;$res = $service->export_basicinfo_save_redis_person($data);return show(true, '', $res);}public function export_data(){$data = input('post.');$service = new BasicinfoService;$res = $service->export_data($data);if($res['state']){if(file_exists($res['file'])){return show(true, '', $res['file']);}else{return show(false, '文件导出失败');}}else{return show(false, '', $res);}}
}

BasicinfoService.php

<?php
namespace app\service;// 引入其他文件...class BasicinfoService{public function export_save_redis($data){$mapper = new BasicinfoMapper;$mapper -> export_save_redis($data['search']??[]);return true;}// 由于第此数据表数据量过多,导致保存失败,单独处理public function export_save_redis2($data){$mapper = new BasicinfoMapper;$mapper -> export_save_redis2($data['search']??[]);return true;}public function export_data($data){$result_data = [];$data['page'] = isset($data['page']) ? $data['page'] : 1;// 获取所有要查询的字段和名称$header_arr = [];$fields_arr = [];foreach($data['export_data'] as $key=>$val){array_push($header_arr, $val['label']);array_push($fields_arr, $val['field']);}// 文件存储目录$public = app()->getRootPath().'public/';$path = 'uploads/export_data/';$file_name = !empty($data['file_name']) ? $data['file_name'] : 'basicinfo_'.rand(99999, 99999999);$result_data['file_name'] = $file_name;$fileName = $file_name.'_'.$data['page'].'.xlsx';if(!file_exists($path)){mkdir($path, 0777);}$excel_config = ['path' => $public.$path // xlsx文件保存路径];$excel  = new \Vtiful\Kernel\Excel($excel_config);$fileObject = $excel->fileName($fileName, 'sheet1');$mapper = new BasicinfoMapper;$page_size = 5000; // 每次查询的数据条数// 在第1页时需要查询总数量,并获取总页数if($data['page'] == 1){// 获取总数$count = $mapper->get_count_basicinfo($data['search']??[]);$loop_num = ceil($count/$page_size);$data['total_page'] = $loop_num; // 设置总页数$result_data['total_page'] = $loop_num; // 返回总页数}else{$result_data['total_page'] = $data['total_page'];}$loop_page = 2; // 每页执行n次循环$loop_num = $loop_page;// 检测当前页数 * 循环数量 >= 总页数后if(intval($data['page'] * $loop_page) >= intval($data['total_page'])){if(intval($data['page'] * $loop_page) == intval($data['total_page'])){$loop_num = 1;}else{$loop_num = ($data['page'] * $loop_num) - $data['total_page'];}}// 当前循环完成后最大id$max_id = !empty($data['max_id']) ? $data['max_id'] : 0;for($l=0; $l<$loop_num; $l++){$list = $mapper->export_list_basicinfo($data['search']??[], $fields_arr, $max_id, $page_size);if($list){$max_id = $list[count($list)-1]['organ_id']; // 重置最大id// var_dump($max_id);$content = [];$i=0;foreach($list as &$val){// 处理转化数据值// ...$result = [];foreach ($fields_arr as $key) {if (isset($val_arr[$key])) {$result[$key] = $val[$key];}else{$result[$key] = '';}}$content[$i] = array_values($result);$i++;unset($val_arr);unset($val);unset($result);}// 将数据写入xls文件if(count($content) > 0){$fileObject->data($content)->output();unset($content);}}  unset($list);}$result_data['max_id'] = $max_id;// 检测如果为最后一页,则将文件合并后返回if(intval($data['page'] * $loop_page) >= intval($data['total_page'])){$result_data['state'] = true;// 处理合并文件$res = $this->merge_export_file($file_name, $data['page'], $header_arr);// $file_dir = 'uploads/export_data/'.$fileName;$result_data['file'] = $res;}else{$result_data['state'] = false;$result_data['file'] = '';}return $result_data;}// 处理合并文件public function merge_export_file($fileName, $page, $header_arr){$public = app()->getRootPath().'public/';$path = 'uploads/export_data/';$excel_config = ['path' => $public.$path // xlsx文件保存路径];$excel  = new \Vtiful\Kernel\Excel($excel_config);$res_file = $fileName . '_all.xlsx';$fileObject = $excel->fileName($res_file, 'sheet1');// // 设置样式$fileHandle = $fileObject->getHandle();$format = new \Vtiful\Kernel\Format($fileHandle);$alignStyle = $format->align(\Vtiful\Kernel\Format::FORMAT_ALIGN_VERTICAL_CENTER, \Vtiful\Kernel\Format::FORMAT_ALIGN_CENTER_ACROSS)->toResource();$boldStyle = $format// ->bold() // 加粗// ->wrap() // 文本换行// ->background(0xFFB6C1) // 设置背景颜色 颜色常量和16进制数->align(\Vtiful\Kernel\Format::FORMAT_ALIGN_CENTER, \Vtiful\Kernel\Format::FORMAT_ALIGN_VERTICAL_CENTER) // 文本居中->toResource();// // fileName 会自动创建一个工作表,你可以自定义该工作表名称,工作表名称为可选参数$fileObject->header($header_arr)// ->data($content)->defaultFormat($alignStyle)->setRow('A1', 30, $boldStyle)->setRow('A2:A9999', 20) // 行高// ->setColumn('A:A', '10')->setColumn('A:CZ', 30)->output();$file_arr = [];for($i=1; $i<=$page; $i++){$nowFile = $fileName.'_'.$i.'.xlsx';array_push($file_arr, $nowFile);if(file_exists($path.$nowFile)){$data = $excel->openFile($nowFile)->openSheet()->getSheetData();$fileObject->data($data)->output();}}// 回收资源$fileObject -> close();// 删除临时文件foreach($file_arr as $file){if(file_exists($path.$file)){unlink($path.$file);}}return $path.$res_file;}
}

BasicinfoMapper.php

<?php
namespace app\mapper;
use think\facade\Db;
use app\model\Basicinfo;
use think\facade\Cache;
class BasicinfoMapper
{public function export_save_redis($search){$where = '';$out_time = 600; // 缓存时间秒$batch_size = 2000;  // 每批次处理的数量$redis = Cache::store('redis')->handler();$redis->flushDb(); // 清空redis缓存$sql1= 'select main_id,field1,field2,... from table1  where 1=1 '.$where;$list1 = Db::query($sql1);if(!empty($list1)){// 建立管道$pipe1 = $redis->pipeline();$batches = array_chunk($list1, $batch_size); // 将数据分批foreach ($batches as $batch) {foreach ($batch as $v) {$pipe1->hMSet('table1_'.$v['main_id'], $v);$pipe1->expire('table1_'.$v['main_id'], $out_time); // 设置过期时间}$pipe1->exec(); // 执行当前批次$pipe1 = $redis->pipeline(); // 重新初始化管道}}unset($list1);$sql2= 'select main_id,field1,field2,... from table2  where 1=1 '.$where;$list2 = Db::query($sql2);if(!empty($list2)){$pipe2 = $redis->pipeline();$batches2 = array_chunk($list2, $batch_size); // 将数据分批foreach ($batches2 as $batch) {foreach ($batch as $v) {$pipe2->hMSet('table2_'.$v['main_id'], $v);$pipe2->expire('table2_'.$v['main_id'], $out_time); // 设置过期时间}$pipe2->exec(); // 执行当前批次$pipe2 = $redis->pipeline(); // 重新初始化管道}}unset($list2);}public function export_save_redis2($search){$where = '';$out_time = 600; // 缓存时间秒$batch_size = 2000;  // 每批次处理的数量$redis = Cache::store('redis')->handler();// $redis->flushDb(); // 清空redis缓存// $temp = '';$sql = 'select main_id,field1,field2,... from table3 where type in (1,3) '.$where;$list = Db::query($sql);if(!empty($list)){$pipe = $redis->pipeline();$batches = array_chunk($list, $batch_size); // 将数据分批foreach ($batches as $batch) {foreach ($batch as $v) {$key = $v['type'] == 1 ? 'table3_1_'.$v['main_id'] : 'table3_3_'.$v['main_id'];$pipe->hMSet($key, $v);$pipe->expire($key, $out_time);}$pipe->exec(); // 执行当前批次$pipe = $redis->pipeline(); // 重新初始化管道}}unset($list);}public function export_list_basicinfo($search, $fields=[], $max_id=0, $limit=1000){$pFields = [];$dFields = [];$cFields = [];$fields = array_reduce($fields, function($carry, $field) use (&$pFields,&$dFields,&$cFields) {if (substr($field, 0, 6) === 'table1_') {$dFields[] = $field;} else if (substr($field, 0, 7) === 'table2_') {// $carry[] = 'n.' . $field;$cFields[] = $field;} else if (substr($field, 0, 6) === 'table3') {$pFields[] = $field;} else {$carry[] =  $field;}return $carry;}, []);$fields = implode(',', $fields);$where = '1 = 1';// 其他条件...$sql = 'select main_id,'.$fields." from basicinfo   where ".$where.' and main_id > '.$max_id.' order by main_id limit '.$limit;$redis = Cache::store('redis')->handler();$list = Db::query($sql);if(!$list){ return []; }foreach ($list as $k => $v){$fd = [];if(count($dFields) > 0){$p_info = $redis->hGetAll('table1_'. $v['main_id']);if(!empty($p_info)){$fd = $p_info;}}if(in_array('organ', $dFields)){$list[$k]['organ'] = !empty($fd) ? $fd['organ']:'';}$fc = [];if(count($cFields) > 0){if (!empty($redis->hGetAll('table2_'. $v['main_id']))) {$fc = $redis->hGetAll('table2_'. $v['main_id']);}}if(in_array('party', $cFields)){$list[$k]['party'] = !empty($fc) ? $fc['party'] : 0;}$jbr = [];$fr = [];if(count($pFields) > 0){$table31_info = $redis->hGetAll('table3_1_'. $v['organ_idno']);$table33_info = $redis->hGetAll('table3_3_'. $v['organ_idno']);if(!empty($table31_info)){$jbr = $table31_info;}if(!empty($table33_info)){$fr = $table33_info;}}if(in_array('name', $pFields)){$list[$k]['name'] = !empty($jbr) ? $jbr['name']:'';}}return $list;
}
}

前端:

<template><el-button type="primary" style="margin-top: 30px;" @click="clickExportData">导出</el-button>
</template>
<script>
import downloadFile from '@/plugins/downloadFile';
export default {data() {return {percentage:0, //进度条的占比progressShow: false, // 是否显示进度条弹出层dowPage: 1}},methods: {clickExportData(){const data = {};// 获取被选中的字段let result = [];data['export_data'] = result;data['search'] = {};this.progressShow = true;this.percentage = 0;this.$http.post("export_save_redis/"+this.code+'/'+this.user_id, data).then(res1 => {// console.log(res1);this.$http.post("export_save_redis2/"+this.code+'/'+this.user_id, data).then(res2 => {this.exportData(data, times);})});},// 导出数据async exportData(data, times){data.page = this.dowPage;let url = "export_data/"+this.code+'/'+this.user_id;const {data:res} = await this.$http.post(url, data);console.log(res);if(res.state){console.log('进行文件下载')var xls_url = this.$request_url + res.content;clearInterval(times); // 清除计时器// 进行下载文件操作// ...// downloadFile.getProgress(xls_url, '下载文件_'+new Date().getTime(), this, this.percentage);}else{if(res.content){this.dowPage ++;data.file_name = res.content.file_name;data.total_page = res.content.total_page;data.max_id = res.content.max_id;this.exportData(data, times);}else{this.progressShow = false;clearInterval(times);this.$message.error('数据导出失败,请稍后重试!');}}},}
}
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/66210.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PT8M2302 触控 A/D 型 8-Bit MCU

1. 产品概述 PT8M2302 是一款可多次编程&#xff08; MTP &#xff09; A/D 型 8 位 MCU &#xff0c;其包括 2K*16bit MTP ROM 、 256*8bit SRAM、 ADC 、 PWM 、 Touch 等功能&#xff0c;具有高性能精简指令集、低工作电压、低功耗特性且完全集 成触控按键功能。为…

如何使用策略模式并让spring管理

1、策略模式公共接口类 BankFileStrategy public interface BankFileStrategy {String getBankFile(String bankType) throws Exception; } 2、策略模式业务实现类 Slf4j Component public class ConcreteStrategy implements BankFileStrategy {Overridepublic String ge…

前端开发:盒子模型、块元素

1.border边框 *{box-sizing:border-box; } //使所有边框不再撑大盒子模型 粗细 : border-width 样式 : border-style, 默认没边框 . solid 实线边框 dashed 虚线边框 dotted 点线边框 颜色 : border-color div { width : 200px ; height : 200px ; border : …

Nvidia Blackwell架构深度剖析:深入了解RTX 50系列GPU的升级

在CES 2025上&#xff0c;英伟达推出了基于Blackwell架构的GeForce RTX 50系列显卡&#xff0c;包括RTX 5090、RTX 5080、RTX 5070 Ti和RTX 5070。一段时间以来&#xff0c;我们已经知晓了该架构的各种细节&#xff0c;其中许多此前还只是传闻。不过&#xff0c;英伟达近日在20…

计算机网络 (45)动态主机配置协议DHCP

前言 计算机网络中的动态主机配置协议&#xff08;DHCP&#xff0c;Dynamic Host Configuration Protocol&#xff09;是一种网络管理协议&#xff0c;主要用于自动分配IP地址和其他网络配置参数给连接到网络的设备。 一、基本概念 定义&#xff1a;DHCP是一种网络协议&#xf…

“扣子”开发之四:与千帆AppBuilder比较

上一个专题——“扣子”开发——未能落地&#xff0c;开始抱着极大的热情进入&#xff0c;但迅速被稚嫩的架构模型折磨打击&#xff0c;硬着头皮坚持了两周&#xff0c;终究还是感觉不实用不趁手放弃了。今天询问了下豆包&#xff0c;看看还有哪些比较好的AI开发平台&#xff0…

RV1126+FFMPEG推流项目(7)AI音频模块编码流程

一、AI 模块和外设麦克风的关系 AI 模块是 RV1126 芯片的一个重要组成部分。它的主要功能是将外部接入的麦克风采集到的模拟信号通过内置的驱动程序转换为数字信号。这意味着麦克风作为外设&#xff0c;提供音频输入信号&#xff0c;AI 模块通过其硬件和软件的结合&#xff0c…

遗传算法 (Genetic Algorithm) 算法详解及案例分析

遗传算法 (Genetic Algorithm) 算法详解及案例分析 目录 遗传算法 (Genetic Algorithm) 算法详解及案例分析1. 引言2. 遗传算法的基本概念2.1 遗传算法的定义2.2 遗传算法的核心思想2.3 遗传算法的应用领域3. 遗传算法的主要步骤3.1 初始化种群3.2 选择3.3 交叉3.4 变异3.5 更新…

Rust 强制类型转换和动态指针类型的转换

在 Rust 中的强制类型转换&#xff08;Coercion&#xff09;语义&#xff0c;与 Java 或 C 中的子类到父类的转换有某些相似之处&#xff0c;但两者的实现机制和使用场景有很大的区别。 我们将从 Java/C 的子类到父类转换 和 Rust 的强制类型转换 的角度进行比较&#xff0c;帮…

第十二章:算法与程序设计

文章目录&#xff1a; 一&#xff1a;基本概念 1.算法与程序 1.1 算法 1.2 程序 2.编译预处理 3.面向对象技术 4.程序设计方法 5.SOP标志作业流程 6.工具 6.1 自然语言 6.2 流程图 6.3 N/S图 6.4 伪代码 6.5 计算机语言 二&#xff1a;程序设计 基础 1.常数 …

【后端面试总结】tls中.crt和.key的关系

tls中.crt和.key的关系 引言 在现代网络通信中&#xff0c;特别是基于SSL/TLS协议的加密通信中&#xff0c;.crt和.key文件扮演着至关重要的角色。这两个文件分别代表了数字证书和私钥&#xff0c;是确保通信双方身份认证和数据传输安全性的基石。本文旨在深入探讨TLS中.crt和…

【k8s面试题2025】2、练气初期

在练气初期&#xff0c;灵气还比较稀薄&#xff0c;只能勉强在体内运转几个周天。 文章目录 简述k8s静态pod为 Kubernetes 集群移除新节点&#xff1a;为 K8s 集群添加新节点Kubernetes 中 Pod 的调度流程 简述k8s静态pod 定义 静态Pod是一种特殊类型的Pod&#xff0c;它是由ku…

初学stm32 --- CAN

目录 CAN介绍 CAN总线拓扑图 CAN总线特点 CAN应用场景 CAN物理层 CAN收发器芯片介绍 CAN协议层 数据帧介绍 CAN位时序介绍 数据同步过程 硬件同步 再同步 CAN总线仲裁 STM32 CAN控制器介绍 CAN控制器模式 CAN控制器模式 CAN控制器框图 发送处理 接收处理 接收过…

运输层安全协议SSL

安全套接字层 SSL (Secure Socket Layer) SSL 作用在端系统应用层的 HTTP 和运输层之间&#xff0c;在 TCP 之上建立起一个安全通道&#xff0c;为通过 TCP 传输的应用层数据提供安全保障。 应用层使用 SSL 最多的就是 HTTP&#xff0c;但 SSL 并非仅用于 HTTP&#xff0c;而是…

ZooKeeper 常见问题与核心机制解析

Zookeeper集群本身不直接支持动态添加机器。在Zookeeper中&#xff0c;集群的配置是在启动时静态定义的&#xff0c;并且集群中的每个成员都需要知道其他所有成员。当你想要增加一个新的Zookeeper服务器到现有的集群中时&#xff0c;你需要更新所有现有服务器的配置文件&#x…

【Sql递归查询】Mysql、Oracle、SQL Server、PostgreSQL 实现递归查询的区别与案例(详解)

文章目录 Mysql 5.7 递归查询Mysql 8 实现递归查询Oracle递归示例SQL Server 递归查询示例PostgreSQL 递归查询示例 更多相关内容可查看 Mysql 5.7 递归查询 MySQL 5.7 本身不直接支持标准 SQL 中的递归查询语法&#xff08;如 WITH RECURSIVE 这种常见的递归查询方式&#xf…

【Rust自学】13.2. 闭包 Pt.2:闭包的类型推断和标注

13.2.0. 写在正文之前 Rust语言在设计过程中收到了很多语言的启发&#xff0c;而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。 在本章中&#xff0c;我们会讨论 Rust 的一…

【JavaScript】比较运算符的运用、定义函数、if(){}...esle{} 语句

比较运算符 !><> < 自定义函数&#xff1a; function 函数名&#xff08;&#xff09;{ } 判断语句&#xff1a; if(判断){ }else if(判断){ 。。。。。。 }else{ } 代码示例&#xff1a; <!DOCTYPE html> <html> <head><meta charset&quo…

WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现)

WOA-Transformer鲸鱼算法优化编码器时间序列预测&#xff08;Matlab实现&#xff09; 目录 WOA-Transformer鲸鱼算法优化编码器时间序列预测&#xff08;Matlab实现&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现WOA-Transformer鲸鱼算法优化编…

25/1/15 嵌入式笔记 初学STM32F108

GPIO初始化函数 GPIO_Ini&#xff1a;初始化GPIO引脚的模式&#xff0c;速度和引脚号 GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA的引脚0 GPIO输出控制函数 GPIO_SetBits&#xff1a;将指定的GPIO引脚设置为高电平 GPIO_SetBits(GPIOA, GPIO_Pin_0); // 将GPIO…