ThinkPHP项目需统一支持Excel和CSV导入,须通过文件头4字节魔数识别真实类型:'efbbbf'为UTF-8 BOM CSV,'d0cf11e0'为.xls,'504b0304'为.xlsx或CSV(需二次验证);统一入口importData()调用detectFileType()分发至CsvImporter或ExcelImporter;CSV解析推荐fgetcsv流式读取并映射字段,Excel用PhpSpreadsheet;校验与入库共用规则,逐行校验后分批事务插入;最后删除临时文件并返回结构化JSON结果。

ThinkPHP项目中需要统一支持Excel和CSV两种格式的导入,但Excel解析库(如PhpSpreadsheet)默认不直接处理CSV,而CSV又不能用Excel方式打开——必须让同一套导入接口能自动识别文件类型并走对应解析路径。
判断上传文件真实类型
不要依赖$_FILES['file']['type'],它由浏览器提供,极易被伪造或缺失。直接读取文件头4个字节做二进制检测:
用fopen($filePath, 'rb')打开文件→fread($handle, 4)读取前4字节→用bin2hex()转为十六进制字符串→比对:若为'efbbbf'则为UTF-8带BOM的CSV;若为'd0cf11e0'则为.xls;若为'504b0304'则为.xlsx或.csv(需进一步验证)。
关键点:CSV文件可能无扩展名或后缀被改,仅靠后缀判断会失败。必须用魔数校验。
立即学习“PHP免费学习笔记(深入)”;
统一入口路由与控制器逻辑
在控制器中定义一个通用导入方法,例如importData(),接收上传文件后立即执行类型识别:
先调用自定义函数detectFileType($filePath)返回csv/xls/xlsx → 根据结果分发到CsvImporter::handle()或ExcelImporter::handle()。
注意:不要在同一个方法里混写两种解析逻辑,否则后续维护成本爆炸。拆成独立类是必须的。
CSV解析器实现(流式+字段映射)
方法一:原生fgetcsv流式读取(推荐用于超大文件)
用fopen($filePath, 'r')打开→设置内部编码为UTF-8(mb_internal_encoding('UTF-8'))→首次调用fgetcsv()获取表头→建立字段映射数组,例如['username' => 0, 'email' => 2]→后续循环中每行用fgetcsv()读取→按映射提取值→组装为关联数组。
使用 qbo-mileage CLI 及用户凭证,从 Airtable、Outlook 或 Google Calendar 记录生成 QuickBooks Online 里程 CSV 文件。
【跳过首行必须显式调用一次fgetcsv(),否则第一行数据会被当作内容误插】
方法二:使用ThinkPHP封装的Csv类(适合中小文件)
引入think\Csv→实例化$new Csv()→调用$input_csv($filePath)→该方法会自动跳过首行并返回键值对数组,键名为首行文字,值为对应列内容。
注意:此方法会把整个文件读入内存,文件超过5MB时可能触发memory_limit错误。
Excel与CSV共用数据校验与入库流程
第一步:定义校验规则数组,例如['username' => 'require|max:20', 'email' => 'email']。
第二步:遍历解析后的每一行数据→用Validate::check($row, $rule)逐行校验→校验失败的行号和错误信息存入$errorLog[]。
第三步:校验通过的数据批量插入数据库→使用Db::transaction()包裹→每500行为一批commit,避免单事务过大锁表。
这一步操作起来很简单,直接把校验通过的$dataList传给Db::table('user')->insertAll()就行,但必须确保事务已开启且错误时能rollback。
删除临时文件并返回结构化结果
无论成功失败,都必须执行unlink($filePath)删除服务器上的临时CSV或Excel文件。
返回JSON包含三个字段:status(true/false)、message(提示语)、data(成功条数/错误详情数组)。


















