868 lines
28 KiB
PHP
868 lines
28 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
namespace app\common\service;
|
|||
|
|
|
|||
|
|
use app\common\model\UsedCar;
|
|||
|
|
use app\common\model\UsedcarCrawlResults;
|
|||
|
|
use app\common\model\UsedcarCrawlResultsImages;
|
|||
|
|
use app\common\model\WordpressUsedCar;
|
|||
|
|
use app\common\validate\WordpressSiteValidate;
|
|||
|
|
use app\common\traits\crud\CrudServiceTrait;
|
|||
|
|
use app\common\model\WordpressSite;
|
|||
|
|
use GuzzleHttp\Client;
|
|||
|
|
use GuzzleHttp\RequestOptions;
|
|||
|
|
use support\Log;
|
|||
|
|
use support\Request;
|
|||
|
|
use support\Response;
|
|||
|
|
|
|||
|
|
class WordpressSiteService
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
use CrudServiceTrait;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 指定当前要操作的模型
|
|||
|
|
*/
|
|||
|
|
protected string $modelClass = WordpressSite::class;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证器
|
|||
|
|
*/
|
|||
|
|
protected $validator = WordpressSiteValidate::class;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取验证器
|
|||
|
|
*/
|
|||
|
|
public function getValidator()
|
|||
|
|
{
|
|||
|
|
return new $this->validator;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param Request $request
|
|||
|
|
* @return mixed|Response
|
|||
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
|||
|
|
*/
|
|||
|
|
public function createPost(Request $request)
|
|||
|
|
{
|
|||
|
|
$client = new Client();
|
|||
|
|
$url = "https://37car.dev.hnxinyouhe.com/wp-json/wp/v2/posts";
|
|||
|
|
|
|||
|
|
$data = [
|
|||
|
|
"title" => "API 创建文章",
|
|||
|
|
"content" => "内容来自 API",
|
|||
|
|
"status" => "publish"
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$response = $client->post($url, [
|
|||
|
|
RequestOptions::AUTH => ['Borderless', '3Tdw 6O02 DM6f lb2S 7T3l WG8a'],
|
|||
|
|
RequestOptions::JSON => $data,
|
|||
|
|
RequestOptions::VERIFY => false, // 禁用 SSL 验证
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
$result = json_decode($response->getBody()->getContents(), true);
|
|||
|
|
|
|||
|
|
return $result;
|
|||
|
|
|
|||
|
|
} catch (\Exception $e) {
|
|||
|
|
throw new \Exception($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param Request $request
|
|||
|
|
* @return mixed|Response
|
|||
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
|||
|
|
*/
|
|||
|
|
public function createProduct(string $domain, $id)
|
|||
|
|
{
|
|||
|
|
$site = WordpressSite::where('site_url',$domain)->first();
|
|||
|
|
|
|||
|
|
//判断是否重复推送
|
|||
|
|
$wpUsed = WordpressUsedCar::where('wp_site_id', $site['id'])->where('used_car_id', $id)->first();
|
|||
|
|
if($wpUsed && ($wpUsed['retry_count']>2 || $wpUsed['status'] ==1)){
|
|||
|
|
$site->last_uid = $id;
|
|||
|
|
$site->save();
|
|||
|
|
return '重复推送';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$client = new Client();
|
|||
|
|
// $url = $domain."/wp-json/wc/v3/products";
|
|||
|
|
$url = $domain."/wp-json/wp/v2/product";
|
|||
|
|
|
|||
|
|
$row = UsedCar::where('id', $id)->first();
|
|||
|
|
$ucImages = explode(",", $row->images);
|
|||
|
|
|
|||
|
|
//车辆相册
|
|||
|
|
$images = [];
|
|||
|
|
foreach ($ucImages as $ucImage) {
|
|||
|
|
$image = $this->uploadImageToWp($domain, $site['api_user'], $site['api_secret'], $ucImage);
|
|||
|
|
$images[] = ['id' => $image['id']];
|
|||
|
|
}
|
|||
|
|
$images = array_merge([$images[0]], $images);
|
|||
|
|
|
|||
|
|
//车辆详细信息
|
|||
|
|
/*$detils= $this->drawDetils($row);
|
|||
|
|
$detils_params_html = [];
|
|||
|
|
foreach ($detils as $key => $detil) {
|
|||
|
|
if(is_array($detil)){
|
|||
|
|
$detil = implode(",", $detil);
|
|||
|
|
}
|
|||
|
|
$detils_params_html[] =$key.':'.(string)$detil;
|
|||
|
|
}
|
|||
|
|
$detils_params_html = implode(PHP_EOL, $detils_params_html);*/
|
|||
|
|
// $detils_params_html = $this->buildVehicleDetailsTable($detils);
|
|||
|
|
|
|||
|
|
//语言确定目标网站的语言
|
|||
|
|
$locale = match ($row['locale']) {
|
|||
|
|
'en' => '英文',
|
|||
|
|
'ru' => '俄语',
|
|||
|
|
'ar' => '阿拉伯语',
|
|||
|
|
'fr ' => '法语',
|
|||
|
|
default => '英文',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
$spark = new SparkService();
|
|||
|
|
$car_name = $row['product_name'];
|
|||
|
|
$car_name_locale = $spark->chat('将车辆名称翻译为'.$locale.':'.$car_name,null,[],'wpSend');
|
|||
|
|
$condition_desc_locale = $spark->chat("下面是车辆的车况介绍将卖家信息去掉,包括但不限于商家名称、集团简介、经销商信息、地址、电话、商家介绍等都去掉\"".$row['condition_desc']."\"",null,[],'wpSend');
|
|||
|
|
$condition_desc_locale = $spark->chat("将下列车况描述翻译为".$locale.":".$condition_desc_locale);
|
|||
|
|
|
|||
|
|
$data = [
|
|||
|
|
"name" => $car_name_locale,
|
|||
|
|
"type" => "simple", //单品,不走规格变体
|
|||
|
|
"regular_price" => number_format(($row['price']*$site['exchange_rate']), 2),
|
|||
|
|
"sku" => "",
|
|||
|
|
//"featured" => true,//是否精选
|
|||
|
|
"description" => $condition_desc_locale,
|
|||
|
|
"stock_status" => "instock",//instock现车
|
|||
|
|
// "categories" => [
|
|||
|
|
// ["id" => 16],//used二手车
|
|||
|
|
// ],
|
|||
|
|
"images" => $images,
|
|||
|
|
];
|
|||
|
|
$data['categories'][] = ["id" => $site['categories']];
|
|||
|
|
|
|||
|
|
//车辆档案相关参数
|
|||
|
|
$meta = $this->drawMeta($row['used_detail']);
|
|||
|
|
|
|||
|
|
//车身颜色
|
|||
|
|
$color = '';
|
|||
|
|
foreach ($meta as $item){
|
|||
|
|
if (!empty($item['key']) && $item['key'] == 'color') {
|
|||
|
|
$color = $item['value'];
|
|||
|
|
$color = $this->sparkTransl($spark,$color,$locale);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if ($site['tpl_type'] == 2) {
|
|||
|
|
$meta = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//$configDetail = json_decode($row['config_detail'], true);
|
|||
|
|
|
|||
|
|
//车身级别
|
|||
|
|
/*$body_style = '';
|
|||
|
|
foreach ($configDetail[0]['items'] as $item){
|
|||
|
|
if (!empty($item['key']) && $item['key'] == 'jb') {
|
|||
|
|
$body_style = $item['value'];
|
|||
|
|
$body_style = $this->sparkTransl($spark,$body_style,$locale);
|
|||
|
|
}
|
|||
|
|
}*/
|
|||
|
|
|
|||
|
|
//产品设置相关参数
|
|||
|
|
$meta[] = [
|
|||
|
|
'key' => 'pro-intro-txt',//产品参数简介
|
|||
|
|
'value' => "<ul>
|
|||
|
|
<li><strong>Engine Size</strong>{$row['displacement']}</li>
|
|||
|
|
<li><strong>Engine Power</strong>{$row['power']}</li>
|
|||
|
|
<li><strong>Mileage(KM)</strong>{$row['mileage']}</li>
|
|||
|
|
<li><strong>Color</strong>{$color}</li>
|
|||
|
|
</ul>",
|
|||
|
|
];
|
|||
|
|
// Engine Size
|
|||
|
|
//Engine Power
|
|||
|
|
//Mileage
|
|||
|
|
//Color
|
|||
|
|
// <li><strong>Year</strong>{$row['year']}</li>
|
|||
|
|
// <li><strong>Body Style</strong>{$body_style}</li>
|
|||
|
|
|
|||
|
|
//属性:品牌、车体类型、颜色
|
|||
|
|
$brand_locale = $spark->chat('将车辆品牌翻译为'.$locale.':'.$row['brand_name'],null,[],'wpSend');
|
|||
|
|
$data['attributes'][] = [ //品牌
|
|||
|
|
"id" => "1",
|
|||
|
|
"visible" => true,
|
|||
|
|
"variation" => false,
|
|||
|
|
"options" => [$brand_locale]
|
|||
|
|
];
|
|||
|
|
/*$data['attributes'][] = [ //车体类型
|
|||
|
|
"id" => "2",
|
|||
|
|
"visible" => true,
|
|||
|
|
"variation" => false,
|
|||
|
|
"options" => [$body_style]
|
|||
|
|
];*/
|
|||
|
|
$data['attributes'][] = [ //颜色
|
|||
|
|
"id" => "4",
|
|||
|
|
"visible" => true,
|
|||
|
|
"variation" => false,
|
|||
|
|
"options" => [$color]
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 构建车辆简介描述
|
|||
|
|
// $baseInfo = $this->parseCarConfigDetail($row['format_car_info']);
|
|||
|
|
// $vehicleDescription = $this->buildVehicleDescription($row, $baseInfo, $meta, $configDetail);
|
|||
|
|
|
|||
|
|
//翻译车辆简介描述
|
|||
|
|
$desc = "车辆名称:{$car_name},车辆参数:".json_encode($meta)."车辆简介:{$row['condition_desc']}";
|
|||
|
|
$vehicleDescription_locale = $spark->chat('根据给出的信息生成'.$locale.'车辆简介描述:'.$desc,null,[],'wpSend');
|
|||
|
|
|
|||
|
|
$meta[] = [
|
|||
|
|
'key' => 'pro-intro-des',//产品简介
|
|||
|
|
'value' => $vehicleDescription_locale,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// $meta[] = [ //列表标签
|
|||
|
|
// 'key' => 'hotpro_mark',//自定义热门产品标签
|
|||
|
|
// 'value' => 'In Stock', //In Stock、HOT SALE
|
|||
|
|
// ];
|
|||
|
|
|
|||
|
|
|
|||
|
|
$data['meta_data'] = array_values($meta);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$response = $client->post($url, [
|
|||
|
|
//RequestOptions::AUTH => ['Borderless', '3Tdw 6O02 DM6f lb2S 7T3l WG8a'],
|
|||
|
|
RequestOptions::AUTH => [$site['api_user'], $site['api_secret']],
|
|||
|
|
RequestOptions::JSON => $data,
|
|||
|
|
RequestOptions::VERIFY => false, // 禁用 SSL 验证
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
$result = json_decode($response->getBody()->getContents(), true);
|
|||
|
|
|
|||
|
|
$wordpressUsedCar = [
|
|||
|
|
'wp_site_id' => $site['id'],
|
|||
|
|
'used_car_id' => $id,
|
|||
|
|
'wp_post_id' => $result['id'],
|
|||
|
|
'request_payload' => json_encode($data, JSON_UNESCAPED_UNICODE),
|
|||
|
|
'response_body' => $response->getBody()->getContents(),
|
|||
|
|
'status' => 1,
|
|||
|
|
'retry_count' => 1,
|
|||
|
|
];
|
|||
|
|
WordpressUsedCar::create($wordpressUsedCar);
|
|||
|
|
|
|||
|
|
$site->last_uid = $id;
|
|||
|
|
$site->save();
|
|||
|
|
|
|||
|
|
return $result;
|
|||
|
|
|
|||
|
|
} catch (\Exception $e) {
|
|||
|
|
$his = WordpressUsedCar::where('wp_site_id', $site['id'])->where('used_car_id', $id)->first();
|
|||
|
|
var_dump($his);
|
|||
|
|
if ($his){
|
|||
|
|
$his->retry_count = $his->retry_count + 1;
|
|||
|
|
$his->save();
|
|||
|
|
if ($his->retry_count>=3){
|
|||
|
|
$site->last_uid = $id;
|
|||
|
|
$site->save();
|
|||
|
|
}
|
|||
|
|
}else{
|
|||
|
|
$wordpressUsedCar = [
|
|||
|
|
'wp_site_id' => $site['id'],
|
|||
|
|
'used_car_id' => $id,
|
|||
|
|
'request_payload' => json_encode($data, JSON_UNESCAPED_UNICODE),
|
|||
|
|
// 'response_body' => !empty($response)?$response->getBody()->getContents():$response,
|
|||
|
|
'error_message' => $e->getMessage(),
|
|||
|
|
'status' => 0,
|
|||
|
|
'retry_count' => 1,
|
|||
|
|
];
|
|||
|
|
WordpressUsedCar::create($wordpressUsedCar);
|
|||
|
|
}
|
|||
|
|
throw new \Exception($e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 使用spark lite对话翻译,检查如果没有中文直接返回,有中文则翻译
|
|||
|
|
* @param $spark
|
|||
|
|
* @param $str
|
|||
|
|
* @param $locale
|
|||
|
|
* @return mixed
|
|||
|
|
*/
|
|||
|
|
public function sparkTransl($spark,$str,$locale)
|
|||
|
|
{
|
|||
|
|
if(!(preg_match('/[\x{4e00}-\x{9fff}]/u', $str) > 0)){
|
|||
|
|
return $str;
|
|||
|
|
};
|
|||
|
|
$str = str_replace(["“", "”"], "", $str);
|
|||
|
|
//翻译
|
|||
|
|
$vehicleDescription_locale = $spark->chat('将“'.$str.'”翻译为'.$locale,null,[],'wpSend');
|
|||
|
|
return $vehicleDescription_locale;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取meta_data字段,即ACF自定义的字段
|
|||
|
|
* @param $used_detail
|
|||
|
|
* @return array
|
|||
|
|
* @throws \Exception
|
|||
|
|
*/
|
|||
|
|
public function drawMeta($used_car){
|
|||
|
|
$fieldMap = [ //中文字段 → ACF 字段名
|
|||
|
|
'plate_time', //上牌时间
|
|||
|
|
'displacement', //排量
|
|||
|
|
'mileage', //里程
|
|||
|
|
'power', //马力
|
|||
|
|
'body_color', //车身颜色
|
|||
|
|
'source_place', //车源地
|
|||
|
|
'price', //价格
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$metaData = [];
|
|||
|
|
|
|||
|
|
foreach ($fieldMap as $acfKey) {
|
|||
|
|
if (!empty($used_car[$acfKey])) {
|
|||
|
|
$metaData[] = [
|
|||
|
|
'key' => $acfKey,
|
|||
|
|
'value' => $used_car[$acfKey],
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $metaData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 安全取值
|
|||
|
|
*/
|
|||
|
|
public function drawDetils($row)
|
|||
|
|
{
|
|||
|
|
$baseInfo = $this->parseCarConfigDetail($row['format_car_info']);
|
|||
|
|
|
|||
|
|
return [
|
|||
|
|
'Ref ID' => $row['used_car_id'],
|
|||
|
|
|
|||
|
|
'Model Code' => $row['model_code'] ?? $row['car_name'],
|
|||
|
|
|
|||
|
|
'Model Year' => $row['year'],
|
|||
|
|
|
|||
|
|
'Exterior Color' => value($baseInfo, 'body_color'),
|
|||
|
|
|
|||
|
|
'Engine' => value($baseInfo, 'engine_description'),
|
|||
|
|
|
|||
|
|
'Drivetrain' => value($baseInfo, 'driver_form'),
|
|||
|
|
|
|||
|
|
'Range' => '–',
|
|||
|
|
|
|||
|
|
'Seats' => value($baseInfo, 'seat_count'),
|
|||
|
|
|
|||
|
|
'Dim.(mm)' => value($baseInfo, 'length_width_height'),
|
|||
|
|
|
|||
|
|
'Weight(kg)' => value($baseInfo, 'curb_weight'),
|
|||
|
|
|
|||
|
|
'Steering' => 'Left',
|
|||
|
|
|
|||
|
|
'Body Type' => value($baseInfo, 'car_body_struct'),
|
|||
|
|
|
|||
|
|
'Mlg(km)' => $row['usage_mileage'],
|
|||
|
|
|
|||
|
|
'Fuel' => $row['fuel_form'],
|
|||
|
|
|
|||
|
|
'Transmission' => value($baseInfo, 'gearbox_description'),
|
|||
|
|
|
|||
|
|
'Batt.Cap.(kWh)' => value($baseInfo, 'battery_capacity'),
|
|||
|
|
|
|||
|
|
'Motor Power(kW)' => value($baseInfo, 'motor_power'),
|
|||
|
|
|
|||
|
|
'M³' => value($baseInfo, 'cargo_volume'),
|
|||
|
|
|
|||
|
|
'Doors' => value($baseInfo, 'door_nums'),
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 安全取值
|
|||
|
|
*/
|
|||
|
|
function value(array $array, string $key, $default = '–')
|
|||
|
|
{
|
|||
|
|
if (!isset($array[$key]) || $array[$key] === '' || $array[$key] === null) {
|
|||
|
|
return $default;
|
|||
|
|
}
|
|||
|
|
return $array[$key];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将 format_car_info 拉平成 key => value
|
|||
|
|
*
|
|||
|
|
* @param string $json
|
|||
|
|
* @return array
|
|||
|
|
*/
|
|||
|
|
function parseCarConfigDetail(string $json): array
|
|||
|
|
{
|
|||
|
|
$data = json_decode($json, true);
|
|||
|
|
if (!is_array($data)) {
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$result = [];
|
|||
|
|
|
|||
|
|
foreach ($data as $group) {
|
|||
|
|
if (!isset($group['items']) || !is_array($group['items'])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($group['items'] as $item) {
|
|||
|
|
if (!isset($item['key'])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
$result[$item['key']] = $item['value'] ?? '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从 config_detail 中提取配置信息,拉平成 key => value
|
|||
|
|
*
|
|||
|
|
* @param array $configDetail
|
|||
|
|
* @return array
|
|||
|
|
*/
|
|||
|
|
private function parseConfigDetail(array $configDetail): array
|
|||
|
|
{
|
|||
|
|
if (!is_array($configDetail)) {
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$result = [];
|
|||
|
|
|
|||
|
|
foreach ($configDetail as $group) {
|
|||
|
|
if (!isset($group['items']) || !is_array($group['items'])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($group['items'] as $item) {
|
|||
|
|
if (!isset($item['key'])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
$key = $item['key'];
|
|||
|
|
$value = $item['value'] ?? '';
|
|||
|
|
|
|||
|
|
// 如果值不为空,存储它(如果已存在相同key,保留第一个非空值)
|
|||
|
|
if ($value !== '' && $value !== null && (!isset($result[$key]) || $result[$key] === '')) {
|
|||
|
|
$result[$key] = $value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从 config_detail 中获取指定配置项的值
|
|||
|
|
*
|
|||
|
|
* @param array $configDetail
|
|||
|
|
* @param string|array $keys 要查找的key,可以是字符串或数组(多个可能的key)
|
|||
|
|
* @param string $default 默认值
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
private function getConfigValue($configDetail, $keys, $default = ''): string
|
|||
|
|
{
|
|||
|
|
if (!is_array($configDetail)) {
|
|||
|
|
return $default;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$keysToSearch = is_array($keys) ? $keys : [$keys];
|
|||
|
|
|
|||
|
|
foreach ($configDetail as $group) {
|
|||
|
|
if (!isset($group['items']) || !is_array($group['items'])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($group['items'] as $item) {
|
|||
|
|
if (!isset($item['key'])) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$itemKey = $item['key'];
|
|||
|
|
$itemValue = $item['value'] ?? '';
|
|||
|
|
|
|||
|
|
// 检查是否匹配任何要查找的key
|
|||
|
|
foreach ($keysToSearch as $searchKey) {
|
|||
|
|
if ($itemKey === $searchKey && $itemValue !== '' && $itemValue !== null) {
|
|||
|
|
return (string)$itemValue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $default;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构建车辆简介描述
|
|||
|
|
*
|
|||
|
|
* @param array $row 车辆基本信息
|
|||
|
|
* @param array $baseInfo 车辆配置信息
|
|||
|
|
* @param array $meta meta_data数组
|
|||
|
|
* @param array $configDetail 配置详情
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
public function buildVehicleDescription($row, $baseInfo, $meta, $configDetail): string
|
|||
|
|
{
|
|||
|
|
$parts = [];
|
|||
|
|
|
|||
|
|
// 1. 品牌 + 系列 + 年份 + 车型名称
|
|||
|
|
$brandName = trim($row['brand_name'] ?? '');
|
|||
|
|
$seriesName = trim($row['series_name'] ?? '');
|
|||
|
|
$year = trim($row['year'] ?? '');
|
|||
|
|
$carName = trim($row['car_name'] ?? '');
|
|||
|
|
|
|||
|
|
$modelParts = [];
|
|||
|
|
if ($brandName) $modelParts[] = $brandName;
|
|||
|
|
if ($seriesName) $modelParts[] = $seriesName;
|
|||
|
|
if ($year) $modelParts[] = $year;
|
|||
|
|
|
|||
|
|
// 尝试从car_name中提取版本信息(如 "Champion Edition", "Champion PRO")
|
|||
|
|
$version = '';
|
|||
|
|
$carNameWithoutVersion = $carName;
|
|||
|
|
if ($carName) {
|
|||
|
|
// 如果car_name包含版本信息,提取出来
|
|||
|
|
if (preg_match('/\s+([A-Z][a-zA-Z\s]+(?:Edition|PRO|PLUS|MAX|PRO|Limited))$/i', $carName, $matches)) {
|
|||
|
|
$version = trim($matches[1]);
|
|||
|
|
$carNameWithoutVersion = trim(str_replace($version, '', $carName));
|
|||
|
|
}
|
|||
|
|
$modelParts[] = $carNameWithoutVersion;
|
|||
|
|
if ($version) {
|
|||
|
|
$modelParts[] = $version;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 引擎和变速箱信息(优先从 config_detail 中获取)
|
|||
|
|
$engine = $this->getConfigValue($configDetail, ['fdj', 'engine', 'engine_description', '发动机'], '');
|
|||
|
|
if (empty($engine)) {
|
|||
|
|
$engine = $this->value($baseInfo, 'engine_description', '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$transmission = $this->getConfigValue($configDetail, ['bsx', 'gearbox', 'gearbox_description', 'transmission', '变速箱'], '');
|
|||
|
|
if (empty($transmission)) {
|
|||
|
|
$transmission = $this->value($baseInfo, 'gearbox_description', '');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($engine || $transmission) {
|
|||
|
|
$engineParts = [];
|
|||
|
|
if ($engine && $engine !== '–') $engineParts[] = $engine;
|
|||
|
|
if ($transmission && $transmission !== '–') $engineParts[] = $transmission;
|
|||
|
|
if (!empty($engineParts)) {
|
|||
|
|
$modelParts[] = implode(' ', $engineParts);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!empty($modelParts)) {
|
|||
|
|
$parts[] = implode(' ', $modelParts);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 座位数(优先从 config_detail 中获取)
|
|||
|
|
$seatCount = $this->getConfigValue($configDetail, ['zws', 'seat_count', 'seat', 'seats', '座位数'], '');
|
|||
|
|
if (empty($seatCount) || $seatCount === '–') {
|
|||
|
|
$seatCount = $this->value($baseInfo, 'seat_count', '');
|
|||
|
|
}
|
|||
|
|
if ($seatCount && $seatCount !== '–' && is_numeric($seatCount)) {
|
|||
|
|
$parts[] = "{$seatCount}-seater";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 外观颜色(优先从 config_detail 中获取)
|
|||
|
|
$exteriorColor = $this->getConfigValue($configDetail, ['cys', 'body_color', 'color', 'exterior_color', '车身颜色'], '');
|
|||
|
|
// 如果 config_detail 中没有,尝试从 baseInfo 中获取
|
|||
|
|
if (empty($exteriorColor) || $exteriorColor === '–') {
|
|||
|
|
$exteriorColor = $this->value($baseInfo, 'body_color', '');
|
|||
|
|
}
|
|||
|
|
// 如果 baseInfo 中也没有,尝试从 meta 中获取
|
|||
|
|
if (empty($exteriorColor) || $exteriorColor === '–') {
|
|||
|
|
foreach ($meta as $item) {
|
|||
|
|
if (!empty($item['key']) && $item['key'] == 'color') {
|
|||
|
|
$exteriorColor = $item['value'] ?? '';
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($exteriorColor && $exteriorColor !== '–') {
|
|||
|
|
// 翻译颜色为英文(如果必要)
|
|||
|
|
$exteriorColorEn = $this->translateColorToEnglish($exteriorColor);
|
|||
|
|
$parts[] = "{$exteriorColorEn} exterior";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. 内饰颜色(尝试从config_detail中查找)
|
|||
|
|
$interiorColor = $this->getInteriorColor($configDetail, $baseInfo);
|
|||
|
|
if ($interiorColor && trim($interiorColor) !== '') {
|
|||
|
|
$interiorColorEn = $this->translateColorToEnglish($interiorColor);
|
|||
|
|
// 如果翻译后仍然是原始的中文值(说明未找到映射),使用light-colored作为默认值
|
|||
|
|
if ($interiorColorEn === mb_strtolower($interiorColor, 'UTF-8') && !preg_match('/^[a-z\s-]+$/i', $interiorColor)) {
|
|||
|
|
$interiorColorEn = 'light-colored';
|
|||
|
|
}
|
|||
|
|
if ($interiorColorEn) {
|
|||
|
|
$parts[] = "{$interiorColorEn} interior";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 6. 使用时间(从上牌时间计算)
|
|||
|
|
$licenseDate = '';
|
|||
|
|
foreach ($meta as $item) {
|
|||
|
|
if (!empty($item['key']) && $item['key'] == 'license_date') {
|
|||
|
|
$licenseDate = $item['value'] ?? '';
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($licenseDate) {
|
|||
|
|
$usageTime = $this->calculateUsageTime($licenseDate);
|
|||
|
|
if ($usageTime) {
|
|||
|
|
$parts[] = "used for {$usageTime}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 7. 行驶里程
|
|||
|
|
$mileage = $row['usage_mileage'] ?? '';
|
|||
|
|
if ($mileage && is_numeric($mileage)) {
|
|||
|
|
$formattedMileage = number_format((float)$mileage, 0, '.', ',');
|
|||
|
|
$parts[] = "traveled {$formattedMileage} kilometers";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return implode(', ', $parts) . '.';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 计算使用时间
|
|||
|
|
*
|
|||
|
|
* @param string $licenseDate 上牌时间,格式可能是 YYYY-MM-DD 或其他
|
|||
|
|
* @return string|null
|
|||
|
|
*/
|
|||
|
|
private function calculateUsageTime($licenseDate): ?string
|
|||
|
|
{
|
|||
|
|
if (empty($licenseDate)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 尝试解析日期
|
|||
|
|
$date = null;
|
|||
|
|
if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})/', $licenseDate, $matches)) {
|
|||
|
|
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-' . $matches[3]);
|
|||
|
|
} elseif (preg_match('/^(\d{4})-(\d{1,2})/', $licenseDate, $matches)) {
|
|||
|
|
$date = new \DateTime($matches[1] . '-' . $matches[2] . '-01');
|
|||
|
|
} elseif (is_numeric($licenseDate) && strlen($licenseDate) == 10) {
|
|||
|
|
// Unix时间戳
|
|||
|
|
$date = new \DateTime('@' . $licenseDate);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!$date) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$now = new \DateTime();
|
|||
|
|
$diff = $now->diff($date);
|
|||
|
|
|
|||
|
|
$years = $diff->y;
|
|||
|
|
$months = $diff->m;
|
|||
|
|
|
|||
|
|
if ($years > 0) {
|
|||
|
|
$months += $years * 12;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($months == 0) {
|
|||
|
|
// 如果不到1个月,显示天数
|
|||
|
|
$days = $diff->days;
|
|||
|
|
if ($days > 0) {
|
|||
|
|
return $days == 1 ? "1 day" : "{$days} days";
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($months == 1) {
|
|||
|
|
return "1 month";
|
|||
|
|
} else {
|
|||
|
|
return "{$months} months";
|
|||
|
|
}
|
|||
|
|
} catch (\Exception $e) {
|
|||
|
|
Log::error('计算使用时间失败', ['license_date' => $licenseDate, 'error' => $e->getMessage()]);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取内饰颜色
|
|||
|
|
*
|
|||
|
|
* @param array $configDetail
|
|||
|
|
* @param array $baseInfo
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
private function getInteriorColor($configDetail, $baseInfo): string
|
|||
|
|
{
|
|||
|
|
$possibleKeys = ['nsys', 'interior_color', 'interiorColor', 'seat_color', 'seatColor', '内饰颜色'];
|
|||
|
|
|
|||
|
|
// 优先从 configDetail 中查找
|
|||
|
|
$color = $this->getConfigValue($configDetail, $possibleKeys, '');
|
|||
|
|
if (!empty($color) && $color !== '–') {
|
|||
|
|
return $color;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果 configDetail 中没有找到,尝试通过中文字段名查找
|
|||
|
|
if (is_array($configDetail)) {
|
|||
|
|
foreach ($configDetail as $group) {
|
|||
|
|
if (isset($group['items']) && is_array($group['items'])) {
|
|||
|
|
foreach ($group['items'] as $item) {
|
|||
|
|
// 检查中文字段名
|
|||
|
|
if (isset($item['name']) && (strpos($item['name'], '内饰') !== false || strpos($item['name'], '座椅') !== false)) {
|
|||
|
|
if (!empty($item['value']) && $item['value'] !== '–') {
|
|||
|
|
return $item['value'];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 最后尝试从 baseInfo 中查找
|
|||
|
|
foreach ($possibleKeys as $key) {
|
|||
|
|
if (!empty($baseInfo[$key]) && $baseInfo[$key] !== '–') {
|
|||
|
|
return $baseInfo[$key];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将颜色翻译为英文
|
|||
|
|
*
|
|||
|
|
* @param string $color 颜色(可能是中文)
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
private function translateColorToEnglish($color): string
|
|||
|
|
{
|
|||
|
|
if (empty($color)) {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$colorMap = [
|
|||
|
|
'白色' => 'white',
|
|||
|
|
'黑色' => 'black',
|
|||
|
|
'红色' => 'red',
|
|||
|
|
'蓝色' => 'blue',
|
|||
|
|
'绿色' => 'green',
|
|||
|
|
'黄色' => 'yellow',
|
|||
|
|
'银色' => 'silver',
|
|||
|
|
'灰色' => 'gray',
|
|||
|
|
'灰色' => 'grey',
|
|||
|
|
'棕色' => 'brown',
|
|||
|
|
'金色' => 'gold',
|
|||
|
|
'橙色' => 'orange',
|
|||
|
|
'紫色' => 'purple',
|
|||
|
|
'粉色' => 'pink',
|
|||
|
|
'米色' => 'beige',
|
|||
|
|
'浅色' => 'light-colored',
|
|||
|
|
'深色' => 'dark-colored',
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$colorLower = mb_strtolower(trim($color), 'UTF-8');
|
|||
|
|
|
|||
|
|
// 如果已经是英文,直接返回(转小写)
|
|||
|
|
if (preg_match('/^[a-z\s-]+$/i', $color)) {
|
|||
|
|
return strtolower($color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查找中文映射
|
|||
|
|
foreach ($colorMap as $cn => $en) {
|
|||
|
|
if (strpos($colorLower, $cn) !== false) {
|
|||
|
|
return $en;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有找到映射,尝试提取可能的英文部分,否则返回原值
|
|||
|
|
if (preg_match('/[a-z]+/i', $color, $matches)) {
|
|||
|
|
return strtolower($matches[0]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return strtolower($color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构建 Vehicle Details 4列表格(2组 Key/Value)
|
|||
|
|
*
|
|||
|
|
* @param array $items [['label'=>'Ref ID','value'=>'xxx'], ...]
|
|||
|
|
* @return string
|
|||
|
|
*/
|
|||
|
|
public function buildVehicleDetailsTable(array $items): string
|
|||
|
|
{
|
|||
|
|
$html = '<table class="vehicle-details"><tbody>';
|
|||
|
|
|
|||
|
|
// 每行 2 个字段(4 列)
|
|||
|
|
$chunks = array_chunk($items, 2,true );
|
|||
|
|
|
|||
|
|
foreach ($chunks as $row) {
|
|||
|
|
$html .= '<tr>';
|
|||
|
|
foreach ($row as $label => $value) {
|
|||
|
|
$label = htmlspecialchars($label);
|
|||
|
|
$value = htmlspecialchars((string)($value ?: '–'));
|
|||
|
|
|
|||
|
|
$html .= "<th>{$label}</th><td>{$value}</td>";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果是奇数,补空列
|
|||
|
|
if (count($row) === 1) {
|
|||
|
|
$html .= '<th></th><td></td>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</tr>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$html .= '</tbody></table>';
|
|||
|
|
|
|||
|
|
return $html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 上传 OSS 图片到 WordPress 媒体库
|
|||
|
|
*
|
|||
|
|
* @param string $domain
|
|||
|
|
* @param string $imageUrl 阿里云 OSS 图片地址
|
|||
|
|
* @return array
|
|||
|
|
* @throws \Exception
|
|||
|
|
*/
|
|||
|
|
public function uploadImageToWp(string $domain,$api_user, $api_secret, string $imageUrl = "fdesk_test/usedcar_crawl/20251125/ff847a6a2b3114b4c2c230455b74cd2e.jpg"): array
|
|||
|
|
{
|
|||
|
|
$imageUrl = (new OssService)->generatePrivateUrl($imageUrl);
|
|||
|
|
|
|||
|
|
|
|||
|
|
$client = new Client();
|
|||
|
|
|
|||
|
|
$filename = basename(parse_url($imageUrl, PHP_URL_PATH));
|
|||
|
|
$mediaUrl = $domain . '/wp-json/wp/v2/media';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 1. 拉取 OSS 图片内容
|
|||
|
|
$imageContent = file_get_contents($imageUrl);
|
|||
|
|
if ($imageContent === false) {
|
|||
|
|
throw new \Exception('图片下载失败:' . $imageUrl);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 上传到 WP 媒体库
|
|||
|
|
$response = $client->post($mediaUrl, [
|
|||
|
|
RequestOptions::AUTH => [$api_user, $api_secret],
|
|||
|
|
RequestOptions::HEADERS => [
|
|||
|
|
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
|
|||
|
|
'Content-Type' => 'image/jpeg', // 如有 png/webp 可自行判断
|
|||
|
|
],
|
|||
|
|
RequestOptions::BODY => $imageContent,
|
|||
|
|
RequestOptions::VERIFY => false,
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
return json_decode($response->getBody()->getContents(), true);
|
|||
|
|
|
|||
|
|
} catch (\Exception $e) {
|
|||
|
|
throw new \Exception('上传图片失败:' .$domain . $imageUrl . "?????". $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|