Files
zhuiguang-ai/scripts/task7-news-to-community.mjs
T
ZhuiGuangAI Dev a9145c4dc1 修复自动化任务:bot-activity上线 + task7板块slug修复 + cron强化
问题排查与修复:
1. 🔴 bot-activity从未运行 — 新增cron任务(9-23点每小时15轮)
2. 🔴 task7报错'板块不存在: ai-tools' — 更新所有slug为新的51子板块格式
3. 🟡 install-cron.sh旧cron清理不完整 — 改为覆盖模式
4. 🟡 Google News RSS被墙 — 优化fallback提示词用DeepSeek知识+当前日期

效果:
- 8个定时任务全部正常运行
- 首次运行产出10个话题(7+3),社区从12→24帖
- 每小时15轮持续产出,预计每天~45帖
2026-06-01 01:39:38 +08:00

225 lines
6.9 KiB
JavaScript

import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import "dotenv/config";
const base = (process.env.DATABASE_URL || "mysql://localhost:3306/zhuiguang_ai?charset=utf8mb4").replace("mysql://", "mariadb://");
const sep = base.includes("?") ? "&" : "?";
const connectionString = `${base}${sep}connection_limit=20&pool_timeout=30`;
const adapter = new PrismaMariaDb(connectionString);
const prisma = new PrismaClient({ adapter });
function ts() {
return `[${new Date().toISOString()}]`;
}
function generateSlug(title) {
return title
.toLowerCase()
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-")
.replace(/^-|-$/g, "");
}
function ensureUniqueSlug(title) {
const baseSlug = generateSlug(title) || "topic";
const clean = baseSlug.substring(0, 80);
const suffix = Date.now().toString(36);
return `${clean}-${suffix}`;
}
const CATEGORY_MAP = {
"产品发布": "ai-tools-app",
"product": "ai-tools-app",
"融资投资": "ai-startup-forum",
"business": "ai-startup-forum",
"技术突破": "ai-llm",
"model": "ai-llm",
"政策法规": "ai-startup-forum",
"policy": "ai-startup-forum",
"开源项目": "ai-llm",
"opensource": "ai-llm",
"行业事件": "ai-startup-forum",
"insight": "ai-startup-forum",
};
async function main() {
if (!process.env.DATABASE_URL) throw new Error("缺少环境变量: DATABASE_URL");
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];
const yesterdayDate = new Date(cnTime.getTime() - 24 * 60 * 60 * 1000);
const yesterday = yesterdayDate.toISOString().split("T")[0];
console.log(`${ts()} 📰 [Task7] 开始将AI日报分发到社区板块 ${today}`);
let report = await prisma.dailyReport.findFirst({
where: { date: new Date(today), status: "published" },
include: { items: { orderBy: { sortOrder: "asc" } } },
});
if (!report) {
console.log(`${ts()} ⚠ 今日(${today})无日报,尝试昨天(${yesterday})...`);
report = await prisma.dailyReport.findFirst({
where: { date: new Date(yesterday), status: "published" },
include: { items: { orderBy: { sortOrder: "asc" } } },
});
}
if (!report || report.items.length === 0) {
console.log(`${ts()} ⚠ 没有可用的日报条目,跳过`);
await prisma.taskLog.create({
data: {
taskKey: "task7-news-to-community",
taskName: "AI日报分发到社区",
status: "completed",
startedAt: startTime,
finishedAt: new Date(),
duration: new Date().getTime() - startTime.getTime(),
result: { created: 0, skipped: 0, message: "没有可用的日报条目" },
},
});
await prisma.$disconnect();
return;
}
console.log(`${ts()} 📋 日报ID=${report.id}, 共${report.items.length}条新闻`);
const forumCategories = await prisma.forumCategory.findMany({
where: {
slug: { in: ["ai-tools-app", "ai-startup-forum", "ai-llm"] },
},
});
const categoryBySlug = {};
for (const c of forumCategories) {
categoryBySlug[c.slug] = c;
}
console.log(`${ts()} 📂 已加载${forumCategories.length}个社区板块: ${Object.keys(categoryBySlug).join(", ")}`);
const requiredSlugs = ["ai-tools-app", "ai-startup-forum", "ai-llm"];
for (const slug of requiredSlugs) {
if (!categoryBySlug[slug]) {
throw new Error(`社区板块不存在: ${slug},请先在管理后台创建社区分类`);
}
}
const adminUser = await prisma.user.findFirst({
where: { role: "ADMIN" },
orderBy: { createdAt: "asc" },
});
if (!adminUser) {
throw new Error("管理员用户不存在,请先创建管理员账号");
}
console.log(`${ts()} 👤 管理员用户: ${adminUser.name || adminUser.email} (ID:${adminUser.id})`);
const topicCount = await prisma.forumTopic.count();
console.log(`${ts()} 📊 社区现有话题数: ${topicCount}`);
let created = 0;
let skipped = 0;
const errors = [];
for (const item of report.items) {
const forumSlug = CATEGORY_MAP[item.category];
if (!forumSlug) {
skipped++;
console.log(`${ts()} ⏭ 跳过(未映射分类): [${item.category}] ${item.title}`);
continue;
}
const forumCategory = categoryBySlug[forumSlug];
if (!forumCategory) {
skipped++;
console.log(`${ts()} ⏭ 跳过(板块不存在): [${item.category}] ${item.title} → ${forumSlug}`);
continue;
}
const existing = await prisma.forumTopic.findFirst({
where: { title: item.title },
});
if (existing) {
skipped++;
console.log(`${ts()} ⏭ 跳过(已存在): ${item.title}`);
continue;
}
const slug = ensureUniqueSlug(item.title);
const content = item.summary
? `${item.summary}\n\n${item.detail || ""}\n\n来源: ${item.sourceUrl || "AI日报"}`
: `${item.detail || ""}\n\n来源: ${item.sourceUrl || "AI日报"}`;
try {
const topic = await prisma.forumTopic.create({
data: {
categoryId: forumCategory.id,
userId: adminUser.id,
title: item.title,
slug,
content: content.trim(),
},
});
await prisma.forumCategory.update({
where: { id: forumCategory.id },
data: { topicCount: { increment: 1 } },
});
created++;
console.log(`${ts()} ✅ 已创建话题: [${forumCategory.name}] ${item.title} (ID:${topic.id})`);
} catch (e) {
errors.push(`${item.title}: ${e.message}`);
console.error(`${ts()} ❌ 创建话题失败: ${item.title} - ${e.message}`);
}
}
const endTime = new Date();
const duration = endTime.getTime() - startTime.getTime();
const result = {
reportId: report.id,
reportDate: report.date.toISOString().split("T")[0],
totalItems: report.items.length,
created,
skipped,
duration: `${(duration / 1000).toFixed(1)}s`,
};
console.log(`\n${ts()} 🏁 [Task7] 完成: 创建${created}个话题, 跳过${skipped}个, 耗时${result.duration}`);
await prisma.taskLog.create({
data: {
taskKey: "task7-news-to-community",
taskName: "AI日报分发到社区",
status: errors.length > 0 && created === 0 ? "failed" : "completed",
startedAt: startTime,
finishedAt: endTime,
duration,
result,
error: errors.length > 0 ? errors.slice(0, 10).join("\n") : null,
},
});
await prisma.$disconnect();
}
main().catch(async (e) => {
console.error(`${ts()} ❌ 任务七失败:`, e.message);
if (prisma) {
try {
await prisma.taskLog.create({
data: {
taskKey: "task7-news-to-community",
taskName: "AI日报分发到社区",
status: "failed",
startedAt: new Date(),
finishedAt: new Date(),
duration: 0,
error: e.message,
},
});
} catch {}
await prisma.$disconnect();
}
process.exit(1);
});