Files
fdesk/app/inquiry/utils/FbLeadHeaderMapper.php
T
mzl92 e6f68a9946 Merge branch 'master' into develop
# Conflicts:
#	app/inquiry/utils/FbLeadHeaderMapper.php
2025-12-12 18:37:36 +08:00

264 lines
9.1 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\utils;
use support\exception\BusinessException;
class FbLeadHeaderMapper
{
/**
* Excel 原始表头 => DB 字段名
* 注意:这里的 key 会经过 normalizeHeader() 处理后再匹配
*/
protected array $headerMap = [
// ===== 基础公共字段 =====
'id' => 'fb_lead_id',
'created_time' => 'fb_created_time',
'ad_id' => 'ad_id',
'ad_name' => 'ad_name',
'adset_id' => 'adset_id',
'adset_name' => 'adset_name',
'campaign_id' => 'campaign_id',
'campaign_name' => 'campaign_name',
'form_id' => 'form_id',
'form_name' => 'form_name',
'is_organic' => 'is_organic',
'platform' => 'platform',
'full_name' => 'full_name',
'phone' => 'phone',
'phone_number' => 'phone', // 兼容写法
'email' => 'email',
'lead_status' => 'lead_status',
'inbox_url' => 'inbox_url',
// ===== 车型相关:全部归到 car_model =====
'which_model_of_this_car_would_you_like_to_purchase' => 'car_model',
'which_car_model_would_you_like_to_purchase' => 'car_model',
'which_model_do_you_want_to_purchase' => 'car_model',
'which_model_would_you_like_to_purchase' => 'car_model',
'which_version_do_you_want_to_buy' => 'car_model', // 这里也归到车型/版本字段
// ===== 数量相关:建议归到 car_quantity_text(避免 1-2/3-10/10+)=====
// 你之前 car_quantity 用 int 时会截断,这里建议后续统一改成 car_quantity_text
'how_many_cars_would_you_like_to_buy' => 'car_quantity_text',
'_how_many_cars_would_you_like_to_buy' => 'car_quantity_text',
'how_many_cars_do_you_buy' => 'car_quantity_text',
'how_many_cars_do_you_want_to_purchase' => 'car_quantity_text',
'how_many_cars_do_you_want_to_buy' => 'car_quantity_text',
'what_quantity_do_you_want_to_buy' => 'car_quantity_text',
'what_quantity_do_you_wantv_to_buy' => 'car_quantity_text', // wantv 拼写兼容
// ===== 颜色相关:归到 favorite_color =====
'what_color_do_you_like' => 'favorite_color',
'which_color_would_you_like_to_purchase' => 'favorite_color',
'what_color_car_do_you_plan_to_buy' => 'favorite_color',
// ===== 用途相关:归到 usage_type =====
'are_you_using_it_for_business_or_personal_use' => 'usage_type',
'business_use_or_personal_use' => 'usage_type',
'personal_use_or_business_use' => 'usage_type',
// ===== 座位数相关 =====
'do_you_want_to_buy_six_seats_or_seven_seats' => 'seat_preference',
// ===== AVATR 11 =====
'which_model_of_the_avatr_11_do_you_want_to_purchase' => 'car_model',
'do_you_want_to_buy_a_four-wheel_drive_or_rear-wheel_drive_car' => 'drive_type',
];
/**
* 宽松模式下仍然强制要求存在的“核心 DB 字段”
* 你说的:联系人、电话、邮箱、国家、渠道
* - country / channel 不是从 Excel 映射,而是你代码里根据电话识别/配置注入,所以这里不用要求
*/
protected array $requiredDbFields = [
'full_name',
'phone',
'email',
];
/**
* 表头别名:标准化后表头 => headerMap里的key
*/
protected array $aliases = [
// ===== full_name =====
'name' => 'full_name',
'full_name' => 'full_name',
'full_name_(full_name)' => 'full_name',
'full_name_(full_name_)' => 'full_name',
'full_name_full_name' => 'full_name',
'full name' => 'full_name', // 万一没被替换成 _
'full_name(full_name)' => 'full_name',
'姓名' => 'full_name',
'姓名_full_name' => 'full_name',
'姓名(full_name)' => 'full_name',
// ===== phone =====
'phone' => 'phone',
'phone_number' => 'phone_number',
'phone number' => 'phone_number',
'telephone' => 'phone',
'tel' => 'phone',
'mobile' => 'phone',
'mobile_number' => 'phone',
'手机号' => 'phone',
'电话' => 'phone',
'电话号码' => 'phone',
'whatsapp' => 'phone', // 有的表单把 whatsapp 当电话
// ===== email =====
'email' => 'email',
'e-mail' => 'email',
'mail' => 'email',
'邮箱' => 'email',
'电子邮箱' => 'email',
];
/**
* 返回所有支持的 Excel 表头(标准化后的 key)
*/
public function getSupportedHeaders(): array
{
return array_keys($this->headerMap);
}
/**
* 单个表头映射:原始表头 -> DB 字段名
*/
public function mapHeader(string $rawHeader): ?string
{
$key = $this->normalizeHeader($rawHeader);
$key = $this->aliases[$key] ?? $key;
return $this->headerMap[$key] ?? null;
}
/**
* 旧的严格校验:保留(有时你可能还想用来排查某个表单的字段)
* - 有任何不认识的表头,就抛 BusinessException
*/
public function validateHeaders(array $rawHeaders): void
{
$unsupported = [];
foreach ($rawHeaders as $header) {
$header = (string)$header;
$key = $this->normalizeHeader($header);
// 允许空表头(多余空列)
if ($key === '') {
continue;
}
if (!isset($this->headerMap[$key])) {
$unsupported[] = $header;
}
}
if (!empty($unsupported)) {
$msg = 'Excel 表头包含未兼容字段:' . implode('、', $unsupported)
. '。请检查这些字段是否需要加入映射配置。';
throw new BusinessException($msg);
}
}
/**
* ✅ 宽松模式:根据第一行表头,构建映射
*
* 返回结构:
* [
* 'colToField' => [ 'A' => 'email', 'B' => 'phone', ... ],
* 'unknownCols' => [ 'C' => 'some_question?', ... ], // 未映射字段(后续可进富文本/extra_data)
* ]
*/
public function buildColumnFieldMap(array $rawHeaders): array
{
$colToField = [];
$unknownCols = [];
foreach ($rawHeaders as $index => $header) {
$raw = (string)$header;
$key = $this->normalizeHeader($raw);
if ($key === '') {
continue;
}
// $field = $this->headerMap[$key] ?? null;
$aliasKey = $this->aliases[$key] ?? $key;
$field = $this->headerMap[$aliasKey] ?? null;
if ($field === null) {
// 宽松:记录未知列(保留原始表头用于展示)
$unknownCols[$index] = trim($raw) !== '' ? trim($raw) : $key;
continue;
}
$colToField[$index] = $field;
}
// 核心字段校验:缺少就终止(保证你说的“联系人/电话/邮箱没问题”)
$this->assertRequiredFields($colToField);
return [
'colToField' => $colToField,
'unknownCols' => $unknownCols,
];
}
/**
* 核心字段校验:要求必须能映射出 requiredDbFields
*/
protected function assertRequiredFields(array $colToField): void
{
$fields = array_values($colToField);
foreach ($this->requiredDbFields as $req) {
if (!in_array($req, $fields, true)) {
throw new BusinessException("Excel 表头缺少必要字段映射:{$req}(请补充映射或别名)");
}
}
}
/**
* 表头标准化:
* - 去 BOM
* - trim
* - ✅ 统一处理尾部多余的 ? / _(解决 xxx?_ 与 xxx?)
*
* 注意:为了避免误伤中文表头,这里默认不做 strtolower / 空格替换。
* 若你后续想增强,可在注释处开启。
*/
protected function normalizeHeader(string $header): string
{
// 去掉 UTF-8 BOM
$header = preg_replace('/^\xEF\xBB\xBF/', '', $header);
$header = trim($header);
if ($header === '') return '';
// 统一全角括号
$header = str_replace(['(',')'], ['(',')'], $header);
// 小写化(对英文有效,中文不受影响)
$header = mb_strtolower($header);
// 把括号内容保留下来,但用下划线连接: "姓名(Full name)" -> "姓名_full_name"
$header = preg_replace('/\((.*?)\)/', '_$1', $header);
// 空白转下划线
$header = preg_replace('/\s+/', '_', $header);
// 连续下划线压缩
$header = preg_replace('/_+/', '_', $header);
// 去掉首尾下划线
$header = trim($header, '_');
// ✅ 去掉末尾连续的 "_" / "?"
$header = rtrim($header, "_?");
return $header;
}
}