From e5afa43198da71f6b7b9cd43ab7e77c0942ccf4c Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Tue, 27 Jan 2026 15:50:18 +0800 Subject: [PATCH 1/2] 1 --- .../controller/WordpressSiteController.php | 56 ++ app/common/model/WordpressSite.php | 49 + app/common/model/WordpressUsedCar.php | 49 + app/common/service/WordpressSiteService.php | 868 ++++++++++++++++++ app/common/validate/WordpressSiteValidate.php | 29 + config/route.php | 16 +- 6 files changed, 1066 insertions(+), 1 deletion(-) create mode 100644 app/admin/controller/WordpressSiteController.php create mode 100644 app/common/model/WordpressSite.php create mode 100644 app/common/model/WordpressUsedCar.php create mode 100644 app/common/service/WordpressSiteService.php create mode 100644 app/common/validate/WordpressSiteValidate.php diff --git a/app/admin/controller/WordpressSiteController.php b/app/admin/controller/WordpressSiteController.php new file mode 100644 index 0000000..199c05b --- /dev/null +++ b/app/admin/controller/WordpressSiteController.php @@ -0,0 +1,56 @@ +service = new WordpressSiteService(); + } + + //推送,在wp站点上创建产品 + public function createProduct(Request $request){ + try { + $domain = $request->get('domain'); + $id = $request->get('id'); + $res = $this->service->createProduct($domain,$id); + return $this->success($res, '推送成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage().'-'.$e->getLine().'-'.$e->getFile()); + } + } + + public function uploadImageToWp(Request $request){ + try { + $domain = $request->get('domain'); + $res = $this->service->uploadImageToWp($domain); + return $this->success($res, '推送成功'); + } catch (\Throwable $e) { + return $this->error($e->getMessage()); + } + } + + /** + * 列表页数据格式化 + * + * @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; + // } +} \ No newline at end of file diff --git a/app/common/model/WordpressSite.php b/app/common/model/WordpressSite.php new file mode 100644 index 0000000..d260df1 --- /dev/null +++ b/app/common/model/WordpressSite.php @@ -0,0 +1,49 @@ +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()); + } + } + +} \ No newline at end of file diff --git a/app/common/validate/WordpressSiteValidate.php b/app/common/validate/WordpressSiteValidate.php new file mode 100644 index 0000000..f0fe8e9 --- /dev/null +++ b/app/common/validate/WordpressSiteValidate.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/config/route.php b/config/route.php index b9337f4..51b96f0 100644 --- a/config/route.php +++ b/config/route.php @@ -75,4 +75,18 @@ Route::group('/common', function () { Route::any('/info', [app\common\controller\UserController::class, 'show']); Route::any('/edit', [app\common\controller\UserController::class, 'edit']); }); -}); \ No newline at end of file + +}); + +Route::group('/admin', function () { + //仪表盘路由规则 + Route::group('/wp', function () { + Route::any('/list', [app\admin\controller\WordpressSiteController::class, 'index']); + Route::any('/show', [app\admin\controller\WordpressSiteController::class, 'show']); + Route::any('/store', [app\admin\controller\WordpressSiteController::class, 'store']); + Route::any('/update', [app\admin\controller\WordpressSiteController::class, 'update']); + Route::any('/destroy', [app\admin\controller\WordpressSiteController::class, 'destroy']); + Route::any('/send', [app\admin\controller\WordpressSiteController::class, 'createProduct']); + }); + +}); From 7ecbb660d52288110cfc234397f4c58b0662c140 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Thu, 29 Jan 2026 11:34:32 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/service/WordpressSiteService.php | 332 ++++++++------------ app/process/Task.php | 25 ++ 2 files changed, 159 insertions(+), 198 deletions(-) diff --git a/app/common/service/WordpressSiteService.php b/app/common/service/WordpressSiteService.php index 662602f..b091057 100644 --- a/app/common/service/WordpressSiteService.php +++ b/app/common/service/WordpressSiteService.php @@ -98,9 +98,10 @@ class WordpressSiteService $images = []; foreach ($ucImages as $ucImage) { $image = $this->uploadImageToWp($domain, $site['api_user'], $site['api_secret'], $ucImage); - $images[] = ['id' => $image['id']]; +// $images[] = ['id' => $image['id']]; + $images[] = $image['id']; } - $images = array_merge([$images[0]], $images); + //$images = array_merge([$images[0]], $images); //车辆详细信息 /*$detils= $this->drawDetils($row); @@ -130,7 +131,9 @@ class WordpressSiteService $condition_desc_locale = $spark->chat("将下列车况描述翻译为".$locale.":".$condition_desc_locale); $data = [ - "name" => $car_name_locale, + "title" => $car_name_locale, + "Content" => '', + "excerpt" => '', "type" => "simple", //单品,不走规格变体 "regular_price" => number_format(($row['price']*$site['exchange_rate']), 2), "sku" => "", @@ -140,94 +143,82 @@ class WordpressSiteService // "categories" => [ // ["id" => 16],//used二手车 // ], - "images" => $images, + //"images" => $images, + "status" => 'publish', //发布状态 ]; - $data['categories'][] = ["id" => $site['categories']]; + //$data['categories'][] = ["id" => $site['categories']]; + $data['product_cat'] = explode(',', $site['categories']); //车辆档案相关参数 - $meta = $this->drawMeta($row['used_detail']); + $meta = $this->drawMeta($row); //车身颜色 $color = ''; - foreach ($meta as $item){ - if (!empty($item['key']) && $item['key'] == 'color') { - $color = $item['value']; - $color = $this->sparkTransl($spark,$color,$locale); + foreach ($meta as $key => $item){ + if (!empty($item['key']) && ($item['key'] == 'body_color' || $item['key'] == 'source_place')) { + $goal = $this->sparkTransl($spark,$item['value'],$locale); + $meta[$key]['value'] = $goal; + + $color = $item['key'] == 'body_color'?$goal:''; } } 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] - ]; - - // 构建车辆简介描述 + $brand = $row['crawl_brand']?:$row['product_name']; + $brand_locale = $spark->chat('将车辆品牌翻译为'.$locale.':'.$brand,null,[],'wpSend'); + + //查询品牌id + $brand_id = $this->get_or_create_brand_id($site,$domain, $brand_locale); + $data['pa_brand'] = [$brand_id]; + + //查询车身类型 + $bodys = $this->get_body($site,$domain); + $bady_style = array_column($bodys, 'name'); + + $style = $spark->chat('根据给出的信息判断车身类型(不要回答多余的文字,只需要返回车身类型,无法判断则返回:Other),车身类型有:'.implode('、',$bady_style).',车辆数据为:'.json_encode($row->toArray(),JSON_UNESCAPED_UNICODE),null,[],'wpSend'); + if(!in_array($style,$bady_style)){ + $style = 'Other'; + } + $style_id = 0; + foreach ($bodys as $key => $item){ + if ($item['name'] == $style) { + $style_id = $item['id']; + } + } + $data['pa_body-style'] = [$style_id]; + + // 翻译车况简介 // $baseInfo = $this->parseCarConfigDetail($row['format_car_info']); -// $vehicleDescription = $this->buildVehicleDescription($row, $baseInfo, $meta, $configDetail); + $conditionDesc_locale = $spark->chat('将车况描述翻译为'.$locale.':'.$row['condition_desc'],null,[],'wpSend'); //翻译车辆简介描述 - $desc = "车辆名称:{$car_name},车辆参数:".json_encode($meta)."车辆简介:{$row['condition_desc']}"; - $vehicleDescription_locale = $spark->chat('根据给出的信息生成'.$locale.'车辆简介描述:'.$desc,null,[],'wpSend'); + $desc = "车辆名称:{$car_name},车辆参数:".json_encode($meta); + $vehicleDescription_locale = $spark->chat('根据给出的信息生成'.$locale.'车辆简介描述(要求简短):'.$desc,null,[],'wpSend'); $meta[] = [ - 'key' => 'pro-intro-des',//产品简介 + 'key' => 'pro-intro-des',//车况介绍 'value' => $vehicleDescription_locale, ]; + $meta[] = [ + 'key' => 'condition_desc',//产品简介 + 'value' => $conditionDesc_locale, + ]; + // $meta[] = [ //列表标签 // 'key' => 'hotpro_mark',//自定义热门产品标签 // 'value' => 'In Stock', //In Stock、HOT SALE // ]; - - $data['meta_data'] = array_values($meta); + //$data['meta'] = array_values($meta); + foreach ($meta as $item){ + $data['acf'][$item['key']] = $item['value']; + } + $data['featured_media'] = $images[0]; + $data['acf']['product_gallery'] = $images; try { $response = $client->post($url, [ @@ -253,11 +244,12 @@ class WordpressSiteService $site->last_uid = $id; $site->save(); - return $result; + $data ['result'] = $result; + return $data; + //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(); @@ -500,138 +492,81 @@ class WordpressSiteService } /** - * 构建车辆简介描述 - * - * @param array $row 车辆基本信息 - * @param array $baseInfo 车辆配置信息 - * @param array $meta meta_data数组 - * @param array $configDetail 配置详情 - * @return string + * 查询wp车身类型 + * @param $brand_name + * @return mixed + * @throws \GuzzleHttp\Exception\GuzzleException */ - 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) . '.'; + function get_body($site,$domain) { + $client = new Client([ + 'base_uri' => $domain . '/wp-json/wp/v2/', + RequestOptions::AUTH => [$site['api_user'], $site['api_secret']], +// 'auth' => ['your_username', 'your_password'], // 或者使用应用密码 + RequestOptions::VERIFY => false, // 禁用 SSL 验证 + ]); + + // 查询车身类型 + $response = $client->get('pa_body-style'); + + $bodys = json_decode($response->getBody(), true); + + // 返回车身类型 + return $bodys; } - + + /** + * 查询wp品牌id + * @param $brand_name + * @return mixed + * @throws \GuzzleHttp\Exception\GuzzleException + */ + function get_or_create_brand_id($site,$domain, $brand_name) { + if(empty($brand_name)){ + return 0; + } + $client = new Client([ + 'base_uri' => $domain . '/wp-json/wp/v2/', + RequestOptions::AUTH => [$site['api_user'], $site['api_secret']], +// 'auth' => ['your_username', 'your_password'], // 或者使用应用密码 + RequestOptions::VERIFY => false, // 禁用 SSL 验证 + ]); + + // 生成slug + $slug = $this->generate_slug($brand_name); + + // 查询品牌是否已存在 + $response = $client->get('pa_brand', [ + 'query' => [ + 'slug' => $slug // 用品牌名称生成slug查询 + ] + ]); + + $brands = json_decode($response->getBody(), true); + + // 如果品牌存在,返回ID + if (!empty($brands)) { + return $brands[0]['id']; // 返回第一个匹配的品牌ID + } + + // 品牌不存在,先创建 + $response = $client->post('pa_brand', [ + 'json' => [ + 'name' => $brand_name, + 'slug' => $slug + ] + ]); + + $brand = json_decode($response->getBody(), true); + + // 返回创建的品牌ID + return $brand['id']; + } + + function generate_slug($brand_name) { + // 将品牌名称转成小写并替换掉非字母数字字符 + return preg_replace('/[^a-z0-9-]+/', '-', strtolower(trim($brand_name))); + } + /** * 计算使用时间 * @@ -832,7 +767,8 @@ class WordpressSiteService */ public function uploadImageToWp(string $domain,$api_user, $api_secret, string $imageUrl = "fdesk_test/usedcar_crawl/20251125/ff847a6a2b3114b4c2c230455b74cd2e.jpg"): array { - $imageUrl = (new OssService)->generatePrivateUrl($imageUrl); + $process = 'image/resize,w_900/quality,Q_50/format,webp'; + $imageUrl = (new OssService)->generatePrivateUrlResize($imageUrl,3600,$process); $client = new Client(); diff --git a/app/process/Task.php b/app/process/Task.php index e00b11d..82ee20b 100644 --- a/app/process/Task.php +++ b/app/process/Task.php @@ -3,6 +3,9 @@ namespace app\process; use app\common\model\UsedCar; +use app\common\model\WordpressSite; +use app\common\model\WordpressUsedCar; +use app\common\service\WordpressSiteService; use support\Db; use support\Log; use Workerman\Crontab\Crontab; @@ -36,6 +39,28 @@ class Task }); + if (getenv('ENVIRONMENT') === 'master') { + // 每三个小时自动归档 + new Crontab('0 0 3 * * *', function(){ + + $sites = WordpressSite::where('status', 1) + ->get(); + foreach ($sites as $site) { + $used_car_id = WordpressUsedCar::where('wp_site_id', $site['id']) + ->where('status', 1) + ->max('used_car_id'); + $used_cars = Db::table('used_car') + ->where('id', '>', $used_car_id) + ->get(); + foreach ($used_cars as $car) { + WordpressSiteService::createProduct($site['site_url'], $car['id']); + } + + } + + }); + } + } } \ No newline at end of file