Files
fdesk/app/inquiry/service/FbFileImportService.php
T
mzl92 eea13a2a58 Merge branch 'develop'
# Conflicts:
#	app/command/InquiryImportFbCommand.php
#	app/inquiry/utils/FbLeadHeaderMapper.php
2025-12-12 17:52:20 +08:00

202 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\inquiry\service;
use PhpOffice\PhpSpreadsheet\IOFactory;
use support\Db;
use app\inquiry\utils\FbLeadHeaderMapper;
use support\exception\BusinessException;
use support\Log;
class FbFileImportService
{
/**
* 导入指定目录下所有 .xlsx 文件
*/
public function importDir(string $dir, int $channelId): int
{
if (!is_dir($dir)) {
throw new BusinessException("目录不存在:{$dir}");
}
// 只扫描 .xlsx,之前处理过的 .xlsx.done 不会被匹配到
$files = glob($dir . DIRECTORY_SEPARATOR . '*.xlsx');
if (!$files) {
return 0;
}
$count = 0;
foreach ($files as $filePath) {
if (!is_file($filePath)) {
continue;
}
$doneFlag = $filePath . '.done';
if (is_file($doneFlag)) {
continue;
}
$rows = $this->importOneFile($filePath, $channelId);
$count++;
// 导入成功后,把文件重命名为 .xlsx.done,避免下次重复导入
$newPath = $filePath . '.done';
if (!@rename($filePath, $newPath)) {
Log::warning("FbFileImportService: 文件导入成功,但重命名失败:{$filePath} -> {$newPath}");
} else {
Log::info("FbFileImportService: 文件导入并标记完成:{$newPath}(行数:{$rows})");
}
}
return $count;
}
/**
* 导入单个 Excel 到 fb_leads
*/
public function importOneFile(string $filePath, int $channelId): int
{
if (!is_file($filePath)) {
throw new BusinessException("Excel 文件不存在:{$filePath}");
}
$spreadsheet = IOFactory::load($filePath);
$sheet = $spreadsheet->getActiveSheet();
$rows = $sheet->toArray(null, true, true, true);
if (empty($rows)) {
throw new BusinessException('Excel 内容为空:' . $filePath);
}
// 表头
$headerRow = array_shift($rows);
$mapper = new FbLeadHeaderMapper();
$res = $mapper->buildColumnFieldMap($headerRow);
$colToField = $res['colToField'];
$unknownCols = $res['unknownCols'];
$insertData = [];
$now = time();
$sourceFile = basename($filePath);
foreach ($rows as $row) {
$item = [];
foreach ($colToField as $colIndex => $field) {
$raw = $row[$colIndex] ?? null;
switch ($field) {
case 'is_organic':
$item['is_organic'] = $this->normalizeIsOrganic($raw);
break;
case 'fb_created_time':
// ✅ 方案B:写入 DATETIME 字符串
$item['fb_created_time'] = $this->normalizeCreatedTime($raw);
break;
default:
// 小增强:字符串统一 trim(避免 " xxx ")
if (is_string($raw)) {
$raw = trim($raw);
}
$item[$field] = $raw;
break;
}
}
// 未知列收集到 extra_data(宽松模式)
$extra = [];
foreach ($unknownCols as $colIndex => $rawHeader) {
$val = $row[$colIndex] ?? null;
if ($val === null || $val === '') continue;
$extra[$rawHeader] = is_string($val) ? trim($val) : $val;
}
$item['extra_data'] = $extra ? json_encode($extra, JSON_UNESCAPED_UNICODE) : null;
$item['source_file'] = $sourceFile;
$item['channel'] = $channelId;
$item['created_at'] = $now;
$item['updated_at'] = $now;
$item['sync_status'] = 0;
$insertData[] = $item;
}
if ($insertData) {
Db::table('fb_leads')->insert($insertData);
}
return count($insertData);
}
/**
* 规范化 is_organic 字段为 0/1
*/
protected function normalizeIsOrganic($raw): int
{
if (is_bool($raw)) {
return $raw ? 1 : 0;
}
$str = strtolower(trim((string)$raw));
if ($str === '') {
return 0;
}
return in_array($str, ['1', 'true', 'yes', 'organic'], true) ? 1 : 0;
}
/**
* ✅ 方案B:把 FB 创建时间统一转成 MySQL DATETIME(Y-m-d H:i:s)
*
* 支持:
* - "10/13/25" / "12/8/25"
* - "2025-12-08 13:22:11"
* - Excel 日期序列(数字)
*
* 解析失败:返回 null(让 DB 用默认值/允许为空)
* 注意:如果你的 fb_created_time 列是 NOT NULL 且无默认值,请把 null 改成 '1970-01-01 00:00:00'
*/
protected function normalizeCreatedTime($raw): ?string
{
if ($raw === null || $raw === '') {
return null;
}
// Excel 日期序列:数字
if (is_numeric($raw)) {
$n = (float)$raw;
// Excel serial date -> unix timestamp
$ts = (int)round(($n - 25569) * 86400);
if ($ts > 0) {
return date('Y-m-d H:i:s', $ts);
}
return null;
}
$s = trim((string)$raw);
if ($s === '') {
return null;
}
// 1) strtotime 能解析很多格式
$ts = strtotime($s);
if ($ts !== false) {
return date('Y-m-d H:i:s', $ts);
}
// 2) 兼容 m/d/yy(如 10/13/25)
if (preg_match('#^(\d{1,2})/(\d{1,2})/(\d{2})$#', $s, $m)) {
$mm = (int)$m[1];
$dd = (int)$m[2];
$yy = (int)$m[3];
$year = 2000 + $yy; // FB 导出通常是 20xx
$ts2 = mktime(0, 0, 0, $mm, $dd, $year);
return date('Y-m-d H:i:s', $ts2);
}
// 3) 兜底:无法解析
return null;
}
}