thinkphp6 + redis实现大数据导出excel超时或内存溢出问题解决方案
redis下载安装(window版本)
参考地址:https://blog.csdn.net/Ci1693840306/article/details/144214215
php安装redis扩展
参考链接:https://blog.csdn.net/jianchenn/article/details/106144313
解决思路:(分批处理,最后合并)
业务逻辑:本项目由于涉及到多张数据表,导出业务逻辑为:先查询主表,查询出数据后通过foreach遍历数据,并在遍历循环中根据与主表关联的字段查询另外几张表对应数据。
解决方案:
后端:
- 先将字典表、需要在循环中查询的所有数据表存储到redis中(如果数据过多,可以将其分为多个方法)
- 将导出数据接口中的数据进行分页,根据页码数导出相应的数据条数,并存放至临时excel文件中。等待全部执行完毕后将这些临时文件合并成一个excel文件,并返回。
前端:
- 首先请求字典等数据表存入redis的接口
- 请求导出数据接口,每次传入当前需导出数据的页码数,在没有全部完成之前页数++,直到完成后执行下载文件操作
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);})});},// 导出数据async exportData(data){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;// 进行下载文件操作// ...// 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);}else{this.progressShow = false;this.$message.error('数据导出失败,请稍后重试!');}}},}
}
</script>
相关文章:

thinkphp6 + redis实现大数据导出excel超时或内存溢出问题解决方案
redis下载安装(window版本) 参考地址:https://blog.csdn.net/Ci1693840306/article/details/144214215 php安装redis扩展 参考链接:https://blog.csdn.net/jianchenn/article/details/106144313 解决思路:࿰…...

