tp6数据导出excel文件时对大数据量处理

php 复制代码
  
    public function export()
    {
        list($page, $limit, $where) = $this->buildTableParames();
        if(!$where){
            $this->error('搜索条件不能为空');
        }

        $count = $this->model
            ->withJoin(['customer','bills'],'left')
            ->where($where)
            ->count();

        if($count <= 0){
            $this->error('没有检测到有效的数据');
        }

        // 定义转换数组
        $bill_statusArr = [0=>'未生成',1=>'已生成'];

        // 定义表头
        $header = [
            ['订单日期', 'order_time'],
            ....            
            ['账单状态', 'bill_status'],
        ];

        // 设置每批处理的数据量
        $batchSize = 1000;
        // 设置单个Excel文件最大行数3000行数据+1行表头, 避免生成过大的文件
        $maxRowsPerFile = 3001;
        
        // 创建一个临时目录来存放生成的Excel文件
        $tempDir = runtime_path() . 'export_temp_yswuliumingxi_' . time() . '/';
        if (!is_dir($tempDir)) {
            mkdir($tempDir, 0755, true);
        }

        $excelFiles = []; // 用于记录生成的所有Excel文件路径

        // 初始化当前Excel写入对象
        $fileNamePrefix = '物流配送费_应收款明细_';

        try {
            // 计算总批次数
            $totalPages = ceil($count / $batchSize);
            $spreadsheet = null;
            $writer = null;
            $fileIndex = 1;
            $rowCounterInCurrentFile = 0;
            
            for ($pageNum = 1; $pageNum <= $totalPages; $pageNum++) {
                // 1. 分页查询数据(逻辑不变)
                $list = $this->model
                    ->alias('cw_ys_wuliu')
                    ->join('cw_ys_wuliu_bills bills','cw_ys_wuliu.bills_id = bills.id','left')
                    ->where($where)
                    ->order(['cw_ys_wuliu.id'=>'desc'])
                    ->field('cw_ys_wuliu.id,order_time,kb_code,bills.ordersn as bills_ordersn,old_code,good_name,delivery_people,pricetype,cw_ys_wuliu.settlement_style,start_provice,start_city,start_area,cw_ys_wuliu.provice,cw_ys_wuliu.city,cw_ys_wuliu.area,cw_ys_wuliu.good_jian,cw_ys_wuliu.good_volumn,cw_ys_wuliu.good_weight,cw_ys_wuliu.ps_price,cw_ys_wuliu.sh_price,cw_ys_wuliu.other_price,cw_ys_wuliu.total_price,cw_ys_wuliu.service_deduct_price,cw_ys_wuliu.bill_status')
                    ->page($pageNum, $batchSize)
                    ->select()
                    ->each(function($item, $key) use($bill_statusArr){
                        $item['order_time'] = date('Y-m-d H:i:s', $item['order_time']);
                        $item['bill_status'] = $bill_statusArr[$item['bill_status']] ?? $item['bill_status'];
                        return $item;
                    })->toArray();
                
                // 2. 检查是否需要创建新的Excel对象和文件
                if ($spreadsheet === null || $rowCounterInCurrentFile + count($list) > $maxRowsPerFile) {
                    // 如果已有上一个文件,先保存
                    if ($writer !== null) {
                        $currentFilePath = $tempDir . $fileNamePrefix . 'Part' . ($fileIndex-1) . '.xlsx';
                        $writer->save($currentFilePath);
                        $excelFiles[] = $currentFilePath;
                    }
                    
                    // 创建新的PhpSpreadsheet对象和工作表
                    $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
                    $sheet = $spreadsheet->getActiveSheet();
                    // 写入表头
                    $colIndex = 1;
                    foreach ($header as $head) {
                        $sheet->setCellValueByColumnAndRow($colIndex, 1, $head[0]);
                        $colIndex++;
                    }
                    $rowCounterInCurrentFile = 1; // 表头占第1行
                    $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx');
                    $fileIndex++;
                }
                
                // 3. 将这一批数据写入当前工作表
                foreach ($list as $dataRow) {
                    $rowCounterInCurrentFile++;
                    $colIndex = 1;
                    foreach ($header as $head) {
                        $fieldName = $head[1];
                        $cellValue = $dataRow[$fieldName] ?? '';
                        $sheet->setCellValueByColumnAndRow($colIndex, $rowCounterInCurrentFile, $cellValue);
                        $colIndex++;
                    }
                }
                // 释放本批数据内存
                unset($list);
            }
            
            // 循环结束后,保存最后一个Excel文件
            if ($writer !== null) {
                $currentFilePath = $tempDir . $fileNamePrefix . 'Part' . ($fileIndex-1) . '.xlsx';
                $writer->save($currentFilePath);
                $excelFiles[] = $currentFilePath;
            }

            // --- 生成ZIP压缩包并提供下载 ---
            if (count($excelFiles) === 1) {
                // 如果只有一个文件,直接提供下载
                $downloadFilePath = $excelFiles[0];
                $downloadFileName = $fileNamePrefix . date('YmdHis') . '.xlsx';
            } else {
                // 如果有多个文件,打包成ZIP
                $zipFileName = $fileNamePrefix . '打包_' . date('YmdHis') . '.zip';
                $zipFilePath = $tempDir . $zipFileName;
                
                $zip = new \ZipArchive();
                if ($zip->open($zipFilePath, \ZipArchive::CREATE) === TRUE) {
                    foreach ($excelFiles as $file) {
                        $zip->addFile($file, basename($file));
                    }
                    $zip->close();
                }
                $downloadFilePath = $zipFilePath;
                $downloadFileName = $zipFileName;
            }

            // 输出文件到浏览器
            header('Content-Description: File Transfer');
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="' . urlencode($downloadFileName) . '"');
            header('Content-Transfer-Encoding: binary');
            header('Expires: 0');
            header('Cache-Control: must-revalidate');
            header('Pragma: public');
            header('Content-Length: ' . filesize($downloadFilePath));
            readfile($downloadFilePath);

            // 清理临时文件(可选)
            $this->deleteDir($tempDir);

            exit; // 结束执行,避免框架输出额外内容

        } catch (\Exception $e) {
            // 清理临时文件
            if (is_dir($tempDir)) {
                $this->deleteDir($tempDir);
            }
            $this->error('导出过程发生错误:' . $e->getMessage());
        }
    }

    /**
     * 辅助函数:删除目录及其下所有文件
     */
    private function deleteDir($dirPath) {
        if (!is_dir($dirPath)) return;
        $files = array_diff(scandir($dirPath), array('.', '..'));
        foreach ($files as $file) {
            (is_dir("$dirPath/$file")) ? $this->deleteDir("$dirPath/$file") : unlink("$dirPath/$file");
        }
        return rmdir($dirPath);
    }
