Bladeren bron

修改daynumber的bug

chengjie 1 maand geleden
bovenliggende
commit
5ad974899c

+ 94 - 0
scripts/fix_42792_and_scan.mjs

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

+ 64 - 0
scripts/fix_44_users.mjs

@@ -0,0 +1,64 @@
1
+// 批量修复最近一个月存在 DayNumber 膨胀/断号的活跃用户(与 42792 相同的去重+按天连续重编号逻辑)。
2
+import Miaoguo from '../src/model/miaoguo.js';
3
+import constantClass from '../src/util/constant/index.js';
4
+import { stringUtils } from '../src/util/stringClass.js';
5
+
6
+const USER_IDS = [
7
+    1, 2, 321, 658, 699, 1586, 1631, 3232, 14845, 14916, 15220, 15278,
8
+    15360, 15362, 15387, 16297, 16791, 17315, 22192, 23093, 24084, 24142,
9
+    24149, 25310, 26887, 27170, 29864, 30393, 30894, 31436, 31917, 33746,
10
+    34777, 39733, 39769, 40981, 41301, 41496, 42666, 42668, 42702, 42792,
11
+    42860, 43485
12
+];
13
+
14
+async function fixUser(userID) {
15
+    const tasks = await Miaoguo.RunSql([userID],
16
+        "SELECT * FROM MiaoguoTask where CardTask>0 and UserID=? order by CreateTime, ID desc;");
17
+    if (!tasks || tasks.length === 0) return `User ${userID}: 无记录`;
18
+
19
+    const dayBest = new Map();
20
+    const score = (x) => (x.IsShare > 0 ? 4 : 0) + (x.MilestoneDayNumber > 0 ? 2 : 0) + (x.AphorismID > 0 ? 1 : 0);
21
+    for (const t of tasks) {
22
+        const best = dayBest.get(t.CreateTime);
23
+        if (!best) dayBest.set(t.CreateTime, t);
24
+        else if (score(t) > score(best) || (score(t) === score(best) && t.ID > best.ID))
25
+            dayBest.set(t.CreateTime, t);
26
+    }
27
+
28
+    let deleted = 0;
29
+    for (const t of tasks) {
30
+        if (dayBest.get(t.CreateTime).ID !== t.ID) {
31
+            await Miaoguo.RunSql([t.ID], "delete from MiaoguoTask where ID=?;");
32
+            deleted++;
33
+        }
34
+    }
35
+
36
+    const dayTasks = [...dayBest.values()].sort((a, b) =>
37
+        a.CreateTime < b.CreateTime ? -1 : a.CreateTime > b.CreateTime ? 1 : a.ID - b.ID);
38
+
39
+    const milestone = constantClass.GetMilestone();
40
+    let milestoneIDs = [];
41
+    for (let i = 0; i < 40; i++) milestoneIDs.push(i + 1);
42
+    milestoneIDs = stringUtils.RandomArray(milestoneIDs);
43
+    let milestoneIndex = 0, hardworkingIndex = 0;
44
+    for (let i = 0; i < dayTasks.length; i++) {
45
+        const t = dayTasks[i];
46
+        const update = { ID: t.ID, DayNumber: i + 1 };
47
+        const milestoneItem = milestone.find(item => item.DayNumber == i + 1);
48
+        if (milestoneItem) {
49
+            update.MilestoneDayNumber = milestoneItem.DayNumber;
50
+            update.MilestoneID = milestoneIDs[milestoneIndex++];
51
+        }
52
+        if (t.IsShare > 0 || update.MilestoneDayNumber > 0 || t.AphorismID > 0)
53
+            update.HardworkingNumber = ++hardworkingIndex;
54
+        await Miaoguo.UpdateMiaoguoTask(update);
55
+    }
56
+    return `User ${userID}: 删除 ${deleted} 条重复,重编号 ${dayTasks.length} 天 (DayNumber 1..${dayTasks.length})`;
57
+}
58
+
59
+for (const id of USER_IDS) {
60
+    const msg = await fixUser(id);
61
+    console.log(msg);
62
+}
63
+console.log(`\n完成,共处理 ${USER_IDS.length} 个用户。`);
64
+process.exit(0);

