From 97217d0cc0064e271e1c99b25ae6f49db1da811f Mon Sep 17 00:00:00 2001 From: Vaica <94612053+zqlit@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:28:02 +0800 Subject: [PATCH] 1527 --- write-server/src/lib/bot/blog-monitor.ts | 89 ++++++++++++++++-------- 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/write-server/src/lib/bot/blog-monitor.ts b/write-server/src/lib/bot/blog-monitor.ts index 5ee78d6b..f8d807df 100644 --- a/write-server/src/lib/bot/blog-monitor.ts +++ b/write-server/src/lib/bot/blog-monitor.ts @@ -1,7 +1,7 @@ import { sendMessage } from "./tg"; import { ALLOWED_IDS } from "./config"; -import { getFeedsHealth } from "@/lib/rssapi"; -import { ARTALK_SERVER, ARTALK_SITE, ARTALK_ADMIN_EMAIL, ARTALK_ADMIN_PASSWORD } from "@/lib/config"; +import { getLinks, checkLinkViaProxy } from "@/lib/rssapi"; +import { ARTALK_SERVER, ARTALK_SITE, ARTALK_ADMIN_EMAIL, ARTALK_ADMIN_PASSWORD, CONTENT_DIR } from "@/lib/config"; import fs from "fs"; import path from "path"; @@ -51,28 +51,51 @@ async function notifyAll(message: string): Promise { } // ============================================================ -// 功能 1: 友链状态监控 +// 功能 1: 友链状态监控(使用 SCF 代理) // ============================================================ export async function checkLinksStatus(): Promise { console.log("[blog-monitor] 🔗 checking links status..."); try { - // 直接调用 API 的健康检查端点 - const healthResult = await getFeedsHealth(); + // 获取可见友链(不传 all 参数,API 自动过滤隐藏友链) + const links = await getLinks(); - if (!healthResult || !healthResult.results) { - console.log("[blog-monitor] ❌ failed to fetch health status"); + if (links.length === 0) { + console.log("[blog-monitor] ℹ️ no visible links found"); return; } - const { total, alive, dead, results } = healthResult; + console.log(`[blog-monitor] 📊 checking ${links.length} visible links...`); + + // 使用 SCF 代理并行检查所有链接(限制并发数为 10) + const batchSize = 10; + const results: Array<{ name: string; url: string; ok: boolean; status?: number; latency?: number; error?: string }> = []; + + for (let i = 0; i < links.length; i += batchSize) { + const batch = links.slice(i, i + batchSize); + const batchResults = await Promise.all( + batch.map(async (link) => { + const health = await checkLinkViaProxy(link.url); + return { + name: link.name, + url: link.url, + ok: health.ok, + status: health.status, + latency: health.latency, + error: health.error, + }; + }) + ); + results.push(...batchResults); + } // 分类链接状态 const invalidLinks = results.filter((r) => !r.ok); const slowLinks = results.filter((r) => r.ok && r.latency && r.latency > 3000); + const validLinks = results.filter((r) => r.ok); - console.log(`[blog-monitor] ✅ links check completed: ${alive} valid, ${dead} invalid, ${slowLinks.length} slow`); + console.log(`[blog-monitor] ✅ links check completed: ${validLinks.length} valid, ${invalidLinks.length} invalid, ${slowLinks.length} slow`); // 只在有失效或缓慢链接时发送通知 if (invalidLinks.length > 0 || slowLinks.length > 0) { @@ -80,39 +103,39 @@ export async function checkLinksStatus(): Promise { `🔗 友链状态监控报告`, ``, `📊 总体情况:`, - `├─ 📁 总数:${total} 个`, - `├─ ✅ 有效:${alive} 个`, - `├─ ❌ 失效:${dead} 个`, + `├─ 📁 总数:${links.length} 个`, + `├─ ✅ 有效:${validLinks.length} 个`, + `├─ ❌ 失效:${invalidLinks.length} 个`, `└─ ⚠️ 缓慢:${slowLinks.length} 个(> 3 秒)`, ``, ]; if (invalidLinks.length > 0) { msg.push(`🚨 失效链接:`); - invalidLinks.slice(0, 5).forEach((link, index) => { + invalidLinks.forEach((link, index) => { const error = link.error || `HTTP ${link.status}`; - const prefix = index === invalidLinks.length - 1 || index === 4 ? "└─" : "├─"; - msg.push(`${prefix} 🔴 ${link.url}`); + const prefix = index === invalidLinks.length - 1 ? "└─" : "├─"; + msg.push(`${prefix} 🔴 ${link.name}`); + msg.push(` └─ ${link.url}`); msg.push(` └─ ${error}`); }); - if (invalidLinks.length > 5) { - msg.push(` ⋯ 还有 ${invalidLinks.length - 5} 个`); - } msg.push(``); } if (slowLinks.length > 0) { msg.push(`🐢 响应缓慢:`); - slowLinks.slice(0, 3).forEach((link, index) => { + slowLinks.forEach((link, index) => { const latency = ((link.latency || 0) / 1000).toFixed(1); - const prefix = index === slowLinks.length - 1 || index === 2 ? "└─" : "├─"; - msg.push(`${prefix} 🟡 ${link.url}`); + const prefix = index === slowLinks.length - 1 ? "└─" : "├─"; + msg.push(`${prefix} 🟡 ${link.name}`); + msg.push(` └─ ${link.url}`); msg.push(` └─ ${latency}秒`); }); msg.push(``); } msg.push(`💡 建议:尽快检查并修复失效链接,保持友链健康`); + msg.push(`📡 检测方式:SCF 代理`); await notifyAll(msg.join("\n")); console.log("[blog-monitor] 📤 links notification sent"); @@ -132,14 +155,16 @@ export async function checkBlogMilestones(): Promise { console.log("[blog-monitor] 🎯 checking blog milestones..."); try { - // 获取文章数量(通过读取 content/posts 目录) - const postsDir = path.join(process.cwd(), "..", "content", "posts"); + // 使用配置中的 CONTENT_DIR + console.log(`[blog-monitor] 📂 posts directory: ${CONTENT_DIR}`); let postsCount = 0; - if (fs.existsSync(postsDir)) { - const years = fs.readdirSync(postsDir).filter((f) => /^\d{4}$/.test(f)); + if (fs.existsSync(CONTENT_DIR)) { + const years = fs.readdirSync(CONTENT_DIR).filter((f) => /^\d{4}$/.test(f)); + console.log(`[blog-monitor] 📅 found years: ${years.join(", ")}`); + for (const year of years) { - const yearDir = path.join(postsDir, year); + const yearDir = path.join(CONTENT_DIR, year); if (fs.statSync(yearDir).isDirectory()) { const posts = fs.readdirSync(yearDir).filter((f) => { const fullPath = path.join(yearDir, f); @@ -148,11 +173,14 @@ export async function checkBlogMilestones(): Promise { postsCount += posts.length; } } + } else { + console.error(`[blog-monitor] ❌ posts directory not found: ${CONTENT_DIR}`); } // 获取评论数量 let commentsCount = 0; const token = await getArtalkToken(); + console.log(`[blog-monitor] 🔑 artalk token: ${token ? "obtained" : "failed"}`); if (token) { try { @@ -164,10 +192,15 @@ export async function checkBlogMilestones(): Promise { if (res.ok) { const data = await res.json() as { count?: number }; commentsCount = data.count || 0; + console.log(`[blog-monitor] 💬 comments API response: ${JSON.stringify(data)}`); + } else { + console.error(`[blog-monitor] ❌ comments API failed: ${res.status}`); } - } catch { - console.error("[blog-monitor] ❌ failed to fetch comments count"); + } catch (e) { + console.error("[blog-monitor] ❌ failed to fetch comments:", e instanceof Error ? e.message : String(e)); } + } else { + console.error("[blog-monitor] ❌ no artalk token, cannot fetch comments"); } console.log(`[blog-monitor] 📊 stats: ${postsCount} posts, ${commentsCount} comments`);