ThinkPHP导入Excel并保存到数据库需完成文件上传校验、格式解析、数据清洗、字段映射、批量写入五步闭环;跳过任一环节都可能导致空行入库、中文乱码、内存溢出或SQL报错。

ThinkPHP导入Excel并保存到数据库,需完成文件上传校验、格式解析、数据清洗、字段映射、批量写入五步闭环,缺一不可;跳过任一环节都可能导致空行入库、中文乱码、内存溢出或SQL报错。
安装与环境准备
执行 composer require phpoffice/phpspreadsheet 安装最新稳定版;【TP6项目必须使用 PHP ≥ 7.4】,低于该版本会因 DateTime 类型处理异常导致读取失败。TP5.1 用户请确认 PHP ≥ 7.1,但强烈建议升级至 TP6+ 避免兼容性陷阱。
安装后无需手动 require,Composer 自动加载已生效;若仍报 “Class not found”,检查是否漏写了 use PhpOfficePhpSpreadsheetIOFactory; ——漏掉 【PhpOffice\】 前缀是常见错误。
上传文件并校验真实类型
接收上传文件后,不要依赖 $_FILES['file']['type'],它由浏览器伪造,完全不可信。
立即学习“PHP免费学习笔记(深入)”;
用 finfo_file() 检查 MIME 类型:$finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file($finfo, $_FILES['file']['tmp_name']); finfo_close($finfo); 只允许 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(.xlsx)或 application/vnd.ms-excel(.xls)。
同时用 pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION) 获取真实后缀,转大写后映射为 reader 类型:【'xlsx' → 'Xlsx','xls' → 'Xls'】;拒绝 .csv 文件——phpspreadsheet 默认不支持 CSV 读取,强行加载会直接抛出异常。
流式读取 Excel 数据
方法一:显式创建 Reader(推荐)
第一步:$reader = IOFactory::createReader($readerType); // $readerType 来自上一步映射结果
第二步:$reader->setReadDataOnly(true); // 跳过公式、样式、宏等非数据内容,降低 40% 内存占用
第三步:$spreadsheet = $reader->load($_FILES['file']['tmp_name']); // 此时路径必须是真实可读的临时文件路径
方法二:避免 load() 全量加载大文件
对超 5000 行的 Excel,改用 getRowIterator() 流式遍历:$worksheet = $spreadsheet->getActiveSheet(); foreach ($worksheet->getRowIterator(2) as $row) { … };从第 2 行开始读(跳过表头),每行调用 $cellIterator = $row->getCellIterator(); $cellIterator->setIterateOnlyExistingCells(false); 确保空列也被捕获。
注意:不要用 $worksheet->toArray() —— 它会一次性把整张表转成二维数组,10 万行 Excel 极易触发内存耗尽(默认 512MB 不够用)。
清洗与映射数据
提取首行作为原始表头:$headerRow = $worksheet->rangeToArray('A1:'.$worksheet->getHighestColumn().'1')[0];
将中文表头(如“用户姓名”、“注册时间”)映射为数据库字段(如 user_name、create_time):$fieldMap = ['用户姓名' => 'user_name', '注册时间' => 'create_time'];
遍历每一行数据时,用 array_combine($headerRow, $rowData) 生成键值对,再按 $fieldMap 重键:$dbRow = []; foreach ($fieldMap as $excelKey => $dbField) { $dbRow[$dbField] = $mappedData[$excelKey] ?? null; };【未在 $fieldMap 中声明的 Excel 列会被丢弃,防止脏数据入库】。
对布尔类字段(如“是否启用”填“是/否”),统一转为 1/0;日期字段用 DateTime::createFromFormat('Y-m-d', $val) 校验后再转时间戳;手机号、邮箱做基础正则过滤。
分批插入数据库
将清洗后的数据按每 500 行切片:$chunks = array_chunk($cleanedData, 500);
循环插入每个分片:foreach ($chunks as $chunk) { Db::table('users')->insertAll($chunk); };TP6 的 insertAll() 自动拼接批量 INSERT 语句,比单条 insert 快 8 倍以上。
每次插入后调用 gc_collect_cycles() 强制回收内存,防止连续插入时内存持续上涨;插入完成后立即 unlink($_FILES['file']['tmp_name']) 删除临时文件,避免磁盘占满。



















