This commit is contained in:
2026-03-11 13:29:10 +08:00
parent 45d045d4e8
commit f32c49b714
+204 -108
View File
@@ -147,6 +147,7 @@ class WordpressSiteService
"status" => 'publish', //发布状态
];
//$data['categories'][] = ["id" => $site['categories']];
//产品分类
$data['product_cat'] = explode(',', $site['categories']);
//车辆档案相关参数
@@ -795,31 +796,18 @@ class WordpressSiteService
$tempFilePath = $tempDir . md5($processedUrl) . '.' . $extension;
try {
Log::info('开始上传图片到WordPress', [
'original_url' => $imageUrl,
'processed_url' => $processedUrl,
'filename' => $filename,
'content_type' => $contentType,
'temp_file' => $tempFilePath,
]);
// 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, [
@@ -837,6 +825,151 @@ class WordpressSiteService
RequestOptions::VERIFY => false,
RequestOptions::TIMEOUT => 30,
]);
// 关闭文件句柄
if (is_resource($fileHandle)) {
fclose($fileHandle);
}
$result = json_decode($response->getBody()->getContents(), true);
return $result;
} catch (\Exception $e) {
Log::error('上传图片到WordPress失败', [
'image_url' => $imageUrl,
'error' => $e->getMessage(),
]);
throw new \Exception('上传图片失败:' . $e->getMessage());
} finally {
// 3. 删除临时文件
if (file_exists($tempFilePath)) {
@unlink($tempFilePath);
}
}
}
/**
* 上传视频到 WordPress 媒体库
*
* @param string $domain
* @param string $api_user
* @param string $api_secret
* @param string $videoUrl 视频URL
* @return array
* @throws \Exception
*/
public function uploadVideoToWp(string $domain, string $api_user, string $api_secret, string $videoUrl): array
{
// 使用 OssService 生成完整的视频URL(不带处理参数)
$ossUrl = (new OssService)->generatePrivateUrl($videoUrl, 3600);
$client = new Client();
// 从原始 URL 提取文件扩展名
$pathInfo = pathinfo(parse_url($videoUrl, PHP_URL_PATH));
$extension = strtolower($pathInfo['extension'] ?? 'mp4');
$filename = ($pathInfo['filename'] ?? 'video') . '.' . $extension;
// 确定Content-Type
$mimeTypes = [
'mp4' => 'video/mp4',
'webm' => 'video/webm',
'ogg' => 'video/ogg',
'mov' => 'video/quicktime',
'avi' => 'video/x-msvideo',
'wmv' => 'video/x-ms-wmv',
'flv' => 'video/x-flv',
];
$contentType = $mimeTypes[$extension] ?? 'video/mp4';
$mediaUrl = $domain . '/wp-json/wp/v2/media';
// 本地临时文件路径
$tempDir = runtime_path('temp') . '/wp_videos/';
if (!is_dir($tempDir)) {
mkdir($tempDir, 0755, true);
}
$tempFilePath = $tempDir . md5($videoUrl) . '.' . $extension;
try {
// 1. 下载视频到本地临时文件
$videoContent = file_get_contents($ossUrl);
if ($videoContent === false) {
throw new \Exception('视频下载失败:' . $ossUrl);
}
// 写入临时文件
$bytesWritten = file_put_contents($tempFilePath, $videoContent);
if ($bytesWritten === false) {
throw new \Exception('写入临时文件失败:' . $tempFilePath);
}
// 2. 使用 GuzzleHttp 上传本地文件到 WP 媒体库
$fileHandle = fopen($tempFilePath, 'r');
$safeFilename = 'video_' . time() . '.' . $extension;
$multipart = [
[
'name' => 'file',
'filename' => $safeFilename,
'contents' => $fileHandle,
'headers' => [
'Content-Type' => $contentType,
],
],
];
try {
$response = $client->post($mediaUrl, [
RequestOptions::AUTH => [$api_user, $api_secret],
RequestOptions::MULTIPART => $multipart,
RequestOptions::VERIFY => false,
RequestOptions::TIMEOUT => 300,
RequestOptions::HEADERS => [
'Accept' => 'application/json',
],
]);
} catch (\Exception $uploadException) {
// GuzzleHttp 上传失败,尝试使用 curl
if (is_resource($fileHandle)) {
fclose($fileHandle);
}
// 使用原生 curl 上传
$ch = curl_init();
$cfile = new \CURLFile($tempFilePath, $contentType, $safeFilename);
curl_setopt_array($ch, [
CURLOPT_URL => $mediaUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file' => $cfile],
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_USERPWD => $api_user . ':' . $api_secret,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 300,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: multipart/form-data',
],
]);
$responseBody = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError) {
throw new \Exception('Curl上传失败:' . $curlError);
}
if ($httpCode >= 400) {
throw new \Exception('Curl上传失败 HTTP ' . $httpCode . ': ' . $responseBody);
}
// 创建一个模拟的 Response 对象
$response = new \GuzzleHttp\Psr7\Response($httpCode, [], $responseBody);
}
// 关闭文件句柄
if (is_resource($fileHandle)) {
@@ -845,32 +978,23 @@ class WordpressSiteService
$result = json_decode($response->getBody()->getContents(), true);
Log::info('图片上传成功', [
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),
'source_url' => $result['source_url'] ?? 'unknown',
]);
return $result;
} catch (\Exception $e) {
Log::error('上传图片到WordPress失败', [
'domain' => $domain,
'image_url' => $imageUrl,
'processed_url' => $processedUrl ?? 'N/A',
'temp_file' => $tempFilePath ?? 'N/A',
Log::error('上传视频到WordPress失败', [
'video_url' => $videoUrl,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw new \Exception('上传图片失败:' . $e->getMessage());
throw new \Exception('上传视频失败:' . $e->getMessage());
} finally {
// 3. 删除临时文件
if (file_exists($tempFilePath)) {
@unlink($tempFilePath);
Log::info('临时文件已删除', ['file' => $tempFilePath]);
}
}
}
@@ -907,15 +1031,6 @@ class WordpressSiteService
$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)) {
@@ -931,12 +1046,6 @@ class WordpressSiteService
}
}
Log::info('开始处理车辆图片', [
'car_id' => $carId,
'images_final' => $images,
'images_count' => count($images),
]);
// 上传图片到WordPress媒体库
$uploadedImages = []; // 存储完整信息 ['id' => xx, 'url' => xx, ...]
$uploadedImageIds = []; // 仅存储ID
@@ -944,7 +1053,6 @@ class WordpressSiteService
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'],
@@ -962,12 +1070,19 @@ class WordpressSiteService
}
}
Log::info('图片上传完成', [
'total' => count($images),
'success' => count($uploadedImages),
'uploaded_ids' => $uploadedImageIds,
'uploaded_details' => $uploadedImages,
]);
// 上传视频到WordPress媒体库
$videoMediaId = null;
$videoSourceUrl = null;
if (!empty($car->intro_video)) {
try {
$uploadedVideo = $this->uploadVideoToWp($domain, $site->api_user, $site->api_secret, $car->intro_video);
$videoMediaId = $uploadedVideo['id'];
$videoSourceUrl = $uploadedVideo['source_url'] ?? $uploadedVideo['url'] ?? '';
} catch (\Exception $e) {
Log::error('视频上传失败', ['error' => $e->getMessage()]);
// 视频上传失败不影响整体推送,继续执行
}
}
// 获取FAQ数据
$faqs = \app\common\model\UsedCarFaq::where('car_id', $carId)
@@ -979,7 +1094,7 @@ class WordpressSiteService
$title = $this->translateIfNeeded($car->product_name, '英文');
// 构建Gutenberg Blocks格式content
$content = $this->buildGutenbergContent($car, $uploadedImages, $faqs);
$content = $this->buildGutenbergContent($car, $uploadedImages, $faqs, $videoMediaId, $videoSourceUrl);
// 构建产品数据
$data = [
@@ -990,6 +1105,9 @@ class WordpressSiteService
'regular_price' => number_format($car->price * $site->exchange_rate, 2),
];
// 设置产品分类使用 product_cat 字段)
$data['product_cat'] = [15, 16];
// 设置主图和车辆相册(ACF字段)
if (!empty($uploadedImages)) {
$allImageIds = array_map(function($img) {
@@ -1004,13 +1122,6 @@ class WordpressSiteService
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车辆参数字段(与旧站点保持一致)
@@ -1032,37 +1143,28 @@ class WordpressSiteService
$value = $this->translateColorToEnglish($value);
} elseif ($acfKey === 'source_place' && $this->containsChinese($value)) {
$value = $this->translateIfNeeded($value, '英文');
} elseif ($acfKey === 'Mileage' && $acfKey) {
$value = $this->translateIfNeeded($value, '英文');
}
$data['acf'][$acfKey] = $value;
}
}
// 翻译并设置车况介绍和产品简介(ACF字段)
$conditionDesc = $car->condition_desc ?? '';
if (!empty($conditionDesc)) {
// 产品简介(完整翻译)
$data['acf']['condition_desc'] = $this->translateIfNeeded($conditionDesc, '英文');
// $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;
// }
// 车况介绍(简短翻译,去掉商家信息)
$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'] ?? [],
]);
// 视频通过 content 中的 video block 展示,不需要 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,
@@ -1127,7 +1229,7 @@ class WordpressSiteService
/**
* 构建 Gutenberg Blocks 格式 content
*/
private function buildGutenbergContent($car, array $uploadedImages, $faqs): string
private function buildGutenbergContent($car, array $uploadedImages, $faqs, $videoMediaId = null, $videoSourceUrl = null): string
{
$content = '';
@@ -1143,14 +1245,6 @@ class WordpressSiteService
$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张
@@ -1160,11 +1254,6 @@ class WordpressSiteService
$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'] ?? '';
@@ -1200,11 +1289,31 @@ class WordpressSiteService
}
}
// 4. Parameters xz-tab block
// 4. Parameters 标题
$content .= "<!-- wp:heading -->\n";
$content .= "<h2 class=\"wp-block-heading\" id=\"Parameters\">Parameters</h2>\n";
$content .= "<!-- /wp:heading -->\n\n";
// Parameters xz-tab block
$tabData = $this->buildXzTabData($car);
$content .= "<!-- wp:acf/xz-tab " . json_encode($tabData, JSON_UNESCAPED_UNICODE) . " /-->\n\n";
// 5. FAQ
// 5. Video section (如果有视频)
if ($videoMediaId && $videoSourceUrl) {
$content .= "<!-- wp:paragraph -->\n";
$content .= "<p></p>\n";
$content .= "<!-- /wp:paragraph -->\n\n";
$content .= "<!-- wp:heading -->\n";
$content .= "<h2 class=\"wp-block-heading\" id=\"Video\">Video</h2>\n";
$content .= "<!-- /wp:heading -->\n\n";
$content .= "<!-- wp:video {\"id\":" . $videoMediaId . "} -->\n";
$content .= "<figure class=\"wp-block-video\"><video controls src=\"" . htmlspecialchars($videoSourceUrl) . "\"></video></figure>\n";
$content .= "<!-- /wp:video -->\n\n";
}
// 6. FAQ
if ($faqs->count() > 0) {
$content .= "<!-- wp:heading -->\n";
$content .= "<h2 class=\"wp-block-heading\" id=\"FAQ\">FAQ</h2>\n";
@@ -1348,24 +1457,15 @@ class WordpressSiteService
$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";
@@ -1377,10 +1477,6 @@ class WordpressSiteService
$html .= '</tbody>' . "\n";
$html .= '</table>' . "\n";
if ($translatedCount > 0) {
Log::info('参数表格翻译完成', ['translated_count' => $translatedCount]);
}
return $html;
}