相关推荐
heartbeat..10 小时前
注解 + 反射:Web 项目 Excel 一键导出工具 EnhancedExportExcelUtil 详解
java·excel·poi
霸王大陆11 小时前
《零基础学 PHP:从入门到实战》模块十:从应用到精通——掌握PHP进阶技术与现代化开发实战-2
android·开发语言·php
小李独爱秋12 小时前
计算机网络经典问题透视:TCP的“误判”——非拥塞因素导致的分组丢失
服务器·网络·tcp/ip·计算机网络·智能路由器·php
yivifu16 小时前
Excel表格取多行数据中的唯一值及多条件数据查询问题
excel
凯子坚持 c17 小时前
Docker 容器全生命周期管理与运维命令深度解析
运维·docker·php
霸王大陆17 小时前
《零基础学 PHP:从入门到实战》模块十:从应用到精通——掌握PHP进阶技术与现代化开发实战-4
开发语言·php
golang学习记18 小时前
Redis Pipeline 实战指南:提升 Go 后端性能的利器
redis·golang·php
JienDa19 小时前
JienDa聊PHP:乡镇外卖跑腿小程序开发实战:基于PHP的乡镇同城O2O系统开发
开发语言·php
霸王大陆19 小时前
《零基础学 PHP:从入门到实战》模块十:从应用到精通——掌握PHP进阶技术与现代化开发实战-1
android·开发语言·php