推送
This commit is contained in:
@@ -89,12 +89,10 @@ class UploadController
|
|||||||
$originalName = $file->getUploadName();
|
$originalName = $file->getUploadName();
|
||||||
|
|
||||||
$ossservice = new OssService();
|
$ossservice = new OssService();
|
||||||
$mimes = $ossservice->getMimeTypes(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar', 'MP4', 'MOV', 'M4V', 'WEBM', 'MKV', 'FLV', 'WMV', 'AVI', 'TS']);
|
|
||||||
|
|
||||||
$result = Storage::adapter('oss')
|
$result = Storage::adapter('oss')
|
||||||
->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd'))
|
->path(config('oss.oss_main_dir') . $sceneUrl . date('Ymd'))
|
||||||
->size(1024*1024*50)
|
->size(1024*1024*100)
|
||||||
->extYes($mimes)
|
|
||||||
->upload($file);
|
->upload($file);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use app\common\utils\Jwt;
|
|||||||
use support\Redis;
|
use support\Redis;
|
||||||
use support\exception\BusinessException;
|
use support\exception\BusinessException;
|
||||||
use support\Log;
|
use support\Log;
|
||||||
use function Symfony\Component\String\b;
|
|
||||||
|
|
||||||
class AuthLogic
|
class AuthLogic
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ class UsedCarDetail extends Model
|
|||||||
* JSON 类型字段自动转换
|
* JSON 类型字段自动转换
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'images' => 'array',
|
// 'images' => 'array',
|
||||||
'attachment' => 'array',
|
// 'attachment' => 'array',
|
||||||
'intro_images' => 'array',
|
// 'intro_images' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -421,7 +421,70 @@ class UsedCarDetailService
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// 更新车辆状态
|
// 更新车辆状态
|
||||||
return UsedCarDetail::where('id', $carId)->update(['status' => $auditStatus]);
|
$result = UsedCarDetail::where('id', $carId)->update(['status' => $auditStatus]);
|
||||||
|
|
||||||
|
// 审核通过后自动推送到WordPress
|
||||||
|
$pushResult = ['success' => true, 'message' => ''];
|
||||||
|
if ($auditStatus == 1) {
|
||||||
|
$pushResult = $this->pushToWordpressAfterAudit($carId);
|
||||||
|
|
||||||
|
// 推送失败,将车辆状态修改为3
|
||||||
|
if (!$pushResult['success']) {
|
||||||
|
UsedCarDetail::where('id', $carId)->update(['status' => 3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回审核结果和推送结果
|
||||||
|
return [
|
||||||
|
'audit_success' => true,
|
||||||
|
'push_success' => $pushResult['success'],
|
||||||
|
'push_message' => $pushResult['message'],
|
||||||
|
'message' => $pushResult['success'] ? '审核通过并推送成功' : '审核通过,但推送失败:' . $pushResult['message'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核通过后推送车辆到WordPress
|
||||||
|
* @return array ['success' => bool, 'message' => string]
|
||||||
|
*/
|
||||||
|
private function pushToWordpressAfterAudit(int $carId): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 获取启用的WordPress站点
|
||||||
|
$site = \app\common\model\WordpressSite::where('status', 1)->first();
|
||||||
|
if (!$site) {
|
||||||
|
$msg = '没有启用的WordPress站点';
|
||||||
|
Log::warning('WordPress推送失败:' . $msg, ['car_id' => $carId]);
|
||||||
|
return ['success' => false, 'message' => $msg];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已推送过
|
||||||
|
$existing = \app\common\model\WordpressUsedCar::where('wp_site_id', $site->id)
|
||||||
|
->where('used_car_id', $carId)
|
||||||
|
->where('status', 1)
|
||||||
|
->first();
|
||||||
|
if ($existing) {
|
||||||
|
$msg = '车辆已推送,跳过';
|
||||||
|
Log::info('WordPress推送:' . $msg, ['car_id' => $carId]);
|
||||||
|
return ['success' => true, 'message' => $msg];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用推送服务
|
||||||
|
$wpService = new \app\common\service\WordpressSiteService();
|
||||||
|
$wpService->createProductForNewSite($site, $carId);
|
||||||
|
|
||||||
|
Log::info('WordPress推送成功', ['car_id' => $carId, 'site_id' => $site->id]);
|
||||||
|
return ['success' => true, 'message' => ''];
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$msg = $e->getMessage();
|
||||||
|
Log::error('WordPress推送失败', [
|
||||||
|
'car_id' => $carId,
|
||||||
|
'error' => $msg,
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
return ['success' => false, 'message' => $msg];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -767,38 +767,653 @@ class WordpressSiteService
|
|||||||
*/
|
*/
|
||||||
public function uploadImageToWp(string $domain,$api_user, $api_secret, string $imageUrl = "fdesk_test/usedcar_crawl/20251125/ff847a6a2b3114b4c2c230455b74cd2e.jpg"): array
|
public function uploadImageToWp(string $domain,$api_user, $api_secret, string $imageUrl = "fdesk_test/usedcar_crawl/20251125/ff847a6a2b3114b4c2c230455b74cd2e.jpg"): array
|
||||||
{
|
{
|
||||||
$process = 'image/resize,w_900/quality,Q_50/format,webp';
|
$process = 'image/resize,w_800/quality,Q_50/format,webp';
|
||||||
$imageUrl = (new OssService)->generatePrivateUrlResize($imageUrl,3600,$process);
|
$processedUrl = (new OssService)->generatePrivateUrlResize($imageUrl,3600,$process);
|
||||||
|
|
||||||
|
|
||||||
$client = new Client();
|
$client = new Client();
|
||||||
|
|
||||||
$filename = basename(parse_url($imageUrl, PHP_URL_PATH));
|
// 从原始 URL 提取文件扩展名
|
||||||
|
$pathInfo = pathinfo(parse_url($imageUrl, PHP_URL_PATH));
|
||||||
|
$extension = strtolower($pathInfo['extension'] ?? 'jpg');
|
||||||
|
|
||||||
|
// 根据处理后的格式确定文件名和 Content-Type
|
||||||
|
if (strpos($processedUrl, 'webp') !== false || strpos($process, 'webp') !== false) {
|
||||||
|
$filename = ($pathInfo['filename'] ?? 'image') . '.webp';
|
||||||
|
$contentType = 'image/webp';
|
||||||
|
} else {
|
||||||
|
$filename = ($pathInfo['filename'] ?? 'image') . '.' . $extension;
|
||||||
|
$contentType = 'image/' . ($extension === 'jpg' ? 'jpeg' : $extension);
|
||||||
|
}
|
||||||
|
|
||||||
$mediaUrl = $domain . '/wp-json/wp/v2/media';
|
$mediaUrl = $domain . '/wp-json/wp/v2/media';
|
||||||
|
|
||||||
try {
|
// 本地临时文件路径
|
||||||
// 1. 拉取 OSS 图片内容
|
$tempDir = runtime_path('temp') . '/wp_images/';
|
||||||
$imageContent = file_get_contents($imageUrl);
|
if (!is_dir($tempDir)) {
|
||||||
if ($imageContent === false) {
|
mkdir($tempDir, 0755, true);
|
||||||
throw new \Exception('图片下载失败:' . $imageUrl);
|
}
|
||||||
}
|
$tempFilePath = $tempDir . md5($processedUrl) . '.' . $extension;
|
||||||
|
|
||||||
// 2. 上传到 WP 媒体库
|
try {
|
||||||
$response = $client->post($mediaUrl, [
|
Log::info('开始上传图片到WordPress', [
|
||||||
RequestOptions::AUTH => [$api_user, $api_secret],
|
'original_url' => $imageUrl,
|
||||||
RequestOptions::HEADERS => [
|
'processed_url' => $processedUrl,
|
||||||
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
|
'filename' => $filename,
|
||||||
'Content-Type' => 'image/jpeg', // 如有 png/webp 可自行判断
|
'content_type' => $contentType,
|
||||||
],
|
'temp_file' => $tempFilePath,
|
||||||
RequestOptions::BODY => $imageContent,
|
|
||||||
RequestOptions::VERIFY => false,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return json_decode($response->getBody()->getContents(), true);
|
// 1. 下载图片到本地临时文件
|
||||||
|
$imageContent = file_get_contents($processedUrl);
|
||||||
|
if ($imageContent === false) {
|
||||||
|
throw new \Exception('图片下载失败:' . $processedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
$imageSize = strlen($imageContent);
|
||||||
|
Log::info('图片下载成功', ['size' => $imageSize, 'temp_file' => $tempFilePath]);
|
||||||
|
|
||||||
|
// 写入临时文件
|
||||||
|
$bytesWritten = file_put_contents($tempFilePath, $imageContent);
|
||||||
|
if ($bytesWritten === false) {
|
||||||
|
throw new \Exception('写入临时文件失败:' . $tempFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('临时文件创建成功', ['file' => $tempFilePath, 'size' => $bytesWritten]);
|
||||||
|
|
||||||
|
// 2. 使用 GuzzleHttp 上传本地文件到 WP 媒体库
|
||||||
|
$fileHandle = fopen($tempFilePath, 'r');
|
||||||
|
$response = $client->post($mediaUrl, [
|
||||||
|
RequestOptions::AUTH => [$api_user, $api_secret],
|
||||||
|
RequestOptions::MULTIPART => [
|
||||||
|
[
|
||||||
|
'name' => 'file',
|
||||||
|
'filename' => $filename,
|
||||||
|
'contents' => $fileHandle,
|
||||||
|
'headers' => [
|
||||||
|
'Content-Type' => $contentType,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
RequestOptions::VERIFY => false,
|
||||||
|
RequestOptions::TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 关闭文件句柄
|
||||||
|
if (is_resource($fileHandle)) {
|
||||||
|
fclose($fileHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = json_decode($response->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
Log::info('图片上传成功', [
|
||||||
|
'media_id' => $result['id'] ?? 'unknown',
|
||||||
|
'source_url' => $result['source_url'] ?? 'not set',
|
||||||
|
'url' => $result['url'] ?? 'not set',
|
||||||
|
'link' => $result['link'] ?? 'not set',
|
||||||
|
'guid' => $result['guid']['rendered'] ?? 'not set',
|
||||||
|
'all_keys' => array_keys($result),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new \Exception('上传图片失败:' .$domain . $imageUrl . "?????". $e->getMessage());
|
Log::error('上传图片到WordPress失败', [
|
||||||
|
'domain' => $domain,
|
||||||
|
'image_url' => $imageUrl,
|
||||||
|
'processed_url' => $processedUrl ?? 'N/A',
|
||||||
|
'temp_file' => $tempFilePath ?? 'N/A',
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
throw new \Exception('上传图片失败:' . $e->getMessage());
|
||||||
|
} finally {
|
||||||
|
// 3. 删除临时文件
|
||||||
|
if (file_exists($tempFilePath)) {
|
||||||
|
@unlink($tempFilePath);
|
||||||
|
Log::info('临时文件已删除', ['file' => $tempFilePath]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建产品到新站点(使用 Gutenberg Blocks 结构)
|
||||||
|
*
|
||||||
|
* @param \app\common\model\WordpressSite $site WordPress站点
|
||||||
|
* @param int $carId 车辆ID
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function createProductForNewSite($site, int $carId): array
|
||||||
|
{
|
||||||
|
// 获取车辆数据
|
||||||
|
$car = \app\common\model\UsedCarDetail::find($carId);
|
||||||
|
if (!$car) {
|
||||||
|
throw new \Exception('车辆不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否重复推送
|
||||||
|
$wpUsed = WordpressUsedCar::where('wp_site_id', $site->id)
|
||||||
|
->where('used_car_id', $carId)
|
||||||
|
->first();
|
||||||
|
if ($wpUsed && ($wpUsed->retry_count > 2 || $wpUsed->status == 1)) {
|
||||||
|
return ['message' => '重复推送,跳过'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = $site->site_url;
|
||||||
|
$client = new Client();
|
||||||
|
$url = $domain . '/wp-json/wp/v2/product';
|
||||||
|
|
||||||
|
// 调试:检查 images 字段的原始值和转换后的值
|
||||||
|
$imagesRaw = $car->getAttribute('images'); // 获取原始值,不经过 casts
|
||||||
|
$imagesCast = $car->images; // 经过 casts 转换后的值
|
||||||
|
|
||||||
|
Log::info('检查 images 字段', [
|
||||||
|
'car_id' => $carId,
|
||||||
|
'images_raw' => $imagesRaw,
|
||||||
|
'images_cast' => $imagesCast,
|
||||||
|
'images_raw_type' => gettype($imagesRaw),
|
||||||
|
'images_cast_type' => gettype($imagesCast),
|
||||||
|
'images_cast_is_array' => is_array($imagesCast),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 解析图片列表 - 优先使用已转换的数组,否则尝试解析原始字符串
|
||||||
|
$images = [];
|
||||||
|
if (!empty($imagesCast) && is_array($imagesCast)) {
|
||||||
|
$images = $imagesCast;
|
||||||
|
} elseif (!empty($imagesRaw)) {
|
||||||
|
// 尝试 JSON 解析
|
||||||
|
$decoded = json_decode($imagesRaw, true);
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
$images = $decoded;
|
||||||
|
} else {
|
||||||
|
// 尝试逗号分割
|
||||||
|
$images = explode(',', $imagesRaw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('开始处理车辆图片', [
|
||||||
|
'car_id' => $carId,
|
||||||
|
'images_final' => $images,
|
||||||
|
'images_count' => count($images),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 上传图片到WordPress媒体库
|
||||||
|
$uploadedImages = []; // 存储完整信息 ['id' => xx, 'url' => xx, ...]
|
||||||
|
$uploadedImageIds = []; // 仅存储ID
|
||||||
|
|
||||||
|
foreach ($images as $index => $imageUrl) {
|
||||||
|
if (!empty($imageUrl)) {
|
||||||
|
try {
|
||||||
|
Log::info('正在上传图片', ['index' => $index, 'url' => $imageUrl]);
|
||||||
|
$uploadedImg = $this->uploadImageToWp($domain, $site->api_user, $site->api_secret, $imageUrl);
|
||||||
|
$uploadedImages[] = [
|
||||||
|
'id' => $uploadedImg['id'],
|
||||||
|
'url' => $uploadedImg['source_url'] ?? $uploadedImg['url'] ?? '',
|
||||||
|
];
|
||||||
|
$uploadedImageIds[] = $uploadedImg['id'];
|
||||||
|
Log::info('图片上传成功', ['index' => $index, 'media_id' => $uploadedImg['id'], 'url' => $uploadedImages[count($uploadedImages)-1]['url']]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('图片上传失败', [
|
||||||
|
'index' => $index,
|
||||||
|
'url' => $imageUrl,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('图片上传完成', [
|
||||||
|
'total' => count($images),
|
||||||
|
'success' => count($uploadedImages),
|
||||||
|
'uploaded_ids' => $uploadedImageIds,
|
||||||
|
'uploaded_details' => $uploadedImages,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 获取FAQ数据
|
||||||
|
$faqs = \app\common\model\UsedCarFaq::where('car_id', $carId)
|
||||||
|
->where('status', 1)
|
||||||
|
->orderBy('sort')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// 翻译标题
|
||||||
|
$title = $this->translateIfNeeded($car->product_name, '英文');
|
||||||
|
|
||||||
|
// 构建Gutenberg Blocks格式content
|
||||||
|
$content = $this->buildGutenbergContent($car, $uploadedImages, $faqs);
|
||||||
|
|
||||||
|
// 构建产品数据
|
||||||
|
$data = [
|
||||||
|
'title' => $title,
|
||||||
|
'content' => $content,
|
||||||
|
'status' => 'publish',
|
||||||
|
'type' => 'simple',
|
||||||
|
'regular_price' => number_format($car->price * $site->exchange_rate, 2),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 设置主图和车辆相册(ACF字段)
|
||||||
|
if (!empty($uploadedImages)) {
|
||||||
|
$allImageIds = array_map(function($img) {
|
||||||
|
return $img['id'];
|
||||||
|
}, $uploadedImages);
|
||||||
|
|
||||||
|
$data['featured_media'] = $uploadedImages[0]['id'];
|
||||||
|
|
||||||
|
// 车辆相册:前5张图片
|
||||||
|
$productGalleryImages = array_slice($uploadedImages, 0, 5);
|
||||||
|
$productGalleryIds = array_map(function($img) {
|
||||||
|
return $img['id'];
|
||||||
|
}, $productGalleryImages);
|
||||||
|
$data['acf']['product_gallery'] = $productGalleryIds;
|
||||||
|
|
||||||
|
Log::info('设置ACF车辆相册', [
|
||||||
|
'featured_media' => $data['featured_media'],
|
||||||
|
'total_images' => count($uploadedImages),
|
||||||
|
'product_gallery_count' => count($data['acf']['product_gallery']),
|
||||||
|
'product_gallery_ids' => $data['acf']['product_gallery'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置ACF车辆参数字段(与旧站点保持一致)
|
||||||
|
$fieldMap = [
|
||||||
|
'plate_time' => '上牌时间',
|
||||||
|
'displacement' => '排量',
|
||||||
|
'mileage' => '里程',
|
||||||
|
'power' => '马力',
|
||||||
|
'body_color' => '车身颜色',
|
||||||
|
'source_place' => '车源地',
|
||||||
|
'price' => '价格',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($fieldMap as $acfKey => $label) {
|
||||||
|
if (!empty($car->$acfKey)) {
|
||||||
|
$value = $car->$acfKey;
|
||||||
|
// 翻译颜色和车源地
|
||||||
|
if ($acfKey === 'body_color') {
|
||||||
|
$value = $this->translateColorToEnglish($value);
|
||||||
|
} elseif ($acfKey === 'source_place' && $this->containsChinese($value)) {
|
||||||
|
$value = $this->translateIfNeeded($value, '英文');
|
||||||
|
}
|
||||||
|
$data['acf'][$acfKey] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 翻译并设置车况介绍和产品简介(ACF字段)
|
||||||
|
$conditionDesc = $car->condition_desc ?? '';
|
||||||
|
if (!empty($conditionDesc)) {
|
||||||
|
// 产品简介(完整翻译)
|
||||||
|
$data['acf']['condition_desc'] = $this->translateIfNeeded($conditionDesc, '英文');
|
||||||
|
|
||||||
|
// 车况介绍(简短翻译,去掉商家信息)
|
||||||
|
$cleanDesc = preg_replace('/[^\x{4e00}-\x{9fff}\w\s.,!?()-]/u', ' ', $conditionDesc);
|
||||||
|
$vehicleDesc = $this->translateIfNeeded($cleanDesc, '英文');
|
||||||
|
$data['acf']['pro-intro-des'] = $vehicleDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('设置ACF参数字段', [
|
||||||
|
'acf_fields' => array_keys($data['acf'] ?? []),
|
||||||
|
'acf_values' => $data['acf'] ?? [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log::info('准备推送产品到WordPress', [
|
||||||
|
'car_id' => $carId,
|
||||||
|
'featured_media' => $data['featured_media'] ?? 'not set',
|
||||||
|
'acf_product_gallery_count' => count($data['acf']['product_gallery'] ?? []),
|
||||||
|
'acf_fields_count' => count($data['acf'] ?? []),
|
||||||
|
'content_length' => strlen($data['content'] ?? ''),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $client->post($url, [
|
||||||
|
RequestOptions::AUTH => [$site->api_user, $site->api_secret],
|
||||||
|
RequestOptions::JSON => $data,
|
||||||
|
RequestOptions::VERIFY => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = json_decode($response->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
// 记录推送成功
|
||||||
|
if ($wpUsed) {
|
||||||
|
$wpUsed->update([
|
||||||
|
'wp_post_id' => $result['id'],
|
||||||
|
'request_payload' => json_encode($data, JSON_UNESCAPED_UNICODE),
|
||||||
|
'response_body' => $response->getBody()->getContents(),
|
||||||
|
'status' => 1,
|
||||||
|
'retry_count' => $wpUsed->retry_count + 1,
|
||||||
|
'pushed_at' => time(),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
WordpressUsedCar::create([
|
||||||
|
'wp_site_id' => $site->id,
|
||||||
|
'used_car_id' => $carId,
|
||||||
|
'wp_post_id' => $result['id'],
|
||||||
|
'request_payload' => json_encode($data, JSON_UNESCAPED_UNICODE),
|
||||||
|
'response_body' => $response->getBody()->getContents(),
|
||||||
|
'status' => 1,
|
||||||
|
'retry_count' => 1,
|
||||||
|
'pushed_at' => time(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$site->last_uid = $carId;
|
||||||
|
$site->save();
|
||||||
|
|
||||||
|
return array_merge($data, ['result' => $result]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// 记录推送失败
|
||||||
|
if ($wpUsed) {
|
||||||
|
$wpUsed->update([
|
||||||
|
'retry_count' => $wpUsed->retry_count + 1,
|
||||||
|
'error_message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
if ($wpUsed->retry_count >= 3) {
|
||||||
|
$site->last_uid = $carId;
|
||||||
|
$site->save();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WordpressUsedCar::create([
|
||||||
|
'wp_site_id' => $site->id,
|
||||||
|
'used_car_id' => $carId,
|
||||||
|
'request_payload' => json_encode($data, JSON_UNESCAPED_UNICODE),
|
||||||
|
'error_message' => $e->getMessage(),
|
||||||
|
'status' => 0,
|
||||||
|
'retry_count' => 1,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
throw new \Exception('WordPress推送失败:' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 Gutenberg Blocks 格式 content
|
||||||
|
*/
|
||||||
|
private function buildGutenbergContent($car, array $uploadedImages, $faqs): string
|
||||||
|
{
|
||||||
|
$content = '';
|
||||||
|
|
||||||
|
// 1. 车辆相册标题
|
||||||
|
$content .= "<!-- wp:heading -->\n";
|
||||||
|
$content .= "<h2 class=\"wp-block-heading\" id=\"vehicle-image\">Vehicle Image</h2>\n";
|
||||||
|
$content .= "<!-- /wp:heading -->\n\n";
|
||||||
|
|
||||||
|
// 2. 图库 block(使用第6张开始的图片,不足8张时重复使用前5张)
|
||||||
|
$content .= "<!-- wp:gallery {\"columns\":4,\"linkTo\":\"none\"} -->\n";
|
||||||
|
$content .= "<figure class=\"wp-block-gallery has-nested-images columns-4 is-cropped\">\n";
|
||||||
|
|
||||||
|
$galleryImages = array_slice($uploadedImages, 5); // 第6张开始
|
||||||
|
$firstFiveImages = array_slice($uploadedImages, 0, 5); // 前5张
|
||||||
|
|
||||||
|
Log::info('构建图库block', [
|
||||||
|
'total_images' => count($uploadedImages),
|
||||||
|
'gallery_start_from' => 6,
|
||||||
|
'gallery_before_fill' => count($galleryImages),
|
||||||
|
'first_five_count' => count($firstFiveImages),
|
||||||
|
'gallery_ids_before' => array_column($galleryImages, 'id'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 不足8张时,重复使用前5张图片
|
||||||
|
if (count($galleryImages) > 0 && count($galleryImages) < 8) {
|
||||||
|
$repeatSource = $firstFiveImages; // 重复使用前5张
|
||||||
|
while (count($galleryImages) < 8) {
|
||||||
|
$galleryImages = array_merge($galleryImages, $repeatSource);
|
||||||
|
}
|
||||||
|
$galleryImages = array_slice($galleryImages, 0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('图库图片准备完成', [
|
||||||
|
'final_count' => count($galleryImages),
|
||||||
|
'image_ids' => array_column($galleryImages, 'id'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($galleryImages as $img) {
|
||||||
|
$imgId = $img['id'];
|
||||||
|
$imgUrl = $img['url'] ?? '';
|
||||||
|
// 构建 Gutenberg image block,包含完整的 URL
|
||||||
|
$blockAttrs = json_encode([
|
||||||
|
"id" => $imgId,
|
||||||
|
"sizeSlug" => "large",
|
||||||
|
"linkDestination" => "none",
|
||||||
|
"url" => $imgUrl,
|
||||||
|
], JSON_UNESCAPED_SLASHES);
|
||||||
|
$content .= "<!-- wp:image {$blockAttrs} -->\n";
|
||||||
|
$content .= "<figure class=\"wp-block-image size-large\"><img src=\"{$imgUrl}\" alt=\"\" class=\"wp-image-{$imgId}\"/></figure>\n";
|
||||||
|
$content .= "<!-- /wp:image -->\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$content .= "</figure>\n";
|
||||||
|
$content .= "<!-- /wp:gallery -->\n\n";
|
||||||
|
|
||||||
|
// 3. Description 标题和段落
|
||||||
|
$content .= "<!-- wp:heading -->\n";
|
||||||
|
$content .= "<h2 class=\"wp-block-heading\" id=\"Description\">Description</h2>\n";
|
||||||
|
$content .= "<!-- /wp:heading -->\n\n";
|
||||||
|
|
||||||
|
// 翻译并分段处理 condition_desc
|
||||||
|
$conditionDesc = $this->translateIfNeeded($car->condition_desc ?? '', '英文');
|
||||||
|
$paragraphs = preg_split('/\n\n+/', trim($conditionDesc));
|
||||||
|
foreach ($paragraphs as $para) {
|
||||||
|
$para = trim($para);
|
||||||
|
if (!empty($para)) {
|
||||||
|
$content .= "<!-- wp:paragraph -->\n";
|
||||||
|
$content .= "<p>" . htmlspecialchars($para) . "</p>\n";
|
||||||
|
$content .= "<!-- /wp:paragraph -->\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Parameters xz-tab block
|
||||||
|
$tabData = $this->buildXzTabData($car);
|
||||||
|
$content .= "<!-- wp:acf/xz-tab " . json_encode($tabData, JSON_UNESCAPED_UNICODE) . " /-->\n\n";
|
||||||
|
|
||||||
|
// 5. FAQ
|
||||||
|
if ($faqs->count() > 0) {
|
||||||
|
$content .= "<!-- wp:heading -->\n";
|
||||||
|
$content .= "<h2 class=\"wp-block-heading\" id=\"FAQ\">FAQ</h2>\n";
|
||||||
|
$content .= "<!-- /wp:heading -->\n\n";
|
||||||
|
|
||||||
|
$faqQuestions = [];
|
||||||
|
foreach ($faqs as $index => $faq) {
|
||||||
|
$question = $this->translateIfNeeded($faq->question, '英文');
|
||||||
|
$answer = $this->translateIfNeeded($faq->answer, '英文');
|
||||||
|
|
||||||
|
$faqQuestions[] = [
|
||||||
|
'id' => 'faq-question-' . $car->id . '-' . $index,
|
||||||
|
'question' => [$question],
|
||||||
|
'answer' => [$answer],
|
||||||
|
'jsonQuestion' => $question,
|
||||||
|
'jsonAnswer' => $answer,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$faqData = ['questions' => $faqQuestions];
|
||||||
|
$content .= "<!-- wp:yoast/faq-block " . json_encode($faqData, JSON_UNESCAPED_UNICODE) . " -->\n";
|
||||||
|
$content .= "<div class=\"schema-faq wp-block-yoast-faq-block\">";
|
||||||
|
foreach ($faqQuestions as $fq) {
|
||||||
|
$content .= "<div class=\"schema-faq-section\" id=\"" . $fq['id'] . "\">";
|
||||||
|
$content .= "<strong class=\"schema-faq-question\">" . htmlspecialchars($fq['question'][0]) . "</strong> ";
|
||||||
|
$content .= "<p class=\"schema-faq-answer\">" . htmlspecialchars($fq['answer'][0]) . "</p> ";
|
||||||
|
$content .= "</div> ";
|
||||||
|
}
|
||||||
|
$content .= "</div>\n";
|
||||||
|
$content .= "<!-- /wp:yoast/faq-block -->\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 xz-tab 数据结构
|
||||||
|
*/
|
||||||
|
private function buildXzTabData($car): array
|
||||||
|
{
|
||||||
|
// Tab 标题
|
||||||
|
$tabTitles = [
|
||||||
|
'Basic Info',
|
||||||
|
'Body',
|
||||||
|
'Transmission',
|
||||||
|
'Chassis & Steering',
|
||||||
|
'Active Safety',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Tab 内容数据
|
||||||
|
$tabContents = [
|
||||||
|
// Tab 1: 基本信息
|
||||||
|
$this->buildTabTable([
|
||||||
|
['label' => 'Official Guide Price', 'value' => $car->official_guide_price],
|
||||||
|
['label' => 'Manufacturer', 'value' => $car->manufacturer],
|
||||||
|
['label' => 'Level', 'value' => $car->level],
|
||||||
|
['label' => 'Energy Type', 'value' => $car->energy_type],
|
||||||
|
['label' => 'Listing Time', 'value' => $car->listing_time],
|
||||||
|
['label' => 'Engine', 'value' => $car->engine],
|
||||||
|
['label' => 'Electric Motor', 'value' => $car->electric_motor],
|
||||||
|
['label' => 'NEDC Range (Km)', 'value' => $car->nedc_range],
|
||||||
|
['label' => 'CLTC Range (Km)', 'value' => $car->cltc_range],
|
||||||
|
['label' => 'Fast Charge Platform', 'value' => $car->fast_charge_platform],
|
||||||
|
['label' => 'Charging Time (Hours)', 'value' => $car->charging_time],
|
||||||
|
['label' => 'Fast Charge Percent (%)', 'value' => $car->fast_charge_percent],
|
||||||
|
['label' => 'Max Power (Kw)', 'value' => $car->max_power_kw],
|
||||||
|
['label' => 'Max Torque (N·m)', 'value' => $car->max_torque_nm],
|
||||||
|
['label' => 'Transmission', 'value' => $car->transmission_desc],
|
||||||
|
['label' => 'Dimensions (Mm)', 'value' => $car->dimensions],
|
||||||
|
['label' => 'Body Structure', 'value' => $car->body_structure],
|
||||||
|
['label' => 'Max Speed (Km/H)', 'value' => $car->max_speed],
|
||||||
|
['label' => 'Acceleration 100 (S)', 'value' => $car->acceleration_100],
|
||||||
|
['label' => 'NEDC Fuel Consumption', 'value' => $car->nedc_fuel_consumption],
|
||||||
|
['label' => 'Power Consumption 100', 'value' => $car->power_consumption_100],
|
||||||
|
['label' => 'Equivalent Fuel Consumption', 'value' => $car->equivalent_fuel_consumption],
|
||||||
|
]),
|
||||||
|
// Tab 2: 车身
|
||||||
|
$this->buildTabTable([
|
||||||
|
['label' => 'Body Length (Mm)', 'value' => $car->body_length],
|
||||||
|
['label' => 'Body Width (Mm)', 'value' => $car->body_width],
|
||||||
|
['label' => 'Body Height (Mm)', 'value' => $car->body_height],
|
||||||
|
['label' => 'Wheelbase (Mm)', 'value' => $car->wheelbase],
|
||||||
|
['label' => 'Body Structure', 'value' => $car->body_structure],
|
||||||
|
['label' => 'Door Count', 'value' => $car->door_count],
|
||||||
|
['label' => 'Door Open Type', 'value' => $car->door_open_type],
|
||||||
|
['label' => 'Seat Count', 'value' => $car->seat_count],
|
||||||
|
]),
|
||||||
|
// Tab 3: 变速箱
|
||||||
|
$this->buildTabTable([
|
||||||
|
['label' => 'Transmission Description', 'value' => $car->transmission_desc],
|
||||||
|
['label' => 'Gear Count', 'value' => $car->gear_count],
|
||||||
|
['label' => 'Transmission Type', 'value' => $car->transmission_type],
|
||||||
|
]),
|
||||||
|
// Tab 4: 底盘/转向
|
||||||
|
$this->buildTabTable([
|
||||||
|
['label' => 'Drive Mode', 'value' => $car->drive_mode],
|
||||||
|
['label' => 'Front Suspension', 'value' => $car->front_suspension],
|
||||||
|
['label' => 'Rear Suspension', 'value' => $car->rear_suspension],
|
||||||
|
['label' => 'Steering Type', 'value' => $car->steering_type],
|
||||||
|
]),
|
||||||
|
// Tab 5: 主动安全
|
||||||
|
$this->buildTabTable([
|
||||||
|
['label' => 'Has ABS', 'value' => $car->has_abs],
|
||||||
|
['label' => 'Has EBD', 'value' => $car->has_ebd],
|
||||||
|
['label' => 'Has BRA', 'value' => $car->has_bra],
|
||||||
|
['label' => 'Has TCS', 'value' => $car->has_tcs],
|
||||||
|
['label' => 'Has ESP', 'value' => $car->has_esp],
|
||||||
|
['label' => 'Has Active Brake', 'value' => $car->has_active_brake],
|
||||||
|
['label' => 'Has Lane Assist', 'value' => $car->has_lane_assist],
|
||||||
|
['label' => 'Has LKA', 'value' => $car->has_lka],
|
||||||
|
['label' => 'Has Lane Centering', 'value' => $car->has_lane_centering],
|
||||||
|
['label' => 'Has Fatigue Remind', 'value' => $car->has_fatigue_remind],
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$tabCount = 5;
|
||||||
|
$data = [
|
||||||
|
'name' => 'acf/xz-tab',
|
||||||
|
'data' => [],
|
||||||
|
'mode' => 'edit',
|
||||||
|
];
|
||||||
|
|
||||||
|
for ($i = 0; $i < $tabCount; $i++) {
|
||||||
|
$data['data']["xz-tabs_{$i}_tab_title"] = $tabTitles[$i] ?? "Tab {$i}";
|
||||||
|
$data['data']["_xz-tabs_{$i}_tab_title"] = "field_69ae438a9a253";
|
||||||
|
$data['data']["xz-tabs_{$i}_tab_content"] = $tabContents[$i] . ' ';
|
||||||
|
$data['data']["_xz-tabs_{$i}_tab_content"] = "field_69ae43959a254";
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['data']['xz-tabs'] = $tabCount;
|
||||||
|
$data['data']['_xz-tabs'] = "field_69ae420f515f6";
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建参数表格 HTML
|
||||||
|
*/
|
||||||
|
private function buildTabTable(array $rows): string
|
||||||
|
{
|
||||||
|
$html = '<table class="spec-table">' . "\n";
|
||||||
|
$html .= '<tbody>' . "\n";
|
||||||
|
|
||||||
|
$translatedCount = 0;
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$value = $row['value'] ?? '';
|
||||||
|
if (empty($value) && $value !== '0') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalValue = $value;
|
||||||
|
// 翻译中文值
|
||||||
|
if (is_string($value) && $this->containsChinese($value)) {
|
||||||
|
$value = $this->translateIfNeeded($value, '英文');
|
||||||
|
$translatedCount++;
|
||||||
|
Log::info('翻译参数表格字段值', [
|
||||||
|
'label' => $row['label'],
|
||||||
|
'original' => $originalValue,
|
||||||
|
'translated' => $value,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '<tr>' . "\n";
|
||||||
|
$html .= '<td class="spec-name">' . htmlspecialchars($row['label']) . '</td>' . "\n";
|
||||||
|
$html .= '<td>' . htmlspecialchars($value) . '</td>' . "\n";
|
||||||
|
$html .= '</tr>' . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</tbody>' . "\n";
|
||||||
|
$html .= '</table>' . "\n";
|
||||||
|
|
||||||
|
if ($translatedCount > 0) {
|
||||||
|
Log::info('参数表格翻译完成', ['translated_count' => $translatedCount]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译文本(如果包含中文)
|
||||||
|
*/
|
||||||
|
private function translateIfNeeded(string $text, string $targetLang = '英文'): string
|
||||||
|
{
|
||||||
|
if (empty($text)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含中文
|
||||||
|
if (!$this->containsChinese($text)) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$spark = new SparkService();
|
||||||
|
$prompt = '将以下文本翻译为' . $targetLang . ':' . $text;
|
||||||
|
return $spark->chat($prompt, null, [], 'wpSend');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('翻译失败,使用原文本', ['text' => $text, 'error' => $e->getMessage()]);
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测文本是否包含中文
|
||||||
|
*/
|
||||||
|
private function containsChinese(string $text): bool
|
||||||
|
{
|
||||||
|
return preg_match('/[\x{4e00}-\x{9fff}]/u', $text) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user