From f32c49b7140396bed8bba43a0d51e0628a4a25c6 Mon Sep 17 00:00:00 2001 From: "WIN-C27C5MSVTPO\\16961" <1696136552@qq.com> Date: Wed, 11 Mar 2026 13:29:10 +0800 Subject: [PATCH] =?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 | 312 +++++++++++++------- 1 file changed, 204 insertions(+), 108 deletions(-) diff --git a/app/common/service/WordpressSiteService.php b/app/common/service/WordpressSiteService.php index 1ebac77..d85ede6 100644 --- a/app/common/service/WordpressSiteService.php +++ b/app/common/service/WordpressSiteService.php @@ -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 .= "\n"; + $content .= "

Parameters

\n"; + $content .= "\n\n"; + + // Parameters xz-tab block $tabData = $this->buildXzTabData($car); $content .= "\n\n"; - // 5. FAQ + // 5. Video section (如果有视频) + if ($videoMediaId && $videoSourceUrl) { + $content .= "\n"; + $content .= "

\n"; + $content .= "\n\n"; + + $content .= "\n"; + $content .= "

Video

\n"; + $content .= "\n\n"; + + $content .= "\n"; + $content .= "
\n"; + $content .= "\n\n"; + } + + // 6. FAQ if ($faqs->count() > 0) { $content .= "\n"; $content .= "

FAQ

\n"; @@ -1348,24 +1457,15 @@ class WordpressSiteService $html = '' . "\n"; $html .= '' . "\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 .= '' . "\n"; @@ -1377,10 +1477,6 @@ class WordpressSiteService $html .= '' . "\n"; $html .= '
' . "\n"; - if ($translatedCount > 0) { - Log::info('参数表格翻译完成', ['translated_count' => $translatedCount]); - } - return $html; }