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); });