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.pswp.cn/web/66210.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/66210.shtml
英文地址,請注明出處:http://en.pswp.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…