必须使用InnoDB引擎并开启事务,通过Filesystem安全保存临时文件,手动判断扩展名加载对应Reader并启用只读模式,分批插入且禁止闭包内try-catch,校验失败时抛出带行号的异常。

ThinkPHP导入Excel时,一旦数据写入中途出错,必须让已入库的记录全部撤回,否则会出现「部分成功」的脏数据状态。
确认数据库表引擎支持事务
执行 SHOW CREATE TABLE `user`; 查看建表语句末尾是否含 ENGINE=InnoDB。MyISAM 表执行 Db::rollback() 会静默成功但实际无效,所有回滚测试都将在该前提下彻底失效。
若显示 ENGINE=MyISAM,需先执行 ALTER TABLE `user` ENGINE=InnoDB; 转换引擎,否则后续所有事务控制均无意义。
上传后安全保存临时文件
不要用 copy() 或 move_uploaded_file() 直接操作 $_FILES['file']['tmp_name']——路径拼错、Windows/Linux 斜杠不一致、运行用户无写权限都会导致文件丢失或覆盖。
使用 ThinkPHP 内置 Filesystem 组件存文件:$path = \think\facade\Filesystem::putFile('upload', $file);,它自动处理目录创建、唯一文件名生成与权限适配。
这一步失败时,【$path 返回 false】,应立即中止流程,不进入后续解析环节。
按格式选择对应 Reader 并启用只读模式
方法一:用 IOFactory 自动探测(简单但不可靠)
不推荐用于生产环境,.xlsx 文件可能被误判为 .csv 导致解析崩溃。
方法二:手动判断扩展名后实例化指定 Reader(推荐)
① 获取原始文件名:$ext = pathinfo($file->getOriginalName(), PATHINFO_EXTENSION);
② 根据扩展名加载对应 Reader:$reader = strtolower($ext) === 'xlsx' ? new \PhpOffice\PhpSpreadsheet\Reader\Xlsx() : new \PhpOffice\PhpSpreadsheet\Reader\Xls();
③ 必须调用 $reader->setReadDataOnly(true); ——跳过样式、公式、合并单元格解析,避免内存溢出与日期格式错乱。
通过可复用的 DuckDB 优先 ETL 执行器运行 Excel/CSV/XLSX 分析,同时保留脚本优先架构。适用于系统需要通用可导出执行器来完成标准表电子表格分析、安全只读 SQL 执行、多表注册、查询模板、验证产物、script_usage.md 使用说明、运行清单,以及调度友好的可重复输出等场景。DuckDB 为首选执行引擎;其他工具仅用于文件探测、Excel 标准化或有明确文档记录的降级场景。
开启事务并分批写入数据
使用 Db::transaction() 闭包方式包裹整个导入逻辑,它会在闭包内抛出未捕获异常时自动触发 rollback:
Db::transaction(function () use ($spreadsheet, $mappedFields) {<br> $sheet = $spreadsheet->getActiveSheet();<br> $rows = [];<br> for ($i = 2; $i getHighestRow(); $i++) {<br> $row = [];<br> foreach ($mappedFields as $col => $field) {<br> $row[$field] = $sheet->getCell($col . $i)->getValue();<br> }<br> $rows[] = $row;<br> if (count($rows) >= 500) {<br> Db::name('user')->insertAll($rows);<br> $rows = [];<br> }<br> }<br> if (!empty($rows)) {<br> Db::name('user')->insertAll($rows);<br> }<br>});
注意:闭包内不能出现任何 try-catch,否则异常被捕获后不会穿透,框架无法感知错误,【事务将静默提交】。
解析失败时精准定位错误行
在循环读取每一行前加校验逻辑,例如检查手机号格式、邮箱合法性、必填字段是否为空:
if (empty($row['mobile']) || !preg_match('/^1[3-9]\d{9}$/', $row['mobile'])) {<br> throw new \Exception("第 {$i} 行手机号格式错误");<br>}
这样异常消息自带行号,前端可直接展示具体哪一行出错,无需人工逐行排查。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










