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);
    }
相关推荐
lskblog6 小时前
PHP中正确处理HTTP响应:从原始响应到JSON数组的完整指南
http·json·php·laravel
課代表7 小时前
Excel VBA 为数据赋予随机浅色标记
excel·vba·可视化·条件格式·标记·对比·随机
万岳软件开发小城9 小时前
2026 在线教育新趋势:网校系统源码正在重塑教育培训平台开发模式
人工智能·php·在线教育系统源码·教育平台搭建·教育app开发·教育软件开发
kylezhao201911 小时前
C#上位机开发数据持久化:excel报表导入导出
开发语言·c#·excel
iCxhust11 小时前
linux /etc 目录 etc是什么缩写
linux·运维·服务器·php
豌豆学姐12 小时前
Sora2 能做什么?25 秒视频生成 API 的一次接入实践
大数据·人工智能·小程序·aigc·php·开源软件
悟能不能悟12 小时前
springboot controller返回的是HttpServletResponse成功返回excel文件流,失败就返回失败参数
spring boot·后端·excel
wangxingps16 小时前
phpmyadmin版本对应的各php版本
服务器·开发语言·php
lzjava202416 小时前
LangChain4j RAG检索增强生成
php
豌豆学姐16 小时前
123 口播数字人 API 接入实战:附完整前后端开源项目
大数据·php·uniapp·开源软件