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);
    }
相关推荐
BingoGo6 小时前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack6 小时前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack1 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo1 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack2 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理3 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082853 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe3 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5
LAM LAB3 天前
【VBA】Excel指定单元格范围内字体设置样式,处理导出课表单元格
excel·vba
在这habit之下3 天前
Keepalived学习总结
excel