1527
This commit is contained in:
1 parent
c506aadb46
commit
97217d0cc0
1 file changed
+61
-28
@@ -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<void> {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 功能 1: 友链状态监控
|
||||
// 功能 1: 友链状态监控(使用 SCF 代理)
|
||||
// ============================================================
|
||||
|
||||
export async function checkLinksStatus(): Promise<void> {
|
||||
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<void> {
|
||||
`🔗 <b>友链状态监控报告</b>`,
|
||||
``,
|
||||
`📊 <b>总体情况:</b>`,
|
||||
`├─ 📁 总数:${total} 个`,
|
||||
`├─ ✅ 有效:${alive} 个`,
|
||||
`├─ ❌ 失效:${dead} 个`,
|
||||
`├─ 📁 总数:${links.length} 个`,
|
||||
`├─ ✅ 有效:${validLinks.length} 个`,
|
||||
`├─ ❌ 失效:${invalidLinks.length} 个`,
|
||||
`└─ ⚠️ 缓慢:${slowLinks.length} 个(> 3 秒)`,
|
||||
``,
|
||||
];
|
||||
|
||||
if (invalidLinks.length > 0) {
|
||||
msg.push(`🚨 <b>失效链接:</b>`);
|
||||
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(`🐢 <b>响应缓慢:</b>`);
|
||||
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(`💡 <b>建议:</b>尽快检查并修复失效链接,保持友链健康`);
|
||||
msg.push(`📡 <b>检测方式:</b>SCF 代理`);
|
||||
|
||||
await notifyAll(msg.join("\n"));
|
||||
console.log("[blog-monitor] 📤 links notification sent");
|
||||
@@ -132,14 +155,16 @@ export async function checkBlogMilestones(): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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`);
|
||||
|
||||
Reference in new issue
Block a user