// 一次性脚本:修复 42792 的 MiaoguoTask 重复记录/DayNumber,并只读扫描最近一个月相似问题用户。 // 仅对 42792 做写操作;其余用户只做只读统计。 import Miaoguo from '../src/model/miaoguo.js'; import constantClass from '../src/util/constant/index.js'; import { stringUtils } from '../src/util/stringClass.js'; async function fixUser(userID) { const tasks = await Miaoguo.RunSql([userID], "SELECT * FROM MiaoguoTask where CardTask>0 and UserID=? order by CreateTime, ID desc;"); if (!tasks || tasks.length === 0) { console.log(`User ${userID}: 无记录`); return; } // 每天只保留“最完整”的一条:优先 IsShare>0 / 有里程碑 / 有格言,其次最新 ID const dayBest = new Map(); const score = (x) => (x.IsShare > 0 ? 4 : 0) + (x.MilestoneDayNumber > 0 ? 2 : 0) + (x.AphorismID > 0 ? 1 : 0); for (const t of tasks) { const best = dayBest.get(t.CreateTime); if (!best) dayBest.set(t.CreateTime, t); else if (score(t) > score(best) || (score(t) === score(best) && t.ID > best.ID)) dayBest.set(t.CreateTime, t); } let deleted = 0; for (const t of tasks) { if (dayBest.get(t.CreateTime).ID !== t.ID) { await Miaoguo.RunSql([t.ID], "delete from MiaoguoTask where ID=?;"); deleted++; } } const dayTasks = [...dayBest.values()].sort((a, b) => a.CreateTime < b.CreateTime ? -1 : a.CreateTime > b.CreateTime ? 1 : a.ID - b.ID); const milestone = constantClass.GetMilestone(); let milestoneIDs = []; for (let i = 0; i < 40; i++) milestoneIDs.push(i + 1); milestoneIDs = stringUtils.RandomArray(milestoneIDs); let milestoneIndex = 0, hardworkingIndex = 0; for (let i = 0; i < dayTasks.length; i++) { const t = dayTasks[i]; const update = { ID: t.ID, DayNumber: i + 1 }; const milestoneItem = milestone.find(item => item.DayNumber == i + 1); if (milestoneItem) { update.MilestoneDayNumber = milestoneItem.DayNumber; update.MilestoneID = milestoneIDs[milestoneIndex++]; } if (t.IsShare > 0 || update.MilestoneDayNumber > 0 || t.AphorismID > 0) update.HardworkingNumber = ++hardworkingIndex; await Miaoguo.UpdateMiaoguoTask(update); } console.log(`User ${userID}: 删除重复行 ${deleted} 条,重编号 ${dayTasks.length} 天(DayNumber 1..${dayTasks.length})`); } async function scanRecent() { // 信号1:最近一个月存在“同一天多条(MiaoguoTask)记录”的用户(根因) const dupDays = await Miaoguo.RunSql([], ` SELECT UserID, DATE(CreateTime) as d, COUNT(*) as cnt FROM MiaoguoTask WHERE CardTask>0 AND CreateTime >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY UserID, DATE(CreateTime) HAVING cnt > 1 ORDER BY UserID, d;`); const byUser = {}; for (const r of dupDays) byUser[r.UserID] = (byUser[r.UserID] || 0) + 1; // 信号2:最近一个月 DayNumber 最大值 > 实际不同天数(说明历史上按行编号被撑大,存在断号风险) const inflated = await Miaoguo.RunSql([], ` SELECT UserID, COUNT(DISTINCT DATE(CreateTime)) as distinctDays, MAX(DayNumber) as maxDayNumber FROM MiaoguoTask WHERE CardTask>0 AND CreateTime >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY UserID HAVING maxDayNumber > distinctDays;`); const inflatedUsers = inflated.map(r => r.UserID); console.log('\n===== 扫描结果(最近一个月)====='); console.log('信号1 - 有“同一天多条记录”的用户数:', Object.keys(byUser).length); console.log('信号1 - 明细(用户ID: 重复天数):', byUser); console.log('\n信号2 - DayNumber>实际天数(历史膨胀)的用户数:', inflatedUsers.length); console.log('信号2 - 用户列表:', inflatedUsers); const allAffected = new Set([...Object.keys(byUser), ...inflatedUsers]); console.log('\n合计受影响用户数(任一信号):', allAffected.size); console.log('合计用户ID:', [...allAffected].map(Number).sort((a, b) => a - b)); } await fixUser(42792); await scanRecent(); process.exit(0);