汽车信息基础库
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehDictI18nService;
|
||||
|
||||
class VehDictI18nController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehDictI18nService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehDictMappingService;
|
||||
|
||||
class VehDictMappingController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehDictMappingService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehVehicleListingService;
|
||||
|
||||
class VehVehicleListingController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehVehicleListingService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehVehicleSkuService;
|
||||
use support\exception\BusinessException;
|
||||
use support\Request;
|
||||
|
||||
class VehVehicleSkuController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehVehicleSkuService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
if (isset($data['updated_at']))
|
||||
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'store');
|
||||
$res = $this->service->serviceStore($request, $data);
|
||||
return $this->success($res, '创建成功');
|
||||
} catch (\PDOException $e) {
|
||||
$errorCode = $e->getCode();
|
||||
$errorMessage = $e->getMessage();
|
||||
if ($errorCode == '23000') {
|
||||
if (strpos($errorMessage, "Duplicate entry") !== false && strpos($errorMessage, "uk_fingerprint") !== false) {
|
||||
return $this->error("该车辆指纹已存在,请勿重复提交");
|
||||
}
|
||||
// 其他类型的完整性约束错误
|
||||
throw new BusinessException("数据完整性错误", 0, $e);
|
||||
}
|
||||
// 重新抛出其他类型的异常
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'update');
|
||||
$res = $this->service->serviceUpdate($request, $data);
|
||||
return $this->success([], '更新成功');
|
||||
} catch (\PDOException $e) {
|
||||
$errorCode = $e->getCode();
|
||||
$errorMessage = $e->getMessage();
|
||||
if ($errorCode == '23000') {
|
||||
if (strpos($errorMessage, "Duplicate entry") !== false && strpos($errorMessage, "uk_fingerprint") !== false) {
|
||||
return $this->error("该车辆指纹已存在,请勿重复提交");
|
||||
}
|
||||
// 其他类型的完整性约束错误
|
||||
throw new BusinessException("数据完整性错误", 0, $e);
|
||||
}
|
||||
// 重新抛出其他类型的异常
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$data = [
|
||||
'sku_code' , // 'SKU编码,系统生成',
|
||||
'fingerprint' , // '规格指纹(sha256)去重',
|
||||
'brand_code' , // '品牌代码,如 BYD、HYUNDAI、KIA',
|
||||
'brand_name' , // '品牌名称展示,如 比亚迪、现代、起亚',
|
||||
'series_code' , // '车系代码,如 ELANTRA、SONGPLUS',
|
||||
'series_name' , // '车系名称展示',
|
||||
'model_code' , // '车型/配置代码(工厂/内部编码)',
|
||||
'model_name' , // '车型/配置名称(可读,如 伊兰特 1.5L、海狮07)',
|
||||
'model_year' , // '年款(YYYY)',
|
||||
'trim_code' , // '版型代码(TRIM)',
|
||||
'trim_level_code' , // '配置等级代码(TRIM_LEVEL)',
|
||||
'edition_code' , // '特别版代码(EDITION)',
|
||||
'trim' , // '版型/细分配置(如 TOP旗舰版、荣耀版)',
|
||||
'trim_level' , // '配置等级(豪华型/旗舰型等,便于筛选)',
|
||||
'edition_name' , // '特别版/纪念版(如 荣耀版、致敬版等)',
|
||||
'body_type_code' , // '车身类型:SEDAN/SUV/HB/MPV/Pickup',
|
||||
'drive_type_code' , // '驱动:FWD/RWD/AWD/4WD',
|
||||
'motor_layout_code' , // '电驱/电机布置(MOTOR_LAYOUT):FWD/RWD/AWD/DM-i/DM-p 等',
|
||||
'fuel_type_code' , // '能源:GAS/HEV/PHEV/EV/EREV',
|
||||
'engine_disp_l' , // '排量(L),如 1.5;纯电为空',
|
||||
'engine_turbo' , // '0' COMMENT '是否涡轮(0/1)',
|
||||
'engine_power_kw' , // '最大功率(kW)',
|
||||
'battery_kwh' , // '电池容量(kWh)',
|
||||
'transmission_code' , // '变速箱:MT/AT/CVT/DCT/1SPEED',
|
||||
'gears' , // '挡位数(EV多为1)',
|
||||
'range_km' , // '续航里程(km)',
|
||||
'range_std_code' , // '续航标准:CLTC/WLTP/EPA/NEDC',
|
||||
'doors' , // '门数',
|
||||
'seats' , // '座位数',
|
||||
'weight_kg' , // '整备质量(kg)',
|
||||
'dimensions_mm' , // '尺寸(mm):{length,width,height,wheelbase}',
|
||||
'color_exterior_code' , // '外观色代码',
|
||||
'color_exterior_text' , // '外观色名称',
|
||||
'color_interior_code' , // '内饰色代码',
|
||||
'color_interior_text' , // '内饰色名称',
|
||||
'options_official' , // '官方选装(结构化 JSON)',
|
||||
'features' , // '功能/安全/智能配置(结构化 JSON)',
|
||||
'emission_std_code' , // '排放标准:CHN6/EURO6/EPA',
|
||||
'origin_country' , // '原产国 ISO-3166-1 alpha-2',
|
||||
'currency' , // 'CNY' COMMENT '币种 ISO-4217',
|
||||
'msrp' , // '建议零售价/参考价',
|
||||
'is_active' , // '1' COMMENT '启用(1)/禁用(0)',
|
||||
'corp_id' , // '0' COMMENT '租户/公司(全局库可写0)',
|
||||
'remark' , // '备注',
|
||||
'created_by' , // '0',
|
||||
'updated_by' , // '0',
|
||||
];
|
||||
|
||||
|
||||
$data = [
|
||||
'fingerprint' , // '规格指纹(sha256)去重',
|
||||
'brand_name' , // '品牌名称展示,如 比亚迪、现代、起亚',
|
||||
'series_name' , // '车系名称展示',
|
||||
'model_name' , // '车型/配置名称(可读,如 伊兰特 1.5L、海狮07)',
|
||||
'model_year' , // '年款(YYYY)',
|
||||
'trim' , // '版型/细分配置(如 TOP旗舰版、荣耀版)',
|
||||
'trim_level' , // '配置等级(豪华型/旗舰型等,便于筛选)',
|
||||
'edition_name' , // '特别版/纪念版(如 荣耀版、致敬版等)',
|
||||
'engine_disp_l' , // '排量(L),如 1.5;纯电为空',
|
||||
'engine_turbo' , // '0' COMMENT '是否涡轮(0/1)',
|
||||
'engine_power_kw' , // '最大功率(kW)',
|
||||
'battery_kwh' , // '电池容量(kWh)',
|
||||
'gears' , // '挡位数(EV多为1)',
|
||||
'range_km' , // '续航里程(km)',
|
||||
'doors' , // '门数',
|
||||
'seats' , // '座位数',
|
||||
'weight_kg' , // '整备质量(kg)',
|
||||
'dimensions_mm' , // '尺寸(mm):{length,width,height,wheelbase}',
|
||||
'color_exterior_text' , // '外观色名称',
|
||||
'color_interior_text' , // '内饰色名称',
|
||||
'options_official' , // '官方选装(结构化 JSON)',
|
||||
'features' , // '功能/安全/智能配置(结构化 JSON)',
|
||||
'origin_country' , // '原产国 ISO-3166-1 alpha-2',
|
||||
'currency' , // 'CNY' COMMENT '币种 ISO-4217',
|
||||
'msrp' , // '建议零售价/参考价',
|
||||
'is_active' , // '1' COMMENT '启用(1)/禁用(0)',
|
||||
'corp_id' , // '0' COMMENT '租户/公司(全局库可写0)',
|
||||
'remark' , // '备注',
|
||||
'created_by' , // '0',
|
||||
'updated_by' , // '0',
|
||||
|
||||
'body_type', // '车身类型'
|
||||
'drive_type', // '驱动:FWD/RWD/AWD/4WD',
|
||||
'motor_layout', // '电机布置(MOTOR_LAYOUT):FWD/RWD/AWD/DM-i/DM-p 等'
|
||||
'fuel_type', // '能源:GAS/HEV/PHEV/EV/EREV'
|
||||
'transmission', // '变速箱:MT/AT/CVT/DCT/1SPEED'
|
||||
'range_std', // '续航标准:CLTC/WLTP/EPA/NEDC'
|
||||
'emission_std', // '排放标准:CHN6/EURO6/EPA'
|
||||
|
||||
'sku_code' , // 'SKU编码,系统生成',
|
||||
'brand_code' , // '品牌代码,如 BYD、HYUNDAI、KIA',
|
||||
'series_code' , // '车系代码,如 ELANTRA、SONGPLUS',
|
||||
'model_code' , // '车型/配置代码(工厂/内部编码)',
|
||||
'trim_code' , // '版型代码(TRIM)',
|
||||
'trim_level_code' , // '配置等级代码(TRIM_LEVEL)',
|
||||
'edition_code' , // '特别版代码(EDITION)',
|
||||
'color_exterior_code' , // '外观色代码',
|
||||
'color_interior_code' , // '内饰色代码',
|
||||
|
||||
'body_type_code' , // '车身类型:SEDAN/SUV/HB/MPV/Pickup',
|
||||
'drive_type_code' , // '驱动:FWD/RWD/AWD/4WD',
|
||||
'motor_layout_code' , // '电驱/电机布置(MOTOR_LAYOUT):FWD/RWD/AWD/DM-i/DM-p 等',
|
||||
'fuel_type_code' , // '能源:GAS/HEV/PHEV/EV/EREV',
|
||||
'transmission_code' , // '变速箱:MT/AT/CVT/DCT/1SPEED',
|
||||
'range_std_code' , // '续航标准:CLTC/WLTP/EPA/NEDC',
|
||||
'emission_std_code' , // '排放标准:CHN6/EURO6/EPA',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\VehVehicleUnitService;
|
||||
|
||||
class VehVehicleUnitController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new VehVehicleUnitService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
// protected function indexFormat($data)
|
||||
// {
|
||||
// if (isset($data['created_at']))
|
||||
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
// if (isset($data['updated_at']))
|
||||
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
|
||||
// return $data;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
/**
|
||||
* Fingerprint
|
||||
*
|
||||
* 目标:
|
||||
* - 默认“强制所有字段参与计算”,保证相同记录生成同一指纹
|
||||
* - 通过统一规范化策略,避免无意义差异(空白、大小写、JSON键序)导致指纹抖动
|
||||
* - 仅排除技术字段:id、fingerprint(可通过参数自行增删)
|
||||
*
|
||||
* 用法:
|
||||
* $hash = Fingerprint::makeFromArray($payload); // 创建前
|
||||
* $hash = Fingerprint::makeFromModel($skuModel); // 已有模型
|
||||
* $hash = Fingerprint::makeFromArray($payload, ['exclude'=>['msrp']]); // 可选排除
|
||||
*/
|
||||
class Fingerprint
|
||||
{
|
||||
/** 你的 veh_vehicle_sku 所有列(保持与表结构一致,顺序无关但建议完整) */
|
||||
public const COLS = [
|
||||
'id','sku_code','fingerprint',
|
||||
'brand_code','brand_name','series_code','series_name',
|
||||
'model_code','model_name','model_year',
|
||||
'trim_code','trim_level_code','edition_code',
|
||||
'trim','trim_level','edition_name',
|
||||
'body_type_code','drive_type_code','motor_layout_code','fuel_type_code',
|
||||
'engine_disp_l','engine_turbo','engine_power_kw','battery_kwh',
|
||||
'transmission_code','gears','range_km','range_std_code',
|
||||
'doors','seats','weight_kg','dimensions_mm',
|
||||
'color_exterior_code','color_exterior_text',
|
||||
'color_interior_code','color_interior_text',
|
||||
'options_official','features','emission_std_code',
|
||||
'origin_country','currency','msrp',
|
||||
'is_active','corp_id','remark',
|
||||
'created_by','updated_by','created_at','updated_at',
|
||||
];
|
||||
|
||||
/** 默认剔除:技术/循环依赖字段 */
|
||||
public const EXCLUDE_DEFAULT = [
|
||||
'id','sku_code','fingerprint', 'dimensions_mm', 'options_official', 'features',
|
||||
'origin_country','currency','msrp',
|
||||
'is_active','corp_id','remark',
|
||||
'created_by','updated_by','created_at','updated_at'
|
||||
];
|
||||
|
||||
/** 识别为“code类”的字段模式(统一大写) */
|
||||
private const CODE_FIELD_REGEX = '/(_code|_country|^currency)$/i';
|
||||
|
||||
/** 识别为 JSON 字段 */
|
||||
private const JSON_FIELDS = ['dimensions_mm','options_official','features'];
|
||||
|
||||
/**
|
||||
* 从 Eloquent 模型生成
|
||||
*/
|
||||
public static function makeFromModel($model, array $opts = []): string
|
||||
{
|
||||
return self::makeFromArray($model->toArray(), $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数组生成指纹
|
||||
* @param array $row 一般为 $_POST 合并后的业务数据或 $model->toArray()
|
||||
* @param array $opts ['exclude'=>[], 'extra'=>[], 'salt'=>string]
|
||||
* - exclude: 额外排除的字段(默认仅排除 id,fingerprint)
|
||||
* - extra : 额外拼入的键值(例如上下文、租户策略),会参与计算
|
||||
* - salt : 额外加盐字符串
|
||||
*/
|
||||
public static function makeFromArray(array $row, array $opts = []): string
|
||||
{
|
||||
$exclude = array_values(array_unique(array_merge(self::EXCLUDE_DEFAULT, $opts['exclude'] ?? [])));
|
||||
$extra = $opts['extra'] ?? [];
|
||||
$salt = $opts['salt'] ?? '';
|
||||
|
||||
// 1) 统一抽取“表中定义的字段” + extra,缺失字段也要纳入(值按 NULL 处理)
|
||||
$data = [];
|
||||
foreach (self::COLS as $col) {
|
||||
if (in_array($col, $exclude, true)) continue;
|
||||
$data[$col] = array_key_exists($col, $row) ? $row[$col] : null;
|
||||
}
|
||||
// extra 也参与
|
||||
foreach ($extra as $k => $v) {
|
||||
if (!is_string($k) || $k === '') continue;
|
||||
$data['__extra__'.$k] = $v;
|
||||
}
|
||||
|
||||
// 2) 规范化(按字段特征)
|
||||
$canon = [];
|
||||
foreach ($data as $k => $v) {
|
||||
$canon[$k] = self::normalize($k, $v);
|
||||
}
|
||||
|
||||
// 3) 键名排序,确保顺序无关
|
||||
ksort($canon, SORT_STRING);
|
||||
|
||||
// 4) 拼成稳定串: key=value | key=value...
|
||||
$parts = [];
|
||||
foreach ($canon as $k => $v) {
|
||||
// 为空也必须占位,避免 key 消失影响其他键的意义
|
||||
$parts[] = $k.'='.$v;
|
||||
}
|
||||
if ($salt !== '') {
|
||||
$parts[] = '__salt__='.$salt;
|
||||
}
|
||||
$plain = implode('|', $parts);
|
||||
|
||||
// 5) sha256
|
||||
return hash('sha256', $plain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化单个字段
|
||||
* - NULL -> 特殊占位 "∅"(避免与空串混淆)
|
||||
* - 布尔/整数/浮点 -> 统一数字字符串
|
||||
* - code类字段 -> 去首尾空白 & 转大写
|
||||
* - 普通字符串 -> trim & 压缩连续空白为单空格
|
||||
* - JSON字段 -> 解析后做“键排序 + 值规范化 + 索引数组可排序”,再无空白编码
|
||||
*/
|
||||
private static function normalize(string $field, $value): string
|
||||
{
|
||||
if ($value === null) return '∅';
|
||||
|
||||
// 数值类型
|
||||
if (is_int($value)) return (string)$value;
|
||||
if (is_bool($value)) return $value ? '1' : '0';
|
||||
|
||||
if (is_float($value) || (is_string($value) && is_numeric($value) && self::looksLikeNumberField($field))) {
|
||||
// 统一浮点格式,最多 6 位小数并去尾零
|
||||
$num = (float)$value;
|
||||
$s = number_format($num, 6, '.', '');
|
||||
$s = rtrim(rtrim($s, '0'), '.');
|
||||
return $s === '' ? '0' : $s;
|
||||
}
|
||||
|
||||
// JSON 字段
|
||||
if (in_array($field, self::JSON_FIELDS, true)) {
|
||||
return self::normalizeJson($value);
|
||||
}
|
||||
|
||||
// 字符串
|
||||
if (is_string($value)) {
|
||||
$s = trim($value);
|
||||
// code类:统一大写
|
||||
if (preg_match(self::CODE_FIELD_REGEX, $field)) {
|
||||
$s = mb_strtoupper($s, 'UTF-8');
|
||||
} else {
|
||||
// 普通文本:压缩连续空白
|
||||
$s = preg_replace('/\s+/u', ' ', $s);
|
||||
}
|
||||
return $s === '' ? '' : $s;
|
||||
}
|
||||
|
||||
// 数组(极少出现,按 JSON 处理)
|
||||
if (is_array($value)) {
|
||||
return self::normalizeJson($value);
|
||||
}
|
||||
|
||||
// 其他类型(对象等):转 JSON 后处理
|
||||
return self::normalizeJson($value);
|
||||
}
|
||||
|
||||
/** 判断这个字段是否“看起来像数值字段” */
|
||||
private static function looksLikeNumberField(string $field): bool
|
||||
{
|
||||
static $numKeys = [
|
||||
'engine_disp_l','engine_power_kw','battery_kwh','gears',
|
||||
'range_km','doors','seats','weight_kg','msrp','model_year',
|
||||
'created_at','updated_at','corp_id','is_active'
|
||||
];
|
||||
return in_array($field, $numKeys, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 规范化:
|
||||
* - 字符串:若能 json_decode 则按 JSON;否则当普通字符串再规范化
|
||||
* - 对象:键名排序;值递归规范化
|
||||
* - 索引数组:元素递归规范化后,按字符串值排序(视为集合,避免顺序抖动)
|
||||
* - 最终 json_encode(JSON_UNESCAPED_UNICODE|UNESCAPED_SLASHES)
|
||||
*/
|
||||
private static function normalizeJson($val): string
|
||||
{
|
||||
// 已是数组/对象
|
||||
if (is_array($val)) {
|
||||
$normalized = self::canonArray($val);
|
||||
return json_encode($normalized, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
// 字符串尝试解析
|
||||
if (is_string($val)) {
|
||||
$val = trim($val);
|
||||
if ($val === '') return '""';
|
||||
$decoded = json_decode($val, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$normalized = self::canonArray($decoded);
|
||||
return json_encode($normalized, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
// 非 JSON 字符串,按普通字符串规范化
|
||||
$s = preg_replace('/\s+/u', ' ', $val);
|
||||
return json_encode($s, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
// 其他类型:直接编码
|
||||
return json_encode($val, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
/** 递归规范化数组/对象 */
|
||||
private static function canonArray($arr)
|
||||
{
|
||||
if (!is_array($arr)) return $arr;
|
||||
|
||||
// 关联数组(对象)
|
||||
if (self::isAssoc($arr)) {
|
||||
$out = [];
|
||||
ksort($arr, SORT_STRING);
|
||||
foreach ($arr as $k => $v) {
|
||||
$out[(string)$k] = self::canonValue($v);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// 索引数组(当集合处理:元素字符串化后排序)
|
||||
$tmp = [];
|
||||
foreach ($arr as $v) {
|
||||
$tmp[] = self::canonValue($v);
|
||||
}
|
||||
// 将元素转为字符串再排序,确保稳定
|
||||
usort($tmp, function($a, $b) {
|
||||
$sa = is_scalar($a) ? (string)$a : json_encode($a, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
$sb = is_scalar($b) ? (string)$b : json_encode($b, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
return strcmp($sa, $sb);
|
||||
});
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
private static function canonValue($v)
|
||||
{
|
||||
if (is_array($v)) return self::canonArray($v);
|
||||
if (is_bool($v)) return $v ? 1 : 0;
|
||||
if (is_int($v)) return $v;
|
||||
if (is_float($v)) {
|
||||
$s = number_format($v, 6, '.', '');
|
||||
$s = rtrim(rtrim($s, '0'), '.');
|
||||
return $s === '' ? '0' : $s;
|
||||
}
|
||||
if (is_string($v)) {
|
||||
$s = trim($v);
|
||||
$s = preg_replace('/\s+/u', ' ', $s);
|
||||
return $s;
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
private static function isAssoc(array $arr): bool
|
||||
{
|
||||
// 有任意一个键不是 0..n 的递增整数,就视为关联数组
|
||||
$i = 0;
|
||||
foreach ($arr as $k => $_) {
|
||||
if ($k !== $i++) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace app\common\logic;
|
||||
|
||||
final class ModelNaming
|
||||
{
|
||||
/** 生成稳定的 model_code(只用 *_code 与关键数字) */
|
||||
public static function buildModelCode(array $s): string
|
||||
{
|
||||
$brand = self::tok($s['brand_code'] ?? '');
|
||||
$series = self::tok($s['series_code'] ?? '');
|
||||
$fuel = self::tok($s['fuel_type_code'] ?? 'UNK');
|
||||
|
||||
$parts = [$brand, $series];
|
||||
$key = [];
|
||||
|
||||
switch ($fuel) {
|
||||
case 'EV':
|
||||
$key[] = 'EV';
|
||||
if (!empty($s['range_km'])) {
|
||||
$key[] = (string) (round((int)$s['range_km'] / 10) * 10); // 到十位
|
||||
} elseif (!empty($s['battery_kwh'])) {
|
||||
$key[] = self::numToken($s['battery_kwh']) . 'KWH';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'GAS':
|
||||
if (!empty($s['engine_disp_l'])) $key[] = self::dispToken($s['engine_disp_l']); // 1.5L -> 15L
|
||||
if (!empty($s['engine_turbo']) && (int)$s['engine_turbo'] === 1) $key[] = 'T';
|
||||
if (!empty($s['transmission_code'])) $key[] = self::tok($s['transmission_code']);
|
||||
break;
|
||||
|
||||
case 'HEV':
|
||||
case 'PHEV':
|
||||
case 'EREV':
|
||||
$key[] = $fuel;
|
||||
if (!empty($s['engine_disp_l'])) $key[] = self::dispToken($s['engine_disp_l']);
|
||||
if (!empty($s['motor_layout_code'])) $key[] = self::tok($s['motor_layout_code']); // 如 DM-I/DM-P/AWD
|
||||
break;
|
||||
|
||||
default:
|
||||
$key[] = $fuel;
|
||||
}
|
||||
|
||||
if (!empty($s['drive_type_code'])) $key[] = self::tok($s['drive_type_code']);
|
||||
|
||||
$parts[] = implode('_', array_filter($key));
|
||||
return strtoupper(implode('_', array_filter($parts)));
|
||||
}
|
||||
|
||||
/** 生成中文可读的 model_name(走快照中文,不依赖 i18n) */
|
||||
public static function buildModelName(array $s): string
|
||||
{
|
||||
$seriesName = trim((string)($s['series_name'] ?? $s['series_code'] ?? ''));
|
||||
$yearPart = !empty($s['model_year']) ? ($s['model_year'] . '款 ') : '';
|
||||
|
||||
$fuel = strtoupper($s['fuel_type_code'] ?? '');
|
||||
$tail = '';
|
||||
|
||||
if ($fuel === 'EV') {
|
||||
$range = !empty($s['range_km']) ? (int)$s['range_km'] . 'KM ' : '';
|
||||
$drive = trim((string)($s['drive_type'] ?? $s['drive_type_code'] ?? ''));
|
||||
$tail = "EV {$range}{$drive}";
|
||||
} elseif ($fuel === 'GAS') {
|
||||
$disp = (!empty($s['engine_disp_l']))
|
||||
? rtrim(rtrim(number_format((float)$s['engine_disp_l'], 1, '.', ''), '0'), '.') . 'L '
|
||||
: '';
|
||||
$trans = trim((string)($s['transmission'] ?? $s['transmission_code'] ?? ''));
|
||||
$tail = "{$disp}{$trans}";
|
||||
} else {
|
||||
$tail = trim((string)($s['fuel_type'] ?? $fuel));
|
||||
}
|
||||
|
||||
$extras = [];
|
||||
foreach (['trim','trim_level','edition_name'] as $k) {
|
||||
if (!empty($s[$k])) $extras[] = trim((string)$s[$k]);
|
||||
}
|
||||
$extraStr = $extras ? ' ' . implode(' ', $extras) : '';
|
||||
|
||||
return trim($seriesName . ' ' . $yearPart . $tail . $extraStr);
|
||||
}
|
||||
|
||||
/* ===== Helpers ===== */
|
||||
|
||||
private static function tok($v): string
|
||||
{
|
||||
$v = strtoupper((string)$v);
|
||||
return preg_replace('/[^A-Z0-9_]/', '', $v ?? '');
|
||||
}
|
||||
|
||||
private static function numToken($v): string
|
||||
{
|
||||
return preg_replace('/[^0-9]/', '', (string)$v);
|
||||
}
|
||||
|
||||
/** 1.5L -> 15L;2.0L -> 20L(便于 code 稳定检索) */
|
||||
private static function dispToken($l): string
|
||||
{
|
||||
$x = (float)$l;
|
||||
return (string)round($x * 10) . 'L';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\model\VehVehicleSku;
|
||||
|
||||
/**
|
||||
* SKU 生成器
|
||||
* - 输入: 车辆规格数组(以 *_code 为主)
|
||||
* - 输出: 稳定、人读友好的 sku_code (<= 40 默认)
|
||||
*/
|
||||
final class SkuCodeBuilder
|
||||
{
|
||||
/** 压缩映射(可按需扩充) */
|
||||
private const ABBR = [
|
||||
// level
|
||||
'FLAGSHIP' => 'FLG',
|
||||
'PREMIUM' => 'PRM',
|
||||
'STANDARD' => 'STD',
|
||||
'DELUXE' => 'DLX',
|
||||
// edition
|
||||
'HONOR' => 'HNR',
|
||||
'CHAMPION' => 'CHP',
|
||||
'ANNIV' => 'ANV',
|
||||
// trans
|
||||
'1SPEED' => '1SP',
|
||||
// drive/fuel/body 等一般已短,不必压缩
|
||||
];
|
||||
|
||||
/** 入口:生成 sku_code */
|
||||
public static function build(array $s, array $opts = []): string
|
||||
{
|
||||
$maxLen = $opts['max_len'] ?? 40;
|
||||
$ensureUnique = $opts['ensure_unique'] ?? true;
|
||||
|
||||
$tokens = [
|
||||
'brand' => self::tok($s['brand_code'] ?? ''),
|
||||
'series' => self::tok($s['series_code'] ?? ''),
|
||||
'year' => isset($s['model_year']) ? (string)intval($s['model_year']) : '',
|
||||
'fuel' => self::tok($s['fuel_type_code'] ?? ''),
|
||||
'drive' => self::tok($s['drive_type_code'] ?? ''),
|
||||
'spec' => self::buildSpec($s, self::tok($s['fuel_type_code'] ?? '')),
|
||||
'level' => self::abbr(self::tok($s['trim_level_code'] ?? '')),
|
||||
'edition' => self::abbr(self::tok($s['edition_code'] ?? '')),
|
||||
];
|
||||
|
||||
// 过滤空段并按既定顺序拼接
|
||||
$order = ['brand','series','year','fuel','drive','spec','level','edition'];
|
||||
$parts = [];
|
||||
foreach ($order as $k) {
|
||||
if (!empty($tokens[$k])) $parts[$k] = $tokens[$k];
|
||||
}
|
||||
|
||||
$sku = self::normalizeSku(implode('-', $parts));
|
||||
|
||||
if (strlen($sku) > $maxLen) {
|
||||
$sku = self::shrink($parts, $maxLen); // 传入“带键名的 parts”
|
||||
}
|
||||
|
||||
if ($ensureUnique) {
|
||||
$sku = self::ensureUnique($sku, $maxLen);
|
||||
}
|
||||
return $sku;
|
||||
}
|
||||
|
||||
/** 构造规格位(SPEC),按能源类型差异化 */
|
||||
private static function buildSpec(array $s, string $fuel): string
|
||||
{
|
||||
$fuel = strtoupper($fuel);
|
||||
if ($fuel === 'EV') {
|
||||
if (!empty($s['range_km'])) {
|
||||
$r = (int)$s['range_km'];
|
||||
$r10 = (int)round($r / 10) * 10; // 到 10 位
|
||||
return (string)$r10;
|
||||
}
|
||||
if (!empty($s['battery_kwh'])) {
|
||||
return self::numToken($s['battery_kwh']) . 'KWH';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($fuel === 'GAS') {
|
||||
$bits = [];
|
||||
if (!empty($s['engine_disp_l'])) {
|
||||
$bits[] = self::dispToken($s['engine_disp_l']); // 1.5L -> 15L
|
||||
}
|
||||
if (!empty($s['engine_turbo']) && intval($s['engine_turbo']) === 1) {
|
||||
$bits[] = 'T';
|
||||
}
|
||||
if (!empty($s['transmission_code'])) {
|
||||
$bits[] = self::abbr(self::tok($s['transmission_code']));
|
||||
}
|
||||
return implode('', $bits); // 如 15LTCVT
|
||||
}
|
||||
|
||||
// HEV/PHEV/EREV 等
|
||||
$bits = [];
|
||||
if (!empty($s['engine_disp_l'])) {
|
||||
$bits[] = self::dispToken($s['engine_disp_l']); // 可空
|
||||
}
|
||||
if (!empty($s['motor_layout_code'])) {
|
||||
$bits[] = self::tok($s['motor_layout_code']); // 如 AWD / DM-I / DM-P
|
||||
}
|
||||
return implode('', $bits);
|
||||
}
|
||||
|
||||
/** 冲突检测并追加短校验(全局唯一) */
|
||||
private static function ensureUnique(string $sku, int $maxLen): string
|
||||
{
|
||||
if (!VehVehicleSku::where('sku_code', $sku)->exists()) {
|
||||
return $sku;
|
||||
}
|
||||
// 用 crc32 生成 5~6 位 base36 校验(稳定且短)
|
||||
$crc = self::base36Crc($sku);
|
||||
$suffix = '-X' . $crc;
|
||||
|
||||
if (strlen($sku . $suffix) > $maxLen) {
|
||||
$sku = substr($sku, 0, max(0, $maxLen - strlen($suffix)));
|
||||
$sku = rtrim($sku, '-'); // 避免尾部 '-'
|
||||
}
|
||||
$sku .= $suffix;
|
||||
|
||||
// 若极端情况下仍冲突,追加递增计数
|
||||
$i = 2;
|
||||
$candidate = $sku;
|
||||
while (VehVehicleSku::where('sku_code', $candidate)->exists()) {
|
||||
$extra = '-'.$i;
|
||||
$cut = max(0, $maxLen - strlen($extra));
|
||||
$candidate = substr($sku, 0, $cut) . $extra;
|
||||
$i++;
|
||||
}
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
/** 把 token 数组尽量压到 maxLen(逐步压缩策略) */
|
||||
private static function shrink(array $partsByKey, int $maxLen): string
|
||||
{
|
||||
// 1) 先对 series/level/edition 做激进压缩
|
||||
$p = $partsByKey;
|
||||
foreach (['series','level','edition'] as $k) {
|
||||
if (!empty($p[$k])) $p[$k] = self::heavyAbbr($p[$k]);
|
||||
}
|
||||
$sku = self::normalizeSku(implode('-', $p));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
|
||||
// 2) 依次移除不太关键的信息:edition → level(若存在)
|
||||
if (!empty($p['edition'])) { unset($p['edition']); }
|
||||
$sku = self::normalizeSku(implode('-', $p));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
|
||||
if (!empty($p['level'])) { unset($p['level']); }
|
||||
$sku = self::normalizeSku(implode('-', $p));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
|
||||
// 3) 压缩 spec(如去 KM/KWH,仅保留核心)
|
||||
if (!empty($p['spec'])) { $p['spec'] = self::compactSpec($p['spec']); }
|
||||
$sku = self::normalizeSku(implode('-', $p));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
|
||||
// 4) 仍超长:从右向左截断次要段(spec/drive/year),保留前三段(brand/series/fuel)优先
|
||||
$dropOrder = ['spec','drive','year'];
|
||||
$q = $p;
|
||||
foreach ($dropOrder as $k) {
|
||||
if (!empty($q[$k])) { unset($q[$k]); }
|
||||
$sku = self::normalizeSku(implode('-', $q));
|
||||
if (strlen($sku) <= $maxLen) return $sku;
|
||||
}
|
||||
|
||||
// 5) 最后兜底:硬截到 maxLen,后续 ensureUnique 会追加 -X 校验并避免冲突
|
||||
return substr(self::normalizeSku(implode('-', $p)), 0, $maxLen);
|
||||
}
|
||||
|
||||
/* ===== Helpers ===== */
|
||||
|
||||
private static function tok($v): string
|
||||
{
|
||||
$v = strtoupper((string)$v);
|
||||
return preg_replace('/[^A-Z0-9_]/', '', $v ?? '');
|
||||
}
|
||||
|
||||
private static function abbr(string $t): string
|
||||
{
|
||||
return self::ABBR[$t] ?? $t;
|
||||
}
|
||||
|
||||
private static function heavyAbbr(string $t): string
|
||||
{
|
||||
// 更激进:去除元音,连字符化
|
||||
$t = str_replace(['STANDARD','FLAGSHIP','PREMIUM','DELUXE'], ['STD','FLG','PRM','DLX'], $t);
|
||||
$t = preg_replace('/[AEIOU]/', '', $t);
|
||||
return $t;
|
||||
}
|
||||
|
||||
private static function compactSpec(string $s): string
|
||||
{
|
||||
// 规则:去 KM/KWH 等单位,只保留数字与核心字母
|
||||
$s = strtoupper($s);
|
||||
$s = str_replace(['KM','KWH'], '', $s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
private static function numToken($v): string
|
||||
{
|
||||
return preg_replace('/[^0-9]/', '', (string)$v);
|
||||
}
|
||||
|
||||
/** 1.5L -> 15L;2.0L -> 20L */
|
||||
private static function dispToken($l): string
|
||||
{
|
||||
$x = (float)$l;
|
||||
return (string)round($x * 10) . 'L';
|
||||
}
|
||||
|
||||
private static function base36Crc(string $s): string
|
||||
{
|
||||
$crc = sprintf('%u', crc32($s)); // 无符号
|
||||
$n = (int)$crc;
|
||||
$chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$res = '';
|
||||
do {
|
||||
$res = $chars[$n % 36] . $res;
|
||||
$n = intdiv($n, 36);
|
||||
} while ($n > 0);
|
||||
return substr($res, -6); // 取后6位足够
|
||||
}
|
||||
|
||||
private static function normalizeSku(string $s): string
|
||||
{
|
||||
// 连续的 '-' 合并,首尾修剪
|
||||
$s = preg_replace('/-+/', '-', $s);
|
||||
$s = trim($s, '-');
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\VehDictI18n;
|
||||
|
||||
class VehDictI18nLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehDictI18n::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\VehDictMapping;
|
||||
|
||||
class VehDictMappingLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehDictMapping::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\VehVehicleListing;
|
||||
|
||||
class VehVehicleListingLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehVehicleListing::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\VehVehicleSku;
|
||||
|
||||
class VehVehicleSkuLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehVehicleSku::class;
|
||||
|
||||
|
||||
/**
|
||||
* 指纹 = “稳定可复现的事实串”做 sha256
|
||||
* - 仅纳入稳定维度:品牌/车系/年款/能源/驱动/变速/车身 + 关键数值
|
||||
* - 不纳入营销名(trim/edition)、颜色文本等
|
||||
* - corp 维度:若你未来希望租户内去重,可传 $scopeCorp=true
|
||||
*/
|
||||
public static function fingerprintMake(array $sku, bool $scopeCorp = false): string
|
||||
{
|
||||
$norm = function ($v) {
|
||||
if ($v === null || $v === '') return '';
|
||||
if (is_float($v)) return rtrim(rtrim(number_format($v, 3, '.', ''), '0'), '.'); // 规范小数
|
||||
return (string)$v;
|
||||
};
|
||||
|
||||
$parts = [
|
||||
$scopeCorp ? ($sku['corp_id'] ?? 0) : '',
|
||||
|
||||
strtoupper($sku['brand_code'] ?? ''),
|
||||
strtoupper($sku['series_code'] ?? ''),
|
||||
(int)($sku['model_year'] ?? 0),
|
||||
strtoupper($sku['fuel_type_code'] ?? ''),
|
||||
strtoupper($sku['drive_type_code']?? ''),
|
||||
strtoupper($sku['transmission_code'] ?? ''),
|
||||
strtoupper($sku['body_type_code'] ?? ''),
|
||||
|
||||
$norm($sku['engine_disp_l'] ?? null),
|
||||
(int)($sku['engine_turbo'] ?? 0),
|
||||
$norm($sku['battery_kwh'] ?? null),
|
||||
(int)($sku['range_km'] ?? 0),
|
||||
|
||||
strtoupper($sku['range_std_code'] ?? ''),
|
||||
strtoupper($sku['emission_std_code'] ?? ''),
|
||||
];
|
||||
|
||||
$plain = implode('|', $parts);
|
||||
return hash('sha256', $plain);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\VehVehicleUnit;
|
||||
|
||||
class VehVehicleUnitLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehVehicleUnit::class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class VehDictI18n extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'veh_dict_i18n';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
/**
|
||||
* 允许自动填充模型字段
|
||||
*/
|
||||
protected $fillable = ['id', 'dict', 'code', 'locale', 'label', 'status', 'remark', 'created_by', 'updated_by', 'created_at', 'updated_at'];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class VehDictMapping extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'veh_dict_mapping';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
protected $casts = [
|
||||
'required' => 'integer',
|
||||
'enabled' => 'integer',
|
||||
'sort_order' => 'integer',
|
||||
'created_at' => 'integer',
|
||||
'updated_at' => 'integer',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'field','dict','required','enabled','sort_order',
|
||||
'remark','created_at','updated_at'
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class VehVehicleListing extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'veh_vehicle_listing';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
/**
|
||||
* 允许自动填充模型字段
|
||||
*/
|
||||
protected $fillable = ['id', 'sku_id', 'unit_id', 'title', 'sub_title', 'lang', 'market', 'channel', 'currency', 'list_price', 'promo_price', 'vat_included', 'lead_time_days', 'cover_image', 'media', 'attrs_override', 'status', 'visible_from', 'visible_to', 'corp_id', 'sort', 'remark', 'created_by', 'updated_by', 'created_at', 'updated_at'];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class VehVehicleSku extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'veh_vehicle_sku';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
protected $casts = [
|
||||
'options_official' => 'array',
|
||||
'features' => 'array',
|
||||
'dimensions_mm' => 'array',
|
||||
'model_year' => 'integer',
|
||||
'engine_turbo' => 'integer',
|
||||
'gears' => 'integer',
|
||||
'range_km' => 'integer',
|
||||
'doors' => 'integer',
|
||||
'seats' => 'integer',
|
||||
'weight_kg' => 'integer',
|
||||
'msrp' => 'float',
|
||||
'battery_kwh' => 'float',
|
||||
'engine_disp_l' => 'float',
|
||||
'is_active' => 'integer',
|
||||
'corp_id' => 'integer',
|
||||
'created_at' => 'integer',
|
||||
'updated_at' => 'integer',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'sku_code','fingerprint',
|
||||
'brand_code','brand_name',
|
||||
'series_code','series_name',
|
||||
'model_code','model_name','model_year',
|
||||
'trim_code','trim_level_code','edition_code',
|
||||
'trim','trim_level','edition_name',
|
||||
'body_type_code','drive_type_code','motor_layout_code',
|
||||
'fuel_type_code','engine_disp_l','engine_turbo','engine_power_kw',
|
||||
'battery_kwh','transmission_code','gears',
|
||||
'range_km','range_std_code',
|
||||
'doors','seats','weight_kg','dimensions_mm',
|
||||
'color_exterior_code','color_exterior_text',
|
||||
'color_interior_code','color_interior_text',
|
||||
'options_official','features','emission_std_code',
|
||||
'origin_country','currency','msrp',
|
||||
'is_active','corp_id','remark',
|
||||
'created_by','updated_by','created_at','updated_at',
|
||||
'body_type', 'drive_type', 'motor_layout', 'fuel_type', 'transmission',
|
||||
'range_std', 'emission_std'
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class VehVehicleUnit extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'veh_vehicle_unit';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
/**
|
||||
* 允许自动填充模型字段
|
||||
*/
|
||||
protected $fillable = ['id', 'sku_id', 'vin', 'engine_no', 'license_plate', 'first_reg_date', 'ownership_count', 'odometer', 'odometer_unit', 'condition_grade', 'accident_record', 'paint_count', 'owner_type', 'import_status', 'location_id', 'location_text', 'images', 'docs', 'stock_status', 'cost_total', 'currency', 'is_active', 'corp_id', 'remark', 'created_by', 'updated_by', 'created_at', 'updated_at'];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\validate\VehDictI18nValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\VehDictI18n;
|
||||
|
||||
class VehDictI18nService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehDictI18n::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = VehDictI18nValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
|
||||
|
||||
/** 本进程缓存:[dict][code] => labels array */
|
||||
protected static array $labelsCache = [];
|
||||
|
||||
public static function label(string $dict=null, ?string $code=null, string $locale='zh-CN', ?string $fallbackZh=null): ?string
|
||||
{
|
||||
if (!$dict || !$code) return $fallbackZh;
|
||||
|
||||
$labels = self::labels($dict, $code);
|
||||
if (!$labels) return $fallbackZh ?? $code;
|
||||
|
||||
return $labels[$locale] ?? $labels['zh-CN'] ?? $code;
|
||||
}
|
||||
|
||||
/** 返回 labels 数组,命中缓存 */
|
||||
public static function labels(string $dict, string $code): ?array
|
||||
{
|
||||
$dict = strtoupper($dict);
|
||||
$code = (string)$code;
|
||||
|
||||
if (isset(self::$labelsCache[$dict][$code])) {
|
||||
return self::$labelsCache[$dict][$code];
|
||||
}
|
||||
|
||||
$term = DictTerm::where('dict',$dict)->where('code',$code)->first(['labels']);
|
||||
if (!$term) return null;
|
||||
|
||||
$labels = $term->labels ?? null;
|
||||
if (!is_array($labels)) return null;
|
||||
|
||||
self::$labelsCache[$dict][$code] = $labels;
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/** 校验 code 是否存在于字典(用于必填维度) */
|
||||
public static function ensureExists(string $dict, ?string $code, string $fieldNameForError): void
|
||||
{
|
||||
if ($code === null || $code === '') {
|
||||
throw new \InvalidArgumentException("缺少必填字段:{$fieldNameForError}");
|
||||
}
|
||||
$ok = VehDictI18n::where('dict',$dict)->where('code',$code)->where('status', 1)->exists();
|
||||
if (!$ok) throw new \InvalidArgumentException("字典不存在或未启用:{$dict}.{$code}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\validate\VehDictMappingValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\VehDictMapping;
|
||||
|
||||
class VehDictMappingService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehDictMapping::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = VehDictMappingValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
|
||||
|
||||
public static function all(): array
|
||||
{
|
||||
$rows = VehDictMapping::where('status', 1)->orderBy('sort')->orderBy('id')->get()->toArray();
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$out[$r['dict']] = [
|
||||
'field' => $r['field'],
|
||||
'dict' => $r['dict'],
|
||||
'required' => (int)$r['required'] === 1,
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function requiredDicts(): array
|
||||
{
|
||||
return array_keys(array_filter(self::all(), fn($x)=>$x['required']===true));
|
||||
}
|
||||
|
||||
public static function enabled(string $dict): bool
|
||||
{
|
||||
$all = self::all();
|
||||
return isset($all[$dict]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\validate\VehVehicleListingValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\VehVehicleListing;
|
||||
|
||||
class VehVehicleListingService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehVehicleListing::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = VehVehicleListingValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\Fingerprint;
|
||||
use app\common\logic\ModelNaming;
|
||||
use app\common\logic\SkuCodeBuilder;
|
||||
use app\common\validate\VehVehicleSkuValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\VehVehicleSku;
|
||||
use support\Request;
|
||||
|
||||
class VehVehicleSkuService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehVehicleSku::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = VehVehicleSkuValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
|
||||
|
||||
// 枚举
|
||||
private const MAPS = [
|
||||
'BODY' => [
|
||||
'SEDAN' => '三厢轿车', 'SUV'=>'SUV', 'HB'=>'两厢/掀背', 'MPV'=>'MPV', 'PICKUP'=>'皮卡'
|
||||
],
|
||||
'DRIVE' => [
|
||||
'FWD'=>'前驱', 'RWD'=>'后驱', 'AWD'=>'四驱(AWD)', '4WD'=>'四驱(4WD)'
|
||||
],
|
||||
'MOTOR_LAYOUT' => [
|
||||
'FWD'=>'前置单电机', 'RWD'=>'后置单电机', 'AWD'=>'双电机四驱', 'DM-I'=>'DM-i', 'DM-P'=>'DM-p'
|
||||
],
|
||||
'FUEL' => [
|
||||
'GAS'=>'汽油', 'HEV'=>'油电混动', 'PHEV'=>'插电混动', 'EV'=>'纯电', 'EREV'=>'增程'
|
||||
],
|
||||
'TRANS' => [
|
||||
'MT'=>'手动', 'AT'=>'自动', 'CVT'=>'无级', 'DCT'=>'双离合', '1SPEED'=>'单速'
|
||||
],
|
||||
'RANGE_STD' => [
|
||||
'CLTC'=>'CLTC', 'WLTP'=>'WLTP', 'EPA'=>'EPA', 'NEDC'=>'NEDC'
|
||||
],
|
||||
'EMISSION' => [
|
||||
'CHN6'=>'国六', 'EURO6'=>'欧6', 'EPA'=>'美国EPA'
|
||||
],
|
||||
];
|
||||
|
||||
// *_code取中文
|
||||
public function label(string $dict, ?string $code): ?string
|
||||
{
|
||||
if ($code === null || $code === '') return null;
|
||||
$dict = strtoupper($dict);
|
||||
$code = strtoupper($code);
|
||||
return self::MAPS[$dict][$code] ?? $code;
|
||||
}
|
||||
|
||||
// 静态映射把 *_code → 中文
|
||||
private function backfill(array $d): array
|
||||
{
|
||||
$d['body_type'] = $this->label('BODY', $d['body_type_code']);
|
||||
$d['drive_type'] = $this->label('DRIVE', $d['drive_type_code']);
|
||||
$d['motor_layout'] = $this->label('MOTOR_LAYOUT', $d['motor_layout_code']);
|
||||
$d['fuel_type'] = $this->label('FUEL', $d['fuel_type_code']);
|
||||
$d['transmission'] = $this->label('TRANS', $d['transmission_code']);
|
||||
$d['range_std'] = $this->label('RANGE_STD', $d['range_std_code']);
|
||||
$d['emission_std'] = $this->label('EMISSION', $d['emission_std_code']);
|
||||
return $d;
|
||||
}
|
||||
|
||||
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
|
||||
{
|
||||
$p = $request->all();
|
||||
|
||||
$config = [];
|
||||
$config['model'] = $this->modelClass;
|
||||
switch ($scene) {
|
||||
case 'default':
|
||||
$config['where']['is_active'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'index':
|
||||
$config['where']['is_active'] = 1;
|
||||
$config['order']['sort'] = 'desc';
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'show':
|
||||
$config['where']['id'] = $p['id'];
|
||||
$config['where']['is_active'] = 1;
|
||||
break;
|
||||
case 'store':
|
||||
break;
|
||||
case 'update':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'destroy':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function beforeServiceStore($data, Request $request)
|
||||
{
|
||||
$data = $this->backfill($data);
|
||||
// 未提供时自动生成;若你想“强制覆盖人工输入”,可去掉 isset 判断
|
||||
if (empty($data['model_code'])) {
|
||||
$data['model_code'] = ModelNaming::buildModelCode($data);
|
||||
}
|
||||
if (empty($data['model_name'])) {
|
||||
$data['model_name'] = ModelNaming::buildModelName($data);
|
||||
}
|
||||
// 填充系统字段
|
||||
$data['sku_code'] = SkuCodeBuilder::build($data);
|
||||
$data['corp_id'] = 0;
|
||||
$data['created_by'] = $request->user->uid;
|
||||
$data['updated_by'] = $request->user->uid;
|
||||
// 指纹
|
||||
$data['fingerprint'] = Fingerprint::makeFromArray($data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterServiceStore($res)
|
||||
{
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
public function beforeServiceUpdate($data, Request $request)
|
||||
{
|
||||
$data = $this->backfill($data);
|
||||
// 未提供时自动生成;若你想“强制覆盖人工输入”,可去掉 isset 判断
|
||||
if (empty($data['model_code'])) {
|
||||
$data['model_code'] = ModelNaming::buildModelCode($data);
|
||||
}
|
||||
if (empty($data['model_name'])) {
|
||||
$data['model_name'] = ModelNaming::buildModelName($data);
|
||||
}
|
||||
// 填充系统字段
|
||||
$data['updated_by'] = $request->user->uid;
|
||||
// 指纹
|
||||
$data['fingerprint'] = Fingerprint::makeFromArray($data);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterServiceUpdate($res)
|
||||
{
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/** SKU 字段名 → dict 名(用于 i18n 取词 & 必填校验) */
|
||||
protected static array $SKU_FIELD_TO_DICT = [
|
||||
'brand_code' => 'BRAND',
|
||||
'series_code' => 'SERIES',
|
||||
'model_code' => 'MODEL',
|
||||
'trim_code' => 'TRIM',
|
||||
'trim_level_code' => 'TRIM_LEVEL',
|
||||
'edition_code' => 'EDITION',
|
||||
'fuel_type_code' => 'FUEL',
|
||||
'drive_type_code' => 'DRIVE',
|
||||
'transmission_code' => 'TRANS',
|
||||
'motor_layout_code' => 'MOTOR_LAYOUT',
|
||||
'body_type_code' => 'BODY',
|
||||
'range_std_code' => 'RANGE_STD',
|
||||
'emission_std_code' => 'EMISSION',
|
||||
'color_exterior_code'=> 'COLOR',
|
||||
'color_interior_code'=> 'COLOR',
|
||||
];
|
||||
|
||||
/** 创建 SKU(含指纹与字典校验) */
|
||||
public static function create(array $p, int $userId=0, int $corpId=0): VehicleSku
|
||||
{
|
||||
// SkuValidator::validateForCreate($p);
|
||||
|
||||
// 必填字典:由映射表控制(一般 BRAND/SERIES/FUEL)
|
||||
$requiredDicts = MappingService::requiredDicts();
|
||||
foreach (self::$SKU_FIELD_TO_DICT as $field => $dict) {
|
||||
if (!MappingService::enabled($dict)) continue;
|
||||
$isRequired = in_array($dict, $requiredDicts, true);
|
||||
if ($isRequired) {
|
||||
DictService::ensureExists($dict, $p[$field] ?? null, $field);
|
||||
}
|
||||
}
|
||||
|
||||
// 填充系统字段
|
||||
$now = time();
|
||||
$p['sku_code'] = $p['sku_code'] ?? ('SKU'.date('Ymd').'-'.substr(bin2hex(random_bytes(3)),0,6));
|
||||
$p['corp_id'] = $p['corp_id'] ?? $corpId;
|
||||
$p['created_by'] = $p['created_by'] ?? $userId;
|
||||
$p['updated_by'] = $p['updated_by'] ?? $userId;
|
||||
$p['created_at'] = $p['created_at'] ?? $now;
|
||||
$p['updated_at'] = $p['updated_at'] ?? $now;
|
||||
|
||||
// 指纹
|
||||
$p['fingerprint'] = Fingerprint::make($p /*, $scopeCorp=true/false*/);
|
||||
|
||||
return DB::connection()->transaction(function () use ($p) {
|
||||
$sku = new VehicleSku($p);
|
||||
$sku->save();
|
||||
return $sku;
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新 SKU(部分字段;指纹随事实变动而刷新) */
|
||||
public static function update(int $id, array $p, int $userId=0): VehicleSku
|
||||
{
|
||||
SkuValidator::validateForUpdate($p);
|
||||
|
||||
$sku = VehicleSku::findOrFail($id);
|
||||
|
||||
// 若传入了必填 dict 的 code,需要确认字典存在
|
||||
$requiredDicts = MappingService::requiredDicts();
|
||||
foreach (self::$SKU_FIELD_TO_DICT as $field => $dict) {
|
||||
if (!array_key_exists($field, $p)) continue;
|
||||
if (!MappingService::enabled($dict)) continue;
|
||||
$isRequired = in_array($dict, $requiredDicts, true);
|
||||
if ($isRequired) {
|
||||
DictService::ensureExists($dict, $p[$field], $field);
|
||||
} else {
|
||||
// 可选的:若传了 code 则也校验存在
|
||||
if (($p[$field] ?? null) !== null && $p[$field] !== '') {
|
||||
$exists = \app\model\DictTerm::where('dict',$dict)->where('code',$p[$field])->exists();
|
||||
if (!$exists) throw new \InvalidArgumentException("字典不存在:{$dict}.{$p[$field]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新系统字段
|
||||
$p['updated_by'] = $userId;
|
||||
$p['updated_at'] = time();
|
||||
|
||||
// 赋值保存
|
||||
$sku->fill($p);
|
||||
|
||||
// 只要影响到指纹的事实字段变动,就重算指纹
|
||||
$fingerFields = [
|
||||
'brand_code','series_code','model_year','fuel_type_code',
|
||||
'drive_type_code','transmission_code','body_type_code',
|
||||
'engine_disp_l','engine_turbo','battery_kwh','range_km',
|
||||
'range_std_code','emission_std_code',
|
||||
'corp_id'
|
||||
];
|
||||
foreach ($fingerFields as $f) {
|
||||
if (array_key_exists($f, $p)) {
|
||||
$sku->fingerprint = Fingerprint::make($sku->toArray());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$sku->save();
|
||||
return $sku;
|
||||
}
|
||||
|
||||
/** 详情(带 i18n label 与标准标题) */
|
||||
public static function detail(int $id, string $locale='zh-CN'): array
|
||||
{
|
||||
$sku = VehicleSku::findOrFail($id);
|
||||
$data = $sku->toArray();
|
||||
|
||||
$labelOf = function(string $field) use ($data, $locale) {
|
||||
$dict = self::$SKU_FIELD_TO_DICT[$field] ?? null;
|
||||
if (!$dict) return null;
|
||||
|
||||
$code = $data[$field] ?? null;
|
||||
// 若是品牌/车系,允许以中文兜底
|
||||
$fallback = null;
|
||||
if ($field === 'brand_code') $fallback = $data['brand_name'] ?? null;
|
||||
if ($field === 'series_code') $fallback = $data['series_name'] ?? null;
|
||||
|
||||
// 版型/等级/特别版允许中文兜底
|
||||
if ($field === 'trim_code') $fallback = $data['trim'] ?? null;
|
||||
if ($field === 'trim_level_code') $fallback = $data['trim_level'] ?? null;
|
||||
if ($field === 'edition_code') $fallback = $data['edition_name'] ?? null;
|
||||
|
||||
// 颜色兜底中文
|
||||
if ($field === 'color_exterior_code') $fallback = $data['color_exterior_text'] ?? null;
|
||||
if ($field === 'color_interior_code') $fallback = $data['color_interior_text'] ?? null;
|
||||
|
||||
return \app\service\DictService::label($dict, $code, $locale, $fallback);
|
||||
};
|
||||
|
||||
$i18n = [
|
||||
'brand_label' => $labelOf('brand_code'),
|
||||
'series_label' => $labelOf('series_code'),
|
||||
'model_label' => \app\service\DictService::label('MODEL', $data['model_code'] ?? null, $locale, $data['model_name'] ?? null),
|
||||
'trim_label' => $labelOf('trim_code'),
|
||||
'trim_level_label' => $labelOf('trim_level_code'),
|
||||
'edition_label' => $labelOf('edition_code'),
|
||||
'fuel_label' => $labelOf('fuel_type_code'),
|
||||
'drive_label' => $labelOf('drive_type_code'),
|
||||
'trans_label' => $labelOf('transmission_code'),
|
||||
'motor_layout_label' => $labelOf('motor_layout_code'),
|
||||
'body_label' => $labelOf('body_type_code'),
|
||||
'range_std_label' => $labelOf('range_std_code'),
|
||||
'emission_label' => $labelOf('emission_std_code'),
|
||||
'color_exterior_label' => $labelOf('color_exterior_code'),
|
||||
'color_interior_label' => $labelOf('color_interior_code'),
|
||||
'listing_title' => TitleBuilder::build($data, $locale),
|
||||
];
|
||||
|
||||
return ['sku'=>$data, 'i18n'=>$i18n];
|
||||
}
|
||||
|
||||
/** 列表查询(支持基础筛选 & i18n 列) */
|
||||
public static function list(array $q, string $locale='zh-CN', int $page=1, int $pageSize=20): array
|
||||
{
|
||||
$builder = VehicleSku::query();
|
||||
|
||||
// 常用筛选
|
||||
if (!empty($q['brand_code'])) $builder->where('brand_code', $q['brand_code']);
|
||||
if (!empty($q['series_code'])) $builder->where('series_code', $q['series_code']);
|
||||
if (!empty($q['fuel_type_code'])) $builder->where('fuel_type_code', $q['fuel_type_code']);
|
||||
if (!empty($q['model_year'])) $builder->where('model_year', (int)$q['model_year']);
|
||||
if (isset($q['is_active'])) $builder->where('is_active', (int)$q['is_active']);
|
||||
|
||||
$builder->orderByDesc('created_at');
|
||||
|
||||
$total = (clone $builder)->count();
|
||||
$rows = $builder->forPage($page, $pageSize)->get()->toArray();
|
||||
|
||||
// 附加 i18n 字段(轻量)
|
||||
foreach ($rows as &$row) {
|
||||
$row['brand_label'] = DictService::label('BRAND', $row['brand_code'] ?? null, $locale, $row['brand_name'] ?? null);
|
||||
$row['series_label'] = DictService::label('SERIES', $row['series_code'] ?? null, $locale, $row['series_name'] ?? null);
|
||||
$row['fuel_label'] = DictService::label('FUEL', $row['fuel_type_code'] ?? null, $locale, null);
|
||||
$row['listing_title']= TitleBuilder::build($row, $locale);
|
||||
}
|
||||
|
||||
return ['total'=>$total, 'list'=>$rows, 'page'=>$page, 'page_size'=>$pageSize];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\validate\VehVehicleUnitValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\VehVehicleUnit;
|
||||
|
||||
class VehVehicleUnitService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = VehVehicleUnit::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = VehVehicleUnitValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
}
|
||||
@@ -156,11 +156,11 @@ class Generate
|
||||
protected function deleteFileIfExists($path)
|
||||
{
|
||||
if (file_exists($path)) {
|
||||
"文件: <comment>{$path}</comment> 已存在,核对后手动删除。\n";
|
||||
return;
|
||||
// "文件: <comment>{$path}</comment> 已存在,核对后手动删除。\n";
|
||||
// return;
|
||||
|
||||
// unlink($path);
|
||||
// echo "删除已存在文件: <comment>{$path}</comment> \n";
|
||||
unlink($path);
|
||||
echo "删除已存在文件: <comment>{$path}</comment> \n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class VehDictI18nValidate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class VehDictMappingValidate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class VehVehicleListingValidate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class VehVehicleSkuValidate extends Validate
|
||||
{
|
||||
/**
|
||||
* 规则定义
|
||||
*/
|
||||
protected $rule = [
|
||||
'sku_code' => 'max:40|alphaNum',
|
||||
'fingerprint' => 'length:64|alphaNum',
|
||||
'brand_code' => 'require|max:32',
|
||||
'brand_name' => 'require|max:64',
|
||||
'series_code' => 'max:64',
|
||||
'series_name' => 'max:128',
|
||||
'model_code' => 'max:64',
|
||||
'model_name' => 'require|max:128',
|
||||
'model_year' => 'require|number|between:1900,2100',
|
||||
'trim_code' => 'max:64',
|
||||
'trim_level_code' => 'max:64',
|
||||
'edition_code' => 'max:64',
|
||||
'trim' => 'max:128',
|
||||
'trim_level' => 'max:64',
|
||||
'edition_name' => 'max:64',
|
||||
'body_type_code' => 'max:32',
|
||||
'drive_type_code' => 'max:16',
|
||||
'motor_layout_code' => 'max:32',
|
||||
'fuel_type_code' => 'max:16',
|
||||
'engine_disp_l' => 'float|between:0,99.9',
|
||||
'engine_turbo' => 'in:0,1',
|
||||
'engine_power_kw' => 'number|between:0,2000',
|
||||
'battery_kwh' => 'float|between:0,999.99',
|
||||
'transmission_code' => 'max:16',
|
||||
'gears' => 'number|between:1,20',
|
||||
'range_km' => 'number|between:0,2000',
|
||||
'range_std_code' => 'max:16',
|
||||
'doors' => 'number|between:2,10',
|
||||
'seats' => 'number|between:1,20',
|
||||
'weight_kg' => 'number|between:0,10000',
|
||||
'dimensions_mm' => 'array',
|
||||
'color_exterior_code'=> 'max:32',
|
||||
'color_exterior_text'=> 'max:64',
|
||||
'color_interior_code'=> 'max:32',
|
||||
'color_interior_text'=> 'max:64',
|
||||
'options_official' => 'array',
|
||||
'features' => 'array',
|
||||
'emission_std_code' => 'max:16',
|
||||
'origin_country' => 'alpha|length:2',
|
||||
'currency' => 'require|alpha|length:3',
|
||||
'msrp' => 'float|between:0,9999999999.99',
|
||||
'is_active' => 'in:0,1',
|
||||
'corp_id' => 'require|number',
|
||||
'remark' => 'max:255',
|
||||
'created_by' => 'number',
|
||||
'updated_by' => 'number',
|
||||
'created_at' => 'require|number',
|
||||
'updated_at' => 'require|number',
|
||||
];
|
||||
|
||||
/**
|
||||
* 字段别名
|
||||
*/
|
||||
protected $field = [
|
||||
'sku_code' => 'SKU编码',
|
||||
'fingerprint' => '规格指纹',
|
||||
'brand_code' => '品牌代码',
|
||||
'brand_name' => '品牌名称',
|
||||
'series_code' => '车系代码',
|
||||
'series_name' => '车系名称',
|
||||
'model_code' => '车型代码',
|
||||
'model_name' => '车型名称',
|
||||
'model_year' => '年款',
|
||||
'trim_code' => '版型代码',
|
||||
'trim_level_code' => '配置等级代码',
|
||||
'edition_code' => '特别版代码',
|
||||
'trim' => '版型/细分配置',
|
||||
'trim_level' => '配置等级',
|
||||
'edition_name' => '特别版名称',
|
||||
'body_type_code' => '车身类型',
|
||||
'drive_type_code' => '驱动方式',
|
||||
'motor_layout_code' => '电机布置',
|
||||
'fuel_type_code' => '能源类型',
|
||||
'engine_disp_l' => '排量(L)',
|
||||
'engine_turbo' => '是否涡轮',
|
||||
'engine_power_kw' => '最大功率(kW)',
|
||||
'battery_kwh' => '电池容量(kWh)',
|
||||
'transmission_code' => '变速箱代码',
|
||||
'gears' => '挡位数',
|
||||
'range_km' => '续航里程',
|
||||
'range_std_code' => '续航标准',
|
||||
'doors' => '门数',
|
||||
'seats' => '座位数',
|
||||
'weight_kg' => '整备质量',
|
||||
'dimensions_mm' => '车身尺寸',
|
||||
'color_exterior_code'=> '外观色代码',
|
||||
'color_exterior_text'=> '外观色名称',
|
||||
'color_interior_code'=> '内饰色代码',
|
||||
'color_interior_text'=> '内饰色名称',
|
||||
'options_official' => '官方选装',
|
||||
'features' => '功能配置',
|
||||
'emission_std_code' => '排放标准',
|
||||
'origin_country' => '原产国',
|
||||
'currency' => '币种',
|
||||
'msrp' => '建议零售价',
|
||||
'is_active' => '是否启用',
|
||||
'corp_id' => '公司ID',
|
||||
'remark' => '备注',
|
||||
'created_by' => '创建人',
|
||||
'updated_by' => '更新人',
|
||||
'created_at' => '创建时间',
|
||||
'updated_at' => '更新时间',
|
||||
];
|
||||
|
||||
/**
|
||||
* 场景定义
|
||||
*/
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
'brand_code', 'brand_name', 'model_year','currency'
|
||||
],
|
||||
'update' => [
|
||||
'id', 'brand_code','brand_name', 'model_year','currency'
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class VehVehicleUnitValidate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -78,25 +78,25 @@ class IndexController
|
||||
$options = [
|
||||
'controller' => [
|
||||
'module' => 'common',
|
||||
'name' => 'message_notifications'
|
||||
'name' => 'veh_dict_i18n'
|
||||
],
|
||||
'model' => [
|
||||
'module' => 'common',
|
||||
'name' => 'message_notifications'
|
||||
'name' => 'veh_dict_i18n'
|
||||
],
|
||||
'service' => [
|
||||
'module' => 'common',
|
||||
'name' => 'message_notifications'
|
||||
'name' => 'veh_dict_i18n'
|
||||
],
|
||||
'logic' => [
|
||||
'module' => 'common',
|
||||
'name' => 'message_notifications'
|
||||
'name' => 'veh_dict_i18n'
|
||||
],
|
||||
'validate' => [
|
||||
'module' => 'common',
|
||||
'name' => 'message_notifications'
|
||||
'name' => 'veh_dict_i18n'
|
||||
],
|
||||
'table' => 'message_notifications'
|
||||
'table' => 'veh_dict_i18n'
|
||||
];
|
||||
|
||||
$result = (new Generate($options))->doGenerate();
|
||||
|
||||
@@ -308,3 +308,26 @@ if (!function_exists('generateMsgId')) {
|
||||
return $date . $micro . $rand;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成sku_code
|
||||
* 格式:年月日时分秒 + 微秒 + 随机数
|
||||
* 例如:20250820175530987654321
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
if (!function_exists('skuCode')) {
|
||||
function skuCode(): string
|
||||
{
|
||||
// 时间部分(精确到微秒)
|
||||
$time = microtime(true);
|
||||
$micro = sprintf("%06d", ($time - floor($time)) * 1e6);
|
||||
$date = date('YmdHis', (int)$time);
|
||||
|
||||
// 随机部分(3位)
|
||||
$rand = mt_rand(100, 999);
|
||||
|
||||
// 拼接:日期时间 + 微秒 + 随机数
|
||||
return $date . $micro . $rand;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\redis;
|
||||
|
||||
use app\inquiry\service\InquiryLogService;
|
||||
use app\common\model\CrmCustomer;
|
||||
use app\common\model\InquiryLog;
|
||||
use Webman\RedisQueue\Consumer;
|
||||
use app\common\model\User;
|
||||
use app\common\utils\FeishuMessage;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class InquiryToCrm implements Consumer
|
||||
{
|
||||
// 要消费的队列名
|
||||
public $queue = 'inquiry-to-crm';
|
||||
|
||||
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
|
||||
public $connection = 'default';
|
||||
|
||||
// 消费
|
||||
public function consume($data)
|
||||
{
|
||||
try {
|
||||
// 获取询盘信息
|
||||
$info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', $data['id'])->where('sync_crm', 0)->first();
|
||||
|
||||
if ($info) {
|
||||
// 获取用户信息
|
||||
$recipient = 0;
|
||||
$corpId = 0;
|
||||
$uinfo = null;
|
||||
if ($info->recipient) {
|
||||
$recipient = $info->recipient;
|
||||
$uinfo = User::where('id', $info->recipient)->first();
|
||||
if ($uinfo) {
|
||||
$corpId = $uinfo->corp_id;
|
||||
}
|
||||
}
|
||||
// 同步客户数据
|
||||
$crmCustomer = new CrmCustomer();
|
||||
$crmCustomer->sn = $info->order_no;
|
||||
$crmCustomer->corp_abbr = $corpId;
|
||||
$crmCustomer->user_id = $recipient;
|
||||
$crmCustomer->type = 1;
|
||||
$crmCustomer->channel_id = $info->channel;
|
||||
$crmCustomer->country_id = $info->country;
|
||||
$crmCustomer->magnitude = $info->quality;
|
||||
$crmCustomer->email = $info->email;
|
||||
$crmCustomer->phone = $info->tel;
|
||||
$crmCustomer->WhatsApp = $info->whatsapp;
|
||||
$crmCustomer->contacts = $info->contacts;
|
||||
$crmCustomer->desc_collect = $info->content;
|
||||
$crmCustomer->enter_user_id = $info->enter_user_id;
|
||||
$crmCustomer->obtain = time();
|
||||
// 修改同步CRM状态
|
||||
$info->sync_crm = 1;
|
||||
// 保存
|
||||
if (!$crmCustomer->save() || !$info->save()) {
|
||||
throw new BusinessException('同步失败~');
|
||||
}
|
||||
|
||||
// // 发送邮件消息
|
||||
// if ($uinfo && $uinfo->email) {
|
||||
// $options = [
|
||||
// 'id' => $info->id,
|
||||
// 'email' => $uinfo->email
|
||||
// ];
|
||||
// // Log::info('发送邮件消息:' . json_encode($options));
|
||||
// \Webman\RedisQueue\Redis::send('email-message', $options);
|
||||
// }
|
||||
|
||||
$wsdata = [
|
||||
'sender_id' => 0,
|
||||
'sender_type' => 'system',
|
||||
'title' => '有新的客户线索,' . $info->order_no . ',请及时处理',
|
||||
'content' => '有新的客户线索,' . $info->order_no . ',请及时处理',
|
||||
'scene' => 1,
|
||||
'recipient' => [$info->recipient]
|
||||
];
|
||||
// 异步推送
|
||||
\Webman\RedisQueue\Redis::send('notice', $wsdata);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
// 消费失败回调
|
||||
/*
|
||||
$package = [
|
||||
'id' => 1357277951, // 消息ID
|
||||
'time' => 1709170510, // 消息时间
|
||||
'delay' => 0, // 延迟时间
|
||||
'attempts' => 2, // 消费次数
|
||||
'queue' => 'send-mail', // 队列名
|
||||
'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容
|
||||
'max_attempts' => 5, // 最大重试次数
|
||||
'error' => '错误信息' // 错误信息
|
||||
]
|
||||
*/
|
||||
public function onConsumeFailure(\Throwable $e, $package)
|
||||
{
|
||||
echo "consume failure\n";
|
||||
echo $e->getMessage() . "\n";
|
||||
// 无需反序列化
|
||||
var_export($package);
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\redis;
|
||||
|
||||
use app\inquiry\service\InquiryLogService;
|
||||
use app\common\model\CrmCustomer;
|
||||
use app\common\model\InquiryLog;
|
||||
use Webman\RedisQueue\Consumer;
|
||||
use app\common\model\User;
|
||||
use app\common\utils\FeishuMessage;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
|
||||
class InquiryToCrmUpdate implements Consumer
|
||||
{
|
||||
// 要消费的队列名
|
||||
public $queue = 'inquiry-to-crm-update';
|
||||
|
||||
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
|
||||
public $connection = 'default';
|
||||
|
||||
// 消费
|
||||
public function consume($data)
|
||||
{
|
||||
try {
|
||||
// 获取询盘信息
|
||||
$info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', $data['id'])->first();
|
||||
|
||||
$corpId = 0;
|
||||
if ($info->recipient) {
|
||||
$uinfo = User::where('id', $info->recipient)->where('status', 1)->first();
|
||||
if ($uinfo) {
|
||||
$corpId = $uinfo->corp_id;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取客户信息
|
||||
$crmCustomerInfo = CrmCustomer::where('sn', $info->order_no)->first();
|
||||
if ($crmCustomerInfo) {
|
||||
$udata = [
|
||||
'corp_abbr' => $corpId,
|
||||
'user_id' => $info->recipient,
|
||||
'channel_id' => $info->channel,
|
||||
'country_id' => $info->country,
|
||||
'magnitude' => $info->quality,
|
||||
'email' => $info->email,
|
||||
'phone' => $info->tel,
|
||||
'WhatsApp' => $info->whatsapp,
|
||||
'contacts' => $info->contacts,
|
||||
'desc_collect' => $info->content,
|
||||
];
|
||||
$crmCustomerInfo->update($udata);
|
||||
} else {
|
||||
// 同步客户数据
|
||||
$crmCustomer = new CrmCustomer();
|
||||
$crmCustomer->sn = $info->order_no;
|
||||
$crmCustomer->corp_abbr = $corpId;
|
||||
$crmCustomer->user_id = $info->recipient;
|
||||
$crmCustomer->type = 1;
|
||||
$crmCustomer->channel_id = $info->channel;
|
||||
$crmCustomer->country_id = $info->country;
|
||||
$crmCustomer->magnitude = $info->quality;
|
||||
$crmCustomer->email = $info->email;
|
||||
$crmCustomer->phone = $info->tel;
|
||||
$crmCustomer->WhatsApp = $info->whatsapp;
|
||||
$crmCustomer->contacts = $info->contacts;
|
||||
$crmCustomer->desc_collect = $info->content;
|
||||
$crmCustomer->enter_user_id = $info->enter_user_id;
|
||||
// 修改同步CRM状态
|
||||
$info->sync_crm = 1;
|
||||
// 保存
|
||||
if (!$crmCustomer->save() || !$info->save()) {
|
||||
throw new BusinessException('同步失败~');
|
||||
}
|
||||
|
||||
// 发送邮件消息
|
||||
if ($uinfo && $uinfo->email) {
|
||||
$options = [
|
||||
'id' => $info->id,
|
||||
'email' => $uinfo->email
|
||||
];
|
||||
\Webman\RedisQueue\Redis::send('email-message', $options);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
// 消费失败回调
|
||||
/*
|
||||
$package = [
|
||||
'id' => 1357277951, // 消息ID
|
||||
'time' => 1709170510, // 消息时间
|
||||
'delay' => 0, // 延迟时间
|
||||
'attempts' => 2, // 消费次数
|
||||
'queue' => 'send-mail', // 队列名
|
||||
'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容
|
||||
'max_attempts' => 5, // 最大重试次数
|
||||
'error' => '错误信息' // 错误信息
|
||||
]
|
||||
*/
|
||||
public function onConsumeFailure(\Throwable $e, $package)
|
||||
{
|
||||
echo "consume failure\n";
|
||||
echo $e->getMessage() . "\n";
|
||||
// 无需反序列化
|
||||
var_export($package);
|
||||
}
|
||||
}
|
||||
@@ -182,6 +182,18 @@ Route::group('/admin', function () {
|
||||
Route::any('/allocation', [app\crmAdmin\controller\CustomerController::class, 'allocation']);
|
||||
});
|
||||
});
|
||||
|
||||
// 车辆
|
||||
Route::group('/vehicle', function () {
|
||||
// 基础信息
|
||||
Route::group('/sku', function () {
|
||||
Route::any('/list', [app\common\controller\VehVehicleSkuController::class, 'index']);
|
||||
Route::any('/show', [app\common\controller\VehVehicleSkuController::class, 'show']);
|
||||
Route::any('/store', [app\common\controller\VehVehicleSkuController::class, 'store']);
|
||||
Route::any('/update', [app\common\controller\VehVehicleSkuController::class, 'update']);
|
||||
Route::any('/destroy', [app\common\controller\VehVehicleSkuController::class, 'destroy']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user