先讲一个极易被忽视的关键细节:在导入 Excel 时,如果没有记录日志,后续的数据回溯基本只能靠猜测,问题排查完全依赖脑补。因此,每一批次的导入操作,都必须完整记录文件名、总行数、成功数、失败数、错误详情以及时间戳,缺一不可。

那么,在 ThinkPHP 框架中实现这一功能,具体应该怎么操作?
配置日志驱动与通道
第一步,在 config/log.php 中新建一个专属通道,命名为 excel_import。指定独立的日志文件路径与格式,这样做的好处是:导入日志与系统日志彻底分离,查询时一目了然,便于运营与开发人员快速定位问题。
以下是配置示例:
return [
'channels' => [
'excel_import' => [
'type' => 'single',
'path' => runtime_path('log/excel_import/'),
'level' => 'info',
'file_size' => 1024 * 1024 * 5,
'max_files' => 30,
],
],
];
这里有一个潜规则值得留意:runtime_path('log/excel_import/') 会自动创建子目录,但如果 runtime/log/ 目录不可写,日志写入会静默失败——你根本不知道它没有被写入。这个细节必须高度重视,否则日志功能形同虚设。
封装日志记录方法
通道配置完成后,接下来封装一个服务类,专门用来输出结构化日志。推荐将文件放在 app/common/service/ExcelImportLogger.php。
namespace app\common\service;
use think\facade\Log;
class ExcelImportLogger
{
public static function record($filename, $totalRows, $successCount, $failCount, $errors = [])
{
$logData = [
'file' => $filename,
'total' => $totalRows,
'success' => $successCount,
'fail' => $failCount,
'errors' => array_slice($errors, 0, 5), // 仅保留前5条错误,防止日志文件过大
'time' => date('Y-m-d H:i:s'),
];
Log::channel('excel_import')->info('EXCEL_IMPORT_RESULT', $logData);
}
}
注意这里只截取了前5条错误记录,原因很直接:如果一张 Excel 表里有上千个错误行,全部写入日志会导致文件迅速膨胀。有节制地记录,既能反映问题全貌,又不会影响系统性能。
在导入逻辑中调用日志记录
接下来进入实战环节。整体流程分为三个步骤:
第一步:在控制器的导入方法开头,捕获上传的文件名。
第二步:逐行解析 Excel 并写入数据库,同时同步统计成功与失败的数量。
第三步:解析完成后立即调用 ExcelImportLogger::record(),传入所有必要参数。
示例代码片段如下:
$file = request()->file('excel');
$filename = $file->getOriginalName();
// ……(中间解析与入库逻辑)
ExcelImportLogger::record($filename, $totalRows, $successCount, $failCount, $errorList);
这里有一个容易踩坑的地方:很多人习惯把日志调用放在 try-catch 的 finally 块里。但这样并不合适。因为如果解析中途抛出未被捕获的异常,错误列表就无法完整收集。最终写出的日志就像一份缺页的报告,不完整也就失去了参考价值。正确的做法是:等所有数据处理明确结束之后,再写入日志——不多不少,一步到位。