+ 13 - 0
scripts/verify_42792.mjs

@@ -0,0 +1,13 @@
1
+import Miaoguo from '../src/model/miaoguo.js';
2
+
3
+const r = await Miaoguo.RunSql([42792],
4
+    'SELECT ID,CreateTime,DayNumber,IsShare,HardworkingNumber,MilestoneDayNumber FROM MiaoguoTask where UserID=? order by CreateTime desc limit 8;');
5
+console.log('42792 末 8 条:');
6
+for (const x of r)
7
+    console.log([x.ID, x.CreateTime, 'Day=' + x.DayNumber, 'Share=' + x.IsShare, 'HW=' + x.HardworkingNumber, 'Mil=' + x.MilestoneDayNumber].join(' | '));
8
+
9
+const g = await Miaoguo.RunSql([42792],
10
+    'SELECT COUNT(*) as rows, COUNT(DISTINCT DATE(CreateTime)) as days, MAX(DayNumber) as maxDay, MIN(DayNumber) as minDay FROM MiaoguoTask where CardTask>0 and UserID=?;');
11
+console.log('\n汇总:', JSON.stringify(g[0]));
12
+console.log(g[0].maxDay === g[0].days && g[0].minDay === 1 ? '=> DayNumber 连续且无膨胀 ✅' : '=> 仍有异常 ❌');
13
+process.exit(0);

+ 26 - 0
scripts/verify_44_users.mjs

@@ -0,0 +1,26 @@
1
+import Miaoguo from '../src/model/miaoguo.js';
2
+
3
+const USER_IDS = [
4
+    1, 2, 321, 658, 699, 1586, 1631, 3232, 14845, 14916, 15220, 15278,
5
+    15360, 15362, 15387, 16297, 16791, 17315, 22192, 23093, 24084, 24142,
6
+    24149, 25310, 26887, 27170, 29864, 30393, 30894, 31436, 31917, 33746,
7
+    34777, 39733, 39769, 40981, 41301, 41496, 42666, 42668, 42702, 42792,
8
+    42860, 43485
9
+];
10
+
11
+const placeholders = USER_IDS.map(() => '?').join(',');
12
+const rows = await Miaoguo.RunSql(USER_IDS, `
13
+    SELECT UserID,
14
+           COUNT(DISTINCT DATE(CreateTime)) as days,
15
+           MAX(DayNumber) as maxDay,
16
+           MIN(DayNumber) as minDay
17
+    FROM MiaoguoTask
18
+    WHERE CardTask>0 AND UserID IN (${placeholders})
19
+    GROUP BY UserID
20
+    HAVING maxDay > days OR minDay <> 1;`);
21
+
22
+if (rows.length === 0)
23
+    console.log('✅ 44 个用户全部 DayNumber 连续且无膨胀,无残留异常。');
24
+else
25
+    console.log('❌ 仍有异常用户:', rows.map(r => `${r.UserID}(max=${r.maxDay},days=${r.days})`).join(', '));
26
+process.exit(0);

+ 28 - 7
src/api/miaoguo/miaoguoController.js

@@ -3598,24 +3598,45 @@ export async function updateMiaoguoTaskData(ctx) {
3598 3598
         );
3599 3599
         const milestone = constantClass.GetMilestone();