Hexo + NexT + Github搭建个人博客
文章目录 一、 安装二、配置相关项NexT config更新主题主题样式本地实时预览常用命令 三、主题设置1.侧边栏2.页脚3.帖子发布字数统计 4.自定义自定义页面Hexo 的默认页面自定义 404 页自定义样式 5.杂项搜索服务 四、第三方插件NexT 自带插件评论系统阅读和访问人数统计 五、部…...
使用Sum计算Loss和解决梯度累积(Gradient Accumulation)的Bug
使用Sum计算Loss和解决梯度累积的Bug 学习 https://unsloth.ai/blog/gradient:Bugs in LLM Training - Gradient Accumulation Fix 这篇文章的记录。 在深度学习训练过程中,尤其是在大批量(large batch)训练中,如何高…...
基于本地消息表实现分布式事务
假设我们有一个电商系统,包含订单服务和库存服务。当用户下单时,需要在订单服务中创建订单,同时在库存服务中扣减库存。这是一个典型的分布式事务场景,我们需要保证这两个操作要么都成功,要么都失败,以保证数据的最终一致性。 项目结构: 订单服务(Order Service)库存服务(Inv…...

Web3与加密技术的结合:增强个人隐私保护的未来趋势
随着互联网的快速发展,个人隐私和数据安全问题越来越受到关注。Web3作为新一代互联网架构,凭借其去中心化的特性,为个人隐私保护提供了全新的解决方案。而加密技术则是Web3的重要组成部分,进一步增强了隐私保护的能力。本文将探讨…...

广播网络实验
1 实验内容 1、构建星性拓扑下的广播网络,实现hub各端口的数据广播,验证网络的连通性并测试网络效率 2、构建环形拓扑网络,验证该拓扑下结点广播会产生数据包环路 2 实验流程与结果分析 2.1 实验环境 ubuntu、mininet、xterm、wireshark、iperf 2.2 实验方案与结果分析…...
Vscode——SSH连接不上的一种解决办法
一、完整报错: > @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ > IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! > Someone could be eavesdropping on you right now (man-in-the...
ChatGPT大模型极简应用开发-目录
引言 要理解 ChatGPT,了解其背后的 Transformer 架构和 GPT 技术一路的演进则变得非常必要。 ChatGPT 背后的 LLM 技术使普通人能够通过自然语言完成过去只能由程序员通过编程语言实现的任务,这是一场巨大的变革。然而,人类通常容易高估技术…...

EI Scopus双检索 | 2025年第四届信息与通信工程国际会议(JCICE 2025)
会议简介 Brief Introduction 2025年第四届信息与通信工程国际会议(JCICE 2025) 会议时间:2025年7月25日-27日 召开地点:中国哈尔滨 大会官网:www.jcice.org 由黑龙江大学和成都信息工程大学主办,江苏科技大学协办的2025年第四届信…...

重学SpringBoot3-Spring Retry实践
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞??收藏评论 重学SpringBoot3-Spring Retry实践 1. 简介2. 环境准备3. 使用方式 3.1 注解方式 基础使用自定义重试策略失败恢复机制重试和失败恢复效果注意事项 3.2 编程式使用3.3 监听重试过程 监…...
TiDB 和 MySQL 的关系:这两者到底有什么不同和联系?
TiDB 和 MySQL 的关系:这两者到底有什么不同和联系? 在了解 TiDB 和 MySQL 之间的关系时,很多人可能会有疑问:这两个数据库到底有什么区别和联系?是不是 TiDB 就是 MySQL 的升级版?或者 TiDB 是一种“替代…...

【Java】JDK17的下载安装(与JDK1.8相互切换)
本文以参考以下链接为主:JDK17 如果上述操作不生效,请看以下操作: 添加以下变量并移动到最上面即可...
CSS3 3D 转换介绍
CSS3 中的 3D 转换提供了一种在二维屏幕上呈现三维效果的方式,主要包括translate3d、rotate3d、scale3d等转换函数,下面来详细介绍: 1. 3D 转换的基本概念 坐标系 在 CSS3 的 3D 空间中,使用的是右手坐标系。X 轴是水平方向&…...
Vue3 Element-Plus el-tree 右键菜单组件
参考代码:实现Vue3Element-Plus(tree、table)右键菜单组件 这篇文章的代码确实能用,但是存在错误,修正后的代码: <template><div style"text-align: right"><el-icon size"12" color"#…...

鸿蒙学习构建视图的基本语法(二)
一、层叠布局 // 图片 本地图片和在线图片 Image(https://developer.huawei.com/allianceCmsResource/resource/HUAWEI_Developer_VUE/images/080662.png) Entry Component//自适应伸缩 设置layoutWeight属性的子元素与兄弟元素 会按照权重进行分配主轴的空间// Position s…...

python-leetcode-存在重复元素 II
219. 存在重复元素 II - 力扣(LeetCode) class Solution:def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:seen set()for i, num in enumerate(nums):if num in seen:return Trueseen.add(num)if len(seen) > k:seen.remove…...

P6周:VGG-16算法-Pytorch实现人脸识别
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 我的环境 语言环境:Python 3.8.12 编译器:jupyter notebook 深度学习环境:torch 1.12.0cu113 一、前期准备 1.设置GPU im…...

BeanFactory 是什么?它与 ApplicationContext 有什么区别?
谈到Spring,那势必要讲讲容器 BeanFactory 和 ApplicationContext。 BeanFactory是什么? BeanFactory,其实就是 Spring 容器,用于管理和操作 Spring 容器中的 Bean。可能此时又有初学的小伙伴会问:Bean 是什么&#x…...

虚幻基础-1:cpu挑选(14600kf)
能帮到你的话,就给个赞吧 😘 文章目录 ue非常吃cpu拉满主频打开项目编写蓝图运行原因 时间长 关于压力测试 本文以14600kf为例,双12购入,7月份产。 ue非常吃cpu 经本人测试,ue是非常吃cpu的。 拉满主频 无论任何时间…...
多种vue前端框架介绍
学如逆水行舟,不进则退。 在现今的软件开发领域,Vue.js凭借其高效、灵活和易于上手的特性,成为了前端开发的热门选择。对于需要快速搭建企业级后台管理系统的开发者而言,使用现成的Vue后台管理系统模板无疑是一个明智之举。 本文…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...