全项目扫描修复: Docker数据卷修复+安全requireAdmin+12页SEO+API白名单+脚本超时+常量提取+假数据删除
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
// 对齐 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);
|
||||
});
|
||||
Reference in New Issue
Block a user