3600 3600
         for (const user of taskUsers) {
3601
-            const tasks = await miaoguo.RunSql([user.UserID], "SELECT * FROM MiaoguoTask where CardTask>0 and UserID=? order by CreateTime;");
3601
+            const tasks = await miaoguo.RunSql([user.UserID], "SELECT * FROM MiaoguoTask where CardTask>0 and UserID=? order by CreateTime, ID desc;");
3602 3602
             if (!tasks || tasks.length == 0) continue;
3603
+
3604
+            // 同一天可能有多条记录(历史并发写入导致)。每天只保留“最完整”的一条,
3605
+            // 优先 已分享(IsShare>0) / 有里程碑 / 有格言,其次保留最新 ID;其余重复行删除。
3606
+            // 这样 DayNumber 按“天”连续递增,且不会因删除重复行而丢失分享/里程碑/勤勉信息。
3607
+            const dayBest = new Map();
3608
+            for (const t of tasks) {
3609
+                const best = dayBest.get(t.CreateTime);
3610
+                if (!best) {
3611
+                    dayBest.set(t.CreateTime, t);
3612
+                } else {
3613
+                    const score = (x) => (x.IsShare > 0 ? 4 : 0) + (x.MilestoneDayNumber > 0 ? 2 : 0) + (x.AphorismID > 0 ? 1 : 0);
3614
+                    if (score(t) > score(best) || (score(t) === score(best) && t.ID > best.ID))
3615
+                        dayBest.set(t.CreateTime, t);
3616
+                }
3617
+            }
3618
+            // 删除同一天的多余记录(仅保留 dayBest 中的那一条)
3619
+            for (const t of tasks) {
3620
+                if (dayBest.get(t.CreateTime).ID !== t.ID)
3621
+                    await miaoguo.RunSql([t.ID], "delete from MiaoguoTask where ID=?;");
3622
+            }
3623
+            const dayTasks = [...dayBest.values()].sort((a, b) =>
3624
+                a.CreateTime < b.CreateTime ? -1 : a.CreateTime > b.CreateTime ? 1 : a.ID - b.ID);
3625
+
3603 3626
             let milestoneIDs = [];
3604 3627
             for (let i = 0; i < 40; i++) milestoneIDs.push(i + 1);
3605 3628
             milestoneIDs = stringUtils.RandomArray(milestoneIDs);
3606 3629
             let milestoneIndex = 0;
3607 3630
             let hardworkingIndex = 0;
3608
-            for (let i = 0; i < tasks.length; i++) {
3609
-                const update = {
3610
-                    ID: tasks[i].ID,
3611
-                    DayNumber: i + 1,
3612
-                };
3631
+            for (let i = 0; i < dayTasks.length; i++) {
3632
+                const t = dayTasks[i];
3633
+                const update = { ID: t.ID, DayNumber: i + 1 };
3613 3634
                 const milestoneItem = milestone.find(item => item.DayNumber == i + 1);
3614 3635
                 if (milestoneItem) {
3615 3636
                     update.MilestoneDayNumber = milestoneItem.DayNumber;
3616 3637
                     update.MilestoneID = milestoneIDs[milestoneIndex++];
3617 3638
                 }
3618
-                if (tasks[i].IsShare > 0 || update.MilestoneDayNumber > 0 || tasks[i].AphorismID > 0)
3639
+                if (t.IsShare > 0 || update.MilestoneDayNumber > 0 || t.AphorismID > 0)
3619 3640
                     update.HardworkingNumber = ++hardworkingIndex;
3620 3641
                 await miaoguo.UpdateMiaoguoTask(update);
3621 3642
             }

+ 3 - 1
src/build.js

@@ -241,7 +241,9 @@ function UpdateMiaoguoTask() {
241 241
                     var item = list[index];
242 242
                     if (item && item.UserID) {
243 243
                         //console.log(item.UserID);
244
-                        var sql2 = "delete from MiaoguoTask where UserID=" + item.UserID + " and DATE_FORMAT(CreateTime,'%Y-%m-%d')='" + item.CreateTime + "' order by ID desc limit 1;";
244
+                        // 保留“最完整”的一条(优先 已分享 / 有里程碑 / 有格言,其次最新 ID),删除其余重复行,
245
+                        // 与 updateMiaoguoTaskData 重算时的去重口径保持一致,避免误删分享/里程碑记录
246
+                        var sql2 = "delete from MiaoguoTask where UserID=" + item.UserID + " and DATE_FORMAT(CreateTime,'%Y-%m-%d')='" + item.CreateTime + "' and ID not in (select ID from (select ID from MiaoguoTask where UserID=" + item.UserID + " and DATE_FORMAT(CreateTime,'%Y-%m-%d')='" + item.CreateTime + "' order by (IsShare>0) desc,(MilestoneDayNumber>0) desc,(AphorismID>0) desc,ID desc limit 1) as t);";
245 247
                         miaoguo.RunSql({}, sql2);
246 248
                     }
247 249