最稳方案是使用phpspreadsheet:安装composer require phpoffice/phpspreadsheet,设SimSun字体和UTF-8响应头,分批导出防内存溢出,日期字段须设数值类型+Excel格式码,禁用toArray()改用getRowIterator流式读取,中文表头需清洗映射,大文件导入每千行调gc_collect_cycles()。

如果您在ThinkPHP6.x项目中需处理报表类Excel数据,但导入导出功能异常或性能低下,则可能是由于PhpSpreadsheet集成不当、类型处理错误或内存控制缺失。以下是解决此问题的步骤:
一、安装与基础配置
确保PhpSpreadsheet正确引入并适配ThinkPHP6.x自动加载机制,避免命名空间解析失败或类未找到错误。该库是PHPExcel的官方继任者,支持PHP 7.4+及TP6/TP8,不兼容已废弃的PHPExcel。
1、在项目根目录执行命令安装:composer require phpoffice/phpspreadsheet
2、确认composer autoload已生效,无需手动require vendor/autoload.php
立即学习“PHP免费学习笔记(深入)”;
3、在控制器顶部声明完整命名空间:use PhpOffice\PhpSpreadsheet\Spreadsheet;
4、验证PHP版本:TP6要求PHP ≥ 7.2,而phpspreadsheet 1.20+需PHP 7.4+,低于7.4将导致DateTime处理异常或类加载失败
二、Excel导出:安全写入与中文支持
导出时需显式控制字体、响应头及单元格类型,防止中文乱码、日期显示为数值或内存溢出。直接使用fromArray()易丢失格式信息且无法处理大表。
1、创建Spreadsheet实例并获取活动工作表:$spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet();
2、设置全局中文字体:$spreadsheet->getDefaultStyle()->getFont()->setName('SimSun');
3、设置HTTP响应头,含UTF-8编码:header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8');
4、对文件名进行URL编码:header('Content-Disposition: attachment; filename="'.rawurlencode('报表数据.xlsx').'";');
5、写入日期字段时,先设为数值类型再应用格式码:$cell->setValueExplicit($timestamp, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_NUMERIC);
6、调用$cell->getStyle()->getNumberFormat()->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME);
三、Excel导入:路径校验与流式读取
上传后直接传$_FILES临时路径给IOFactory::load()极易失败,原因包括路径权限不足、临时文件被TP清理、中文路径未转义。应优先使用流式加载并校验真实路径。
1、通过request()->file('file')获取think\File对象
2、立即调用->getRealPath()获取服务器端绝对路径,禁用getPathname()或moveTo()
3、使用fopen流加载避免路径异常:$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); $spreadsheet = $reader->load(fopen($file->getRealPath(), 'r'));
4、校验MIME类型与后缀:用finfo_file()代替$_FILES['file']['type'],仅允许application/vnd.openxmlformats-officedocument.spreadsheetml.sheet和application/vnd.ms-excel
5、显式指定读取器类型:.xlsx用'Xlsx',.xls用'Xls',传错将抛Invalid reader异常
四、数据清洗与字段映射
Excel表头常为中文(如“客户姓名”),而数据库字段为customer_name;某列填写“是/否”,但数据库为tinyint(1)。不做映射将导致字段错位或SQL报错。
1、读取首行作为原始表头:$headerRow = $worksheet->getRowIterator()->current()->getCellIterator()->map(function($cell) { return $cell->getValue(); });
2、建立映射数组:$map = ['客户姓名' => 'customer_name', '是否启用' => 'status'];
3、遍历后续行时,按映射键组装数据:$data[] = [$map[$header[0]] => $row[0], $map[$header[1]] => $row[1]];
4、对布尔值列做转换:if (in_array($value, ['是', 'Y', 'y', '1'], true)) $value = 1; else $value = 0;
5、跳过全空行:使用array_filter($rowData)判断,避免插入空记录触发NOT NULL约束失败
五、大文件处理:分批导入与内存控制
10万行Excel若一次性toArray(),可能耗尽512MB内存。应采用行迭代+手动清洗+分批入库策略,防止超限被kill。
1、禁用getActiveSheet()->toArray(),改用getRowIterator()逐行遍历
2、每处理1000行,调用gc_collect_cycles()释放内存
3、使用Db::transaction()包裹批量插入,每500条提交一次事务
4、对单行数据执行validate()验证后再入库,捕获\PhpOffice\PhpSpreadsheet\Exception和\InvalidArgumentException
5、导入完成后立即unlink($file->getRealPath())清理临时文件,防止磁盘占满



















