2026-05-25: 等级体系升级(20级)+论坛二级分类+会员空间
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
||||
import "dotenv/config";
|
||||
import { withRetry } from "./lib/retry.mjs";
|
||||
|
||||
const base = process.env.DATABASE_URL || "mysql://localhost:3306/zhuiguang_ai?charset=utf8mb4";
|
||||
const sep = base.includes("?") ? "&" : "?";
|
||||
const connectionString = `${base}${sep}connection_limit=20&pool_timeout=30`;
|
||||
const adapter = new PrismaMariaDb(connectionString);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
async function checkWebsite(url) {
|
||||
try {
|
||||
const res = await withRetry(async () => {
|
||||
return await fetch(url, {
|
||||
method: "GET",
|
||||
signal: AbortSignal.timeout(8000),
|
||||
redirect: "follow",
|
||||
headers: { "User-Agent": "Mozilla/5.0 (compatible; CheckBot/1.0)" },
|
||||
});
|
||||
}, { maxRetries: 2, label: `Website ${url}` });
|
||||
return res.status >= 200 && res.status < 400;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkLogo(logoUrl) {
|
||||
if (!logoUrl) return false;
|
||||
if (logoUrl.startsWith("/logos/")) return true;
|
||||
if (logoUrl.startsWith("data:")) return true;
|
||||
try {
|
||||
const res = await withRetry(async () => {
|
||||
return await fetch(logoUrl, { method: "HEAD", signal: AbortSignal.timeout(5000) });
|
||||
}, { maxRetries: 2, label: `Logo ${logoUrl}` });
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const startTime = new Date();
|
||||
const now = new Date();
|
||||
const cnOffset = 8 * 60 * 60 * 1000;
|
||||
const cnTime = new Date(now.getTime() + cnOffset);
|
||||
const today = cnTime.toISOString().split("T")[0];
|
||||
|
||||
console.log(`🔍 [Task3] ${today} 巡检已入库工具...`);
|
||||
|
||||
const tools = await prisma.tool.findMany({
|
||||
select: { id: true, name: true, websiteUrl: true, logoUrl: true, status: true },
|
||||
});
|
||||
const pendingTools = await prisma.pendingTool.findMany({
|
||||
where: { status: "pending" },
|
||||
select: { id: true, name: true, websiteUrl: true, logoUrl: true, checkWebsiteOk: true, checkLogoOk: true },
|
||||
});
|
||||
|
||||
console.log(` 已发布工具: ${tools.length}, 待审核: ${pendingTools.length}`);
|
||||
|
||||
let checked = 0;
|
||||
let websiteFails = 0;
|
||||
let logoFails = 0;
|
||||
let autoPublished = 0;
|
||||
const errors = [];
|
||||
|
||||
for (const tool of tools) {
|
||||
try {
|
||||
const websiteOk = await checkWebsite(tool.websiteUrl);
|
||||
const logoOk = await checkLogo(tool.logoUrl);
|
||||
|
||||
await prisma.tool.update({
|
||||
where: { id: tool.id },
|
||||
data: { lastCheckedAt: new Date(), websiteOk, logoOk },
|
||||
});
|
||||
|
||||
if (!websiteOk) websiteFails++;
|
||||
if (!logoOk) logoFails++;
|
||||
checked++;
|
||||
} catch (e) {
|
||||
errors.push(`Tool ${tool.name}: ${e.message}`);
|
||||
}
|
||||
|
||||
if (checked % 10 === 0) {
|
||||
console.log(` 进度: ${checked}/${tools.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n 已发布工具巡检完成: ${checked}个, 网站失效${websiteFails}个, Logo异常${logoFails}个`);
|
||||
|
||||
checked = 0;
|
||||
for (const pt of pendingTools) {
|
||||
try {
|
||||
const websiteOk = pt.websiteUrl ? await checkWebsite(pt.websiteUrl) : false;
|
||||
const logoOk = await checkLogo(pt.logoUrl);
|
||||
|
||||
await prisma.pendingTool.update({
|
||||
where: { id: pt.id },
|
||||
data: { checkWebsiteOk: websiteOk, checkLogoOk: logoOk, checkDescOk: true, checkCategoryOk: true },
|
||||
});
|
||||
|
||||
if (websiteOk && logoOk) {
|
||||
const slug = pt.slug || pt.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") + "-" + Date.now().toString(36);
|
||||
const existingSlug = await prisma.tool.findUnique({ where: { slug } });
|
||||
if (!existingSlug) {
|
||||
await prisma.tool.create({
|
||||
data: {
|
||||
name: pt.name,
|
||||
slug,
|
||||
categoryId: pt.categoryId || 1,
|
||||
description: pt.description || "",
|
||||
logoUrl: pt.logoUrl,
|
||||
websiteUrl: pt.websiteUrl || "",
|
||||
pricingModel: pt.pricingModel || "freemium",
|
||||
pricingDetail: pt.pricingDetail,
|
||||
features: pt.features,
|
||||
tags: pt.tags,
|
||||
status: "published",
|
||||
websiteOk: true,
|
||||
logoOk: true,
|
||||
},
|
||||
});
|
||||
await prisma.pendingTool.delete({ where: { id: pt.id } });
|
||||
autoPublished++;
|
||||
console.log(` ✅ 自动发布: ${pt.name}`);
|
||||
}
|
||||
}
|
||||
checked++;
|
||||
} catch (e) {
|
||||
errors.push(`PendingTool ${pt.name}: ${e.message}`);
|
||||
}
|
||||
|
||||
if (checked % 5 === 0) {
|
||||
console.log(` 待审核进度: ${checked}/${pendingTools.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
const endTime = new Date();
|
||||
const duration = endTime.getTime() - startTime.getTime();
|
||||
const result = {
|
||||
totalTools: tools.length,
|
||||
totalPending: pendingTools.length,
|
||||
websiteFails,
|
||||
logoFails,
|
||||
autoPublished,
|
||||
duration: `${(duration / 1000).toFixed(1)}s`,
|
||||
};
|
||||
|
||||
console.log(`\n🏁 [Task3] 完成: 巡检${tools.length + pendingTools.length}个, 自动发布${autoPublished}个, 网站失效${websiteFails}个`);
|
||||
|
||||
await prisma.taskLog.create({
|
||||
data: {
|
||||
taskKey: "task3-check-tools",
|
||||
taskName: "工具巡检",
|
||||
status: errors.length > 0 ? "completed" : "completed",
|
||||
startedAt: startTime,
|
||||
finishedAt: endTime,
|
||||
duration,
|
||||
result,
|
||||
error: errors.length > 0 ? errors.slice(0, 5).join("\n") : null,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
main().catch(async (e) => {
|
||||
console.error("❌ 任务三失败:", e.message);
|
||||
if (prisma) {
|
||||
try {
|
||||
await prisma.taskLog.create({
|
||||
data: {
|
||||
taskKey: "task3-check-tools",
|
||||
taskName: "工具巡检",
|
||||
status: "failed",
|
||||
startedAt: new Date(),
|
||||
finishedAt: new Date(),
|
||||
duration: 0,
|
||||
error: e.message,
|
||||
},
|
||||
});
|
||||
} catch {}
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user