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);
    }
相关推荐
上海云盾-小余2 小时前
云主机安全加固:从系统、网络到应用的零信任配置
网络·安全·php
Eric.Lee20214 小时前
查看ubuntu机器正在使用的网络端口
网络·ubuntu·php
jinanwuhuaguo5 小时前
OpenClaw安全使用实践全景深度指南:从“裸奔龙虾”到“可信数字堡垒”的体系化构建
开发语言·php
xiangpanf5 小时前
Laravel学习指南:从入门到精通
php·laravel
南梦浅8 小时前
全过程步骤(从零到高可用企业网络)
开发语言·网络·php
xiangpanf9 小时前
Laravel 9.X新特性全解析
php·laravel
xiangpanf9 小时前
Laravel与ThinkPHP框架深度对比
php·laravel
hongtianzai9 小时前
Laravel7.x十大核心特性解析
java·c语言·开发语言·golang·php
ZHOUPUYU9 小时前
从缓存到消息队列的全面应用,PHP与Redis深度实战
redis·缓存·php
ZHOUPUYU10 小时前
PHP性能分析与调优:从定位瓶颈到实战优化
开发语言·后端·html·php