2026-05-25: 等级体系升级(20级)+论坛二级分类+会员空间
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user