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);
    }
相关推荐
darkb1rd1 小时前
八、PHP SAPI与运行环境差异
开发语言·网络安全·php·webshell
darkb1rd1 小时前
七、PHP配置(php.ini)安全最佳实践
安全·php·webshell
JSON_L2 小时前
Fastadmin中使用GatewayClient
php·fastadmin
青茶3602 小时前
php怎么实现订单接口状态轮询请求
前端·javascript·php
wxin_VXbishe4 小时前
C#(asp.net)学员竞赛信息管理系统-计算机毕业设计源码28790
java·vue.js·spring boot·spring·django·c#·php
Risehuxyc6 小时前
备份三个PHP程序
android·开发语言·php
lpfasd1236 小时前
物联网后端岗位java面试题
java·物联网·php
JSON_L6 小时前
Fastadmin中使用百度翻译API
php·fastadmin·百度翻译api
qq_297574676 小时前
【实战】POI 实现 Excel 多级表头导出(含合并单元格完整方案)
java·spring boot·后端·excel
m0_748229997 小时前
PHP简易聊天室开发指南
开发语言·php