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' => "", ]; // Engine Size //Engine Power //Mileage //Color //
  • Year{$row['year']}
  • //
  • Body Style{$body_style}
  • //属性:品牌、车体类型、颜色 $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 = ''; // 每行 2 个字段(4 列) $chunks = array_chunk($items, 2,true ); foreach ($chunks as $row) { $html .= ''; foreach ($row as $label => $value) { $label = htmlspecialchars($label); $value = htmlspecialchars((string)($value ?: '–')); $html .= ""; } // 如果是奇数,补空列 if (count($row) === 1) { $html .= ''; } $html .= ''; } $html .= '
    {$label}{$value}
    '; 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()); } } }