Files
zhuiguang-ai/scripts/reconcile-like-counts.mjs

164 lines
5.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 对齐 forumTopic.likeCount / forumPost.likeCount 与真实 Like 表的计数
// 用法:
// node scripts/reconcile-like-counts.mjs # 仅巡检
// node scripts/reconcile-like-counts.mjs --fix # 巡检 + 自动修正
// node scripts/reconcile-like-counts.mjs --scope=topic|post|both
// node scripts/reconcile-like-counts.mjs --bot-only # 只检查 bot 的话题/回复
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import "dotenv/config";
const base = process.env.DATABASE_URL.replace("mysql://", "mariadb://");
const sep = base.includes("?") ? "&" : "?";
const connectionString = `${base}${sep}connection_limit=5&pool_timeout=15`;
const adapter = new PrismaMariaDb(connectionString);
const prisma = new PrismaClient({ adapter });
const args = process.argv.slice(2);
const fixMode = args.includes("--fix");
const botOnly = args.includes("--bot-only");
const scopeArg = args.find((a) => a.startsWith("--scope="));
const scope = scopeArg ? scopeArg.split("=")[1] : "both";
const limitArg = args.find((a) => a.startsWith("--limit="));
const limit = limitArg ? parseInt(limitArg.split("=")[1], 10) : 5000;
function ts() {
return `[${new Date().toISOString()}]`;
}
async function reconcileTopics() {
console.log(`\n${ts()} 巡检 forumTopic.likeCount ...`);
const where = {};
if (botOnly) where.user = { isBot: true };
const all = await prisma.forumTopic.findMany({
where,
select: { id: true, likeCount: true, userId: true },
take: limit,
});
console.log(` 范围: ${all.length} 个 topic${botOnly ? " (bot 限定)" : ""}`);
// 批量统计真实点赞数(按 topicId groupBy)
const topicIds = all.map((t) => t.id);
if (topicIds.length === 0) return { drift: 0, fixed: 0 };
const realCounts = await prisma.forumTopicLike.groupBy({
by: ["topicId"],
where: { topicId: { in: topicIds } },
_count: { _all: true },
});
const realMap = new Map(realCounts.map((r) => [r.topicId, r._count._all]));
let drift = 0;
let fixed = 0;
const samples = [];
for (const t of all) {
const real = realMap.get(t.id) || 0;
if (real !== t.likeCount) {
drift++;
if (samples.length < 8) {
samples.push({ id: t.id, stored: t.likeCount, real });
}
if (fixMode) {
await prisma.forumTopic.update({
where: { id: t.id },
data: { likeCount: real },
});
fixed++;
}
}
}
console.log(` 偏差: ${drift}${fixMode ? `,已修正 ${fixed}` : ""}`);
if (samples.length > 0) {
console.log(` 示例(最多 8 条):`);
for (const s of samples) {
console.log(` topic ${s.id}: stored=${s.stored} 实际=${s.real}`);
}
}
return { drift, fixed };
}
async function reconcilePosts() {
console.log(`\n${ts()} 巡检 forumPost.likeCount ...`);
const where = {};
if (botOnly) where.user = { isBot: true };
const all = await prisma.forumPost.findMany({
where,
select: { id: true, likeCount: true, userId: true },
take: limit,
});
console.log(` 范围: ${all.length} 个 post${botOnly ? " (bot 限定)" : ""}`);
const postIds = all.map((p) => p.id);
if (postIds.length === 0) return { drift: 0, fixed: 0 };
const realCounts = await prisma.forumPostLike.groupBy({
by: ["postId"],
where: { postId: { in: postIds } },
_count: { _all: true },
});
const realMap = new Map(realCounts.map((r) => [r.postId, r._count._all]));
let drift = 0;
let fixed = 0;
const samples = [];
for (const p of all) {
const real = realMap.get(p.id) || 0;
if (real !== p.likeCount) {
drift++;
if (samples.length < 8) {
samples.push({ id: p.id, stored: p.likeCount, real });
}
if (fixMode) {
await prisma.forumPost.update({
where: { id: p.id },
data: { likeCount: real },
});
fixed++;
}
}
}
console.log(` 偏差: ${drift}${fixMode ? `,已修正 ${fixed}` : ""}`);
if (samples.length > 0) {
console.log(` 示例(最多 8 条):`);
for (const s of samples) {
console.log(` post ${s.id}: stored=${s.stored} 实际=${s.real}`);
}
}
return { drift, fixed };
}
async function main() {
console.log(`========== 点赞数对齐巡检 ==========`);
console.log(`模式: ${fixMode ? "FIX(自动修正)" : "CHECK(仅巡检)"}`);
console.log(`范围: ${scope}${botOnly ? " (仅 bot)" : ""}`);
console.log(`扫描上限: ${limit}`);
const summary = { topic: { drift: 0, fixed: 0 }, post: { drift: 0, fixed: 0 } };
if (scope === "topic" || scope === "both") {
summary.topic = await reconcileTopics();
}
if (scope === "post" || scope === "both") {
summary.post = await reconcilePosts();
}
console.log(`\n========== 总结 ==========`);
console.log(`话题偏差: ${summary.topic.drift}${fixMode ? `(已修正 ${summary.topic.fixed})` : ""}`);
console.log(`回复偏差: ${summary.post.drift}${fixMode ? `(已修正 ${summary.post.fixed})` : ""}`);
const totalDrift = summary.topic.drift + summary.post.drift;
if (totalDrift === 0) {
console.log(`\n✅ 全部对齐,无需修正`);
} else if (fixMode) {
console.log(`\n🔧 已完成修正`);
} else {
console.log(`\n⚠️ 发现偏差,加 --fix 自动修正`);
}
await prisma.$disconnect();
}
main().catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});