Files
useds/app/common/service/WordpressSiteService.php
T

868 lines
28 KiB
PHP
Raw Normal View History

2026-01-27 15:50:18 +08:00
<?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());
}
}
}