fix_42792_and_scan.mjs 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. // 一次性脚本:修复 42792 的 MiaoguoTask 重复记录/DayNumber,并只读扫描最近一个月相似问题用户。
  2. // 仅对 42792 做写操作;其余用户只做只读统计。
  3. import Miaoguo from '../src/model/miaoguo.js';
  4. import constantClass from '../src/util/constant/index.js';
  5. import { stringUtils } from '../src/util/stringClass.js';
  6. async function fixUser(userID) {
  7. const tasks = await Miaoguo.RunSql([userID],
  8. "SELECT * FROM MiaoguoTask where CardTask>0 and UserID=? order by CreateTime, ID desc;");
  9. if (!tasks || tasks.length === 0) {
  10. console.log(`User ${userID}: 无记录`);
  11. return;
  12. }
  13. // 每天只保留“最完整”的一条:优先 IsShare>0 / 有里程碑 / 有格言,其次最新 ID
  14. const dayBest = new Map();
  15. const score = (x) => (x.IsShare > 0 ? 4 : 0) + (x.MilestoneDayNumber > 0 ? 2 : 0) + (x.AphorismID > 0 ? 1 : 0);
  16. for (const t of tasks) {
  17. const best = dayBest.get(t.CreateTime);
  18. if (!best) dayBest.set(t.CreateTime, t);
  19. else if (score(t) > score(best) || (score(t) === score(best) && t.ID > best.ID))
  20. dayBest.set(t.CreateTime, t);
  21. }
  22. let deleted = 0;
  23. for (const t of tasks) {
  24. if (dayBest.get(t.CreateTime).ID !== t.ID) {
  25. await Miaoguo.RunSql([t.ID], "delete from MiaoguoTask where ID=?;");
  26. deleted++;
  27. }
  28. }
  29. const dayTasks = [...dayBest.values()].sort((a, b) =>
  30. a.CreateTime < b.CreateTime ? -1 : a.CreateTime > b.CreateTime ? 1 : a.ID - b.ID);
  31. const milestone = constantClass.GetMilestone();
  32. let milestoneIDs = [];
  33. for (let i = 0; i < 40; i++) milestoneIDs.push(i + 1);
  34. milestoneIDs = stringUtils.RandomArray(milestoneIDs);
  35. let milestoneIndex = 0, hardworkingIndex = 0;
  36. for (let i = 0; i < dayTasks.length; i++) {
  37. const t = dayTasks[i];
  38. const update = { ID: t.ID, DayNumber: i + 1 };
  39. const milestoneItem = milestone.find(item => item.DayNumber == i + 1);
  40. if (milestoneItem) {
  41. update.MilestoneDayNumber = milestoneItem.DayNumber;
  42. update.MilestoneID = milestoneIDs[milestoneIndex++];
  43. }
  44. if (t.IsShare > 0 || update.MilestoneDayNumber > 0 || t.AphorismID > 0)
  45. update.HardworkingNumber = ++hardworkingIndex;
  46. await Miaoguo.UpdateMiaoguoTask(update);
  47. }
  48. console.log(`User ${userID}: 删除重复行 ${deleted} 条,重编号 ${dayTasks.length} 天(DayNumber 1..${dayTasks.length})`);
  49. }
  50. async function scanRecent() {
  51. // 信号1:最近一个月存在“同一天多条(MiaoguoTask)记录”的用户(根因)
  52. const dupDays = await Miaoguo.RunSql([], `
  53. SELECT UserID, DATE(CreateTime) as d, COUNT(*) as cnt
  54. FROM MiaoguoTask
  55. WHERE CardTask>0 AND CreateTime >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
  56. GROUP BY UserID, DATE(CreateTime)
  57. HAVING cnt > 1
  58. ORDER BY UserID, d;`);
  59. const byUser = {};
  60. for (const r of dupDays) byUser[r.UserID] = (byUser[r.UserID] || 0) + 1;
  61. // 信号2:最近一个月 DayNumber 最大值 > 实际不同天数(说明历史上按行编号被撑大,存在断号风险)
  62. const inflated = await Miaoguo.RunSql([], `
  63. SELECT UserID,
  64. COUNT(DISTINCT DATE(CreateTime)) as distinctDays,
  65. MAX(DayNumber) as maxDayNumber
  66. FROM MiaoguoTask
  67. WHERE CardTask>0 AND CreateTime >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
  68. GROUP BY UserID
  69. HAVING maxDayNumber > distinctDays;`);
  70. const inflatedUsers = inflated.map(r => r.UserID);
  71. console.log('\n===== 扫描结果(最近一个月)=====');
  72. console.log('信号1 - 有“同一天多条记录”的用户数:', Object.keys(byUser).length);
  73. console.log('信号1 - 明细(用户ID: 重复天数):', byUser);
  74. console.log('\n信号2 - DayNumber>实际天数(历史膨胀)的用户数:', inflatedUsers.length);
  75. console.log('信号2 - 用户列表:', inflatedUsers);
  76. const allAffected = new Set([...Object.keys(byUser), ...inflatedUsers]);
  77. console.log('\n合计受影响用户数(任一信号):', allAffected.size);
  78. console.log('合计用户ID:', [...allAffected].map(Number).sort((a, b) => a - b));
  79. }
  80. await fixUser(42792);
  81. await scanRecent();
  82. process.exit(0);