{"content":"<?php\n/**\n * 从苹果 CMS V10 数据库 (mac_vod) 获取最近一周更新的 URL,生成 sitemap 文件(rss/sitemap_weekly.xml)\n * 提交 sitemap 到 Google Search Console (GSC),每周运行一次\n * 不检查 URL 收录状态,仅生成和提交单一 sitemap\n */\ndate_default_timezone_set('Asia/Shanghai');\nrequire_once __DIR__ . '/config.php'; // 需包含 $serviceAccountFile, $property, $logFile, $dbConfig\nset_time_limit(0);\nini_set('memory_limit', '512M');\n\n/**\n * 日志记录函数(带文件锁和轮换)\n */\nfunction logMsg($msg, $logFile) {\n if (!is_writable($logFile) && !is_writable(dirname($logFile))) {\n die(\"Log file is not writable: $logFile\");\n }\n if (file_exists($logFile) && filesize($logFile) > 5 * 1024 * 1024) { // 5MB\n $lock = fopen($logFile, 'a');\n if (flock($lock, LOCK_EX)) {\n rename($logFile, $logFile . '.old');\n flock($lock, LOCK_UN);\n }\n fclose($lock);\n }\n echo $msg . \"\\n\";\n file_put_contents($logFile, $msg . \"\\n\", FILE_APPEND | LOCK_EX);\n}\n\n/**\n * 获取 Google API Access Token\n */\nfunction getAccessToken($serviceAccountFile, $logFile) {\n if (!file_exists($serviceAccountFile)) {\n logMsg(\"Service account JSON file not found: $serviceAccountFile\", $logFile);\n return null;\n }\n $json = json_decode(file_get_contents($serviceAccountFile), true);\n if (json_last_error() !== JSON_ERROR_NONE) {\n logMsg(\"Invalid JSON in service account file: \" . json_last_error_msg(), $logFile);\n return null;\n }\n\n $header = ['alg' => 'RS256', 'typ' => 'JWT'];\n $now = time();\n $claim = [\n 'iss' => $json['client_email'],\n 'scope' => 'https://www.googleapis.com/auth/webmasters',\n 'aud' => 'https://oauth2.googleapis.com/token',\n 'iat' => $now,\n 'exp' => $now + 3600\n ];\n\n $base64url = function($data) { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); };\n $jwt = $base64url(json_encode($header)) . '.' . $base64url(json_encode($claim));\n\n $privateKey = openssl_pkey_get_private($json['private_key']);\n if ($privateKey === false) {\n logMsg(\"Failed to load private key: \" . openssl_error_string(), $logFile);\n return null;\n }\n if (!openssl_sign($jwt, $signature, $privateKey, 'SHA256')) {\n logMsg(\"Failed to sign JWT: \" . openssl_error_string(), $logFile);\n return null;\n }\n $jwt .= '.' . $base64url($signature);\n\n $post = http_build_query([\n 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n 'assertion' => $jwt\n ]);\n\n $ch = curl_init('https://oauth2.googleapis.com/token');\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_POSTFIELDS => $post,\n CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],\n CURLOPT_TIMEOUT => 10\n ]);\n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n\n if ($httpCode !== 200) {\n logMsg(\"Failed to get Access Token, HTTP $httpCode: $response\", $logFile);\n return null;\n }\n\n $data = json_decode($response, true);\n if (isset($data['access_token'])) {\n return $data['access_token'];\n }\n logMsg(\"Access Token not found in response: $response\", $logFile);\n return null;\n}\n\n/**\n * 生成 sitemap 文件\n */\nfunction generateSitemap($urlList, $sitemapFile, $logFile) {\n $xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>' . \"\\n\";\n $xml .= '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">' . \"\\n\";\n foreach ($urlList as $url) {\n $xml .= \" <url>\\n\";\n $xml .= \" <loc>\" . htmlspecialchars($url) . \"</loc>\\n\";\n $xml .= \" <lastmod>\" . date('Y-m-d') . \"</lastmod>\\n\";\n $xml .= \" <changefreq>daily</changefreq>\\n\";\n $xml .= \" <priority>0.8</priority>\\n\";\n $xml .= \" </url>\\n\";\n }\n $xml .= '</urlset>';\n\n $dir = dirname($sitemapFile);\n if (!is_dir($dir)) {\n if (!mkdir($dir, 0755, true)) {\n logMsg(\"Failed to create directory: $dir\", $logFile);\n return false;\n }\n }\n if (file_put_contents($sitemapFile, $xml, LOCK_EX) === false) {\n logMsg(\"Failed to write sitemap file: $sitemapFile\", $logFile);\n return false;\n }\n logMsg(\"Generated sitemap file: $sitemapFile with \" . count($urlList) . \" URLs\", $logFile);\n return true;\n}\n\n/**\n * 提交 sitemap 到 GSC\n */\nfunction submitSitemap($sitemapUrl, $property, $accessToken, $logFile) {\n $encodedSiteUrl = urlencode($property);\n $encodedSitemapUrl = urlencode($sitemapUrl);\n $ch = curl_init(\"https://searchconsole.googleapis.com/webmasters/v3/sites/{$encodedSiteUrl}/sitemaps/{$encodedSitemapUrl}\");\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_CUSTOMREQUEST => 'PUT',\n CURLOPT_HTTPHEADER => [\n 'Authorization: Bearer ' . $accessToken,\n 'Content-Type: application/json',\n 'Content-Length: 0'\n ],\n CURLOPT_TIMEOUT => 10\n ]);\n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n logMsg(\"提交 Sitemap: $sitemapUrl | HTTP $httpCode | 响应: \" . json_encode($response, JSON_UNESCAPED_UNICODE), $logFile);\n return $httpCode === 200 || $httpCode === 204;\n}\n\ntry {\n // 数据库连接\n $dsn = \"mysql:host={$dbConfig['host']};dbname={$dbConfig['dbname']};charset=utf8mb4\";\n try {\n $pdo = new PDO($dsn, $dbConfig['username'], $dbConfig['password'], \n [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);\n logMsg(\"Database connection successful: {$dbConfig['dbname']}\", $logFile);\n } catch (PDOException $e) {\n logMsg(\"Database connection failed: \" . $e->getMessage(), $logFile);\n throw new Exception(\"Failed to connect to database: \" . $e->getMessage());\n }\n \n // 查询最近 7 天的 vod_id(时间戳格式),按 vod_time 倒序\n $oneWeekAgo = time() - 7 * 24 * 3600; // 一周前时间戳\n $stmt = $pdo->query(\"SELECT vod_id FROM mac_vod WHERE vod_time >= $oneWeekAgo ORDER BY vod_time DESC\");\n $urlList = [];\n while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {\n $urlList[] = \"https://www.cofentea.com/voddetail/{$row['vod_id']}/\";\n }\n logMsg(\"Queried \" . count($urlList) . \" URLs from mac_vod for the past 7 days\", $logFile);\n\n // 生成 sitemap 文件(包含最近一周的 URL)\n $sitemapFile = __DIR__ . '/../rss/sitemap_weekly.xml'; // 保存到根目录的 rss 文件夹\n $sitemapUrl = 'https://www.cofentea.com/rss/sitemap_weekly.xml'; // GSC 提交的 URL\n if (!empty($urlList)) {\n generateSitemap($urlList, $sitemapFile, $logFile);\n } else {\n logMsg(\"No URLs found for sitemap generation.\", $logFile);\n }\n\n // 获取 Access Token\n $accessToken = getAccessToken($serviceAccountFile, $logFile);\n if (!$accessToken) {\n throw new Exception(\"Failed to obtain Access Token.\");\n }\n\n // 验证 GSC 属性\n $encodedSiteUrl = urlencode($property);\n $ch = curl_init(\"https://searchconsole.googleapis.com/webmasters/v3/sites/{$encodedSiteUrl}\");\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_HTTPHEADER => [\n 'Authorization: Bearer ' . $accessToken,\n 'Content-Type: application/json'\n ],\n CURLOPT_TIMEOUT => 10\n ]);\n $siteResponse = curl_exec($ch);\n $siteHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n logMsg(\"验证 GSC 属性: {$property} | HTTP $siteHttpCode | 响应: \" . json_encode($siteResponse, JSON_UNESCAPED_UNICODE), $logFile);\n if ($siteHttpCode !== 200) {\n throw new Exception(\"Invalid GSC property: {$property}, HTTP $siteHttpCode\");\n }\n\n // 提交 sitemap 到 GSC\n if (!empty($urlList)) {\n submitSitemap($sitemapUrl, $property, $accessToken, $logFile);\n }\n\n // 输出完成信息\n logMsg(\"\\n=== 完成 ===\\n\" .\n \"完成时间: \" . date('Y-m-d H:i:s'), $logFile);\n\n} catch (Exception $e) {\n logMsg(\"错误: \" . $e->getMessage(), $logFile);\n}\n?>","created_at":"2025-09-30T19:53:28.073Z","updated_at":"2025-09-30T19:53:30.035Z","encrypted":false}