1
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\WordpressSiteService;
|
||||
use support\Request;
|
||||
|
||||
class WordpressSiteController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->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;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class WordpressSite extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'wordpress_site';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
/**
|
||||
* 允许自动填充模型字段
|
||||
*/
|
||||
protected $fillable = ['id', 'site_name', 'site_url', 'tpl_type', 'admin_url', 'api_base_url', 'auth_type', 'api_user', 'api_secret', 'exchange_rate', 'categories', 'status', 'sort', 'last_push_at', 'remark', 'locale', 'last_uid', 'created_at', 'updated_at'];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class WordpressUsedCar extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'wordpress_used_car';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
|
||||
/**
|
||||
* 允许自动填充模型字段
|
||||
*/
|
||||
protected $fillable = ['id', 'wp_site_id', 'used_car_id', 'wp_post_id', 'status', 'request_payload', 'response_body', 'error_message', 'retry_count', 'pushed_at', 'created_at', 'updated_at'];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -0,0 +1,868 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class WordpressSiteValidate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -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']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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']);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user