import { PrismaClient } from "@prisma/client"; import { PrismaMariaDb } from "@prisma/adapter-mariadb"; import { Octokit } from "octokit"; import "dotenv/config"; 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 }); const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); function extractOwnerRepo(sourceUrl) { const match = sourceUrl.match(/github\.com\/([^/]+)\/([^/]+)/); if (!match) return null; return { owner: match[1], repo: match[2].replace(/\.git$/, "") }; } 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(`📈 [Task5] ${today} 更新GitHub星值...`); const skills = await prisma.skill.findMany({ where: { sourceUrl: { contains: "github.com" } }, select: { id: true, name: true, sourceUrl: true, githubStars: true, githubStarsHistory: true }, }); console.log(` 共 ${skills.length} 个GitHub技能需要更新`); let updated = 0; let failed = 0; let skipped = 0; const errors = []; for (const skill of skills) { const parsed = extractOwnerRepo(skill.sourceUrl); if (!parsed) { skipped++; console.log(` ⏭️ ${skill.name} - 无法解析GitHub URL`); continue; } try { const { data } = await octokit.rest.repos.get({ owner: parsed.owner, repo: parsed.repo }); const currentStars = data.stargazers_count; const history = Array.isArray(skill.githubStarsHistory) ? [...skill.githubStarsHistory] : []; const prevEntry = history.length > 0 ? history[history.length - 1] : null; const prevStars = prevEntry ? prevEntry.stars : currentStars; history.push({ date: today, stars: currentStars }); if (history.length > 90) history.splice(0, history.length - 90); const dayChange = currentStars - prevStars; const weekAgo = history.find((h) => { const d = new Date(h.date); const diff = (cnTime.getTime() - d.getTime()) / (24 * 60 * 60 * 1000); return diff >= 6 && diff <= 8; }); const weekChange = weekAgo ? currentStars - weekAgo.stars : dayChange * 7; const monthAgo = history.find((h) => { const d = new Date(h.date); const diff = (cnTime.getTime() - d.getTime()) / (24 * 60 * 60 * 1000); return diff >= 28 && diff <= 32; }); const monthChange = monthAgo ? currentStars - monthAgo.stars : weekChange * 4; await prisma.skill.update({ where: { id: skill.id }, data: { githubStars: currentStars, starsChangeDay: dayChange, starsChangeWeek: weekChange, starsChangeMonth: monthChange, githubStarsHistory: history, }, }); updated++; const arrow = dayChange >= 0 ? "↑" : "↓"; console.log(` ✅ ${skill.name}: ${currentStars}⭐ (${arrow}${Math.abs(dayChange)}/日)`); } catch (e) { failed++; errors.push(`${skill.name}: ${e.message}`); console.log(` ❌ ${skill.name}: ${e.message}`); } if (updated % 5 === 0) { await new Promise((r) => setTimeout(r, 1000)); } } const endTime = new Date(); const duration = endTime.getTime() - startTime.getTime(); const result = { total: skills.length, updated, failed, skipped, duration: `${(duration / 1000).toFixed(1)}s`, }; console.log(`\n🏁 [Task5] 完成: 更新${updated} / 失败${failed}`); await prisma.taskLog.create({ data: { taskKey: "task5-update-stars", taskName: "更新技能星值", status: failed > skills.length * 0.5 ? "failed" : "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: "task5-update-stars", taskName: "更新技能星值", status: "failed", startedAt: new Date(), finishedAt: new Date(), duration: 0, error: e.message, }, }); } catch {} await prisma.$disconnect(); } process.exit(1); });