|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+// 补齐近两个月:有学习记录(Flag=0)但缺 MiaoguoTask 记录的练习日,并对受影响用户完整重算 DayNumber/里程碑/勤勉号。
|
|
|
2
|
+import moment from 'moment';
|
|
|
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
|
+const startDate = new Date(Date.now() - 60 * 86400000).toISOString().slice(0, 10);
|
|
|
8
|
+const dayKey = (x) => moment(x.CreateTime).format('YYYY-MM-DD');
|
|
|
9
|
+const score = (x) => (x.IsShare > 0 ? 4 : 0) + (x.MilestoneDayNumber > 0 ? 2 : 0) + (x.AphorismID > 0 ? 1 : 0);
|
|
|
10
|
+
|
|
|
11
|
+// 1) 找出缺失的练习日
|
|
|
12
|
+const missing = await Miaoguo.RunSql([startDate], `
|
|
|
13
|
+ SELECT mc.UserID, DATE_FORMAT(r.CreateTime,'%Y-%m-%d') d,
|
|
|
14
|
+ COUNT(DISTINCT r.CardID) CardCount, SUM(r.Duration) Duration
|
|
|
15
|
+ FROM MiaoguoLearningRecord r
|
|
|
16
|
+ INNER JOIN MiaoguoCard mc ON mc.MiaoguoCardID = r.CardID
|
|
|
17
|
+ LEFT JOIN MiaoguoTask t ON t.UserID = mc.UserID AND DATE(t.CreateTime) = DATE(r.CreateTime)
|
|
|
18
|
+ WHERE r.Flag=0 AND r.CreateTime >= ? AND t.ID IS NULL
|
|
|
19
|
+ GROUP BY mc.UserID, DATE_FORMAT(r.CreateTime,'%Y-%m-%d')
|
|
|
20
|
+ ORDER BY mc.UserID, d;`);
|
|
|
21
|
+
|
|
|
22
|
+console.log('待补齐的练习日:', missing.length, '涉及用户:', [...new Set(missing.map(r => r.UserID))].length);
|
|
|
23
|
+if (missing.length === 0) process.exit(0);
|
|
|
24
|
+
|
|
|
25
|
+const byUser = new Map();
|
|
|
26
|
+for (const r of missing) {
|
|
|
27
|
+ if (!byUser.has(r.UserID)) byUser.set(r.UserID, []);
|
|
|
28
|
+ byUser.get(r.UserID).push(r);
|
|
|
29
|
+}
|
|
|
30
|
+
|
|
|
31
|
+// 2) 插入缺失行(带存在性保护)
|
|
|
32
|
+let inserted = 0;
|
|
|
33
|
+for (const [userID, days] of byUser) {
|
|
|
34
|
+ for (const d of days) {
|
|
|
35
|
+ const createTime = d.d + ' 00:00:00';
|
|
|
36
|
+ await Miaoguo.RunSql([userID, createTime, d.CardCount, d.Duration, userID, createTime],
|
|
|
37
|
+ "insert into MiaoguoTask (UserID,CreateTime,CardTask,Duration,IsShare,Flag) " +
|
|
|
38
|
+ "select ?,?,?,?,0,0 from dual where not exists " +
|
|
|
39
|
+ "(select UserID,CreateTime from MiaoguoTask where UserID=? and CreateTime=?) limit 1;");
|
|
|
40
|
+ inserted++;
|
|
|
41
|
+ }
|
|
|
42
|
+ console.log(`U${userID}: 补插 ${days.length} 天 ->`, days.map(x => x.d).join(','));
|
|
|
43
|
+}
|
|
|
44
|
+console.log('补插完成,共', inserted, '天。\n');
|
|
|
45
|
+
|
|
|
46
|
+// 3) 对受影响用户完整重算(与夜间重算一致)
|
|
|
47
|
+const users = [...byUser.keys()].map(Number);
|
|
|
48
|
+const placeholders = users.map(() => '?').join(',');
|
|
|
49
|
+await Miaoguo.RunSql(users, `update MiaoguoTask set MilestoneID=0,MilestoneDayNumber=0,HardworkingNumber=0 where UserID in (${placeholders});`);
|
|
|
50
|
+
|
|
|
51
|
+const milestone = constantClass.GetMilestone();
|
|
|
52
|
+for (const userID of users) {
|
|
|
53
|
+ const tasks = await Miaoguo.RunSql([userID],
|
|
|
54
|
+ "SELECT * FROM MiaoguoTask where CardTask>0 and UserID=? order by CreateTime, ID desc;");
|
|
|
55
|
+ if (!tasks || tasks.length === 0) continue;
|
|
|
56
|
+
|
|
|
57
|
+ const dayBest = new Map();
|
|
|
58
|
+ for (const t of tasks) {
|
|
|
59
|
+ const key = dayKey(t);
|
|
|
60
|
+ const best = dayBest.get(key);
|
|
|
61
|
+ if (!best) dayBest.set(key, t);
|
|
|
62
|
+ else if (score(t) > score(best) || (score(t) === score(best) && t.ID > best.ID))
|
|
|
63
|
+ dayBest.set(key, t);
|
|
|
64
|
+ }
|
|
|
65
|
+ for (const t of tasks) {
|
|
|
66
|
+ if (dayBest.get(dayKey(t)).ID !== t.ID)
|
|
|
67
|
+ await Miaoguo.RunSql([t.ID], "delete from MiaoguoTask where ID=?;");
|
|
|
68
|
+ }
|
|
|
69
|
+ const dayTasks = [...dayBest.values()].sort((a, b) =>
|
|
|
70
|
+ a.CreateTime < b.CreateTime ? -1 : a.CreateTime > b.CreateTime ? 1 : a.ID - b.ID);
|
|
|
71
|
+
|
|
|
72
|
+ let milestoneIDs = [];
|
|
|
73
|
+ for (let i = 0; i < 40; i++) milestoneIDs.push(i + 1);
|
|
|
74
|
+ milestoneIDs = stringUtils.RandomArray(milestoneIDs);
|
|
|
75
|
+ let milestoneIndex = 0, hardworkingIndex = 0;
|
|
|
76
|
+ for (let i = 0; i < dayTasks.length; i++) {
|
|
|
77
|
+ const t = dayTasks[i];
|
|
|
78
|
+ const update = { ID: t.ID, DayNumber: i + 1 };
|
|
|
79
|
+ const milestoneItem = milestone.find(item => item.DayNumber == i + 1);
|
|
|
80
|
+ if (milestoneItem) {
|
|
|
81
|
+ update.MilestoneDayNumber = milestoneItem.DayNumber;
|
|
|
82
|
+ update.MilestoneID = milestoneIDs[milestoneIndex++];
|
|
|
83
|
+ }
|
|
|
84
|
+ if (t.IsShare > 0 || update.MilestoneDayNumber > 0 || t.AphorismID > 0)
|
|
|
85
|
+ update.HardworkingNumber = ++hardworkingIndex;
|
|
|
86
|
+ await Miaoguo.UpdateMiaoguoTask(update);
|
|
|
87
|
+ }
|
|
|
88
|
+ console.log(`U${userID}: 重算完成,共 ${dayTasks.length} 天 (1..${dayTasks.length})`);
|
|
|
89
|
+}
|
|
|
90
|
+
|
|
|
91
|
+console.log('\n全部补齐完成。');
|
|
|
92
|
+process.exit(0);
|