// 补齐近两个月:有学习记录(Flag=0)但缺 MiaoguoTask 记录的练习日,并对受影响用户完整重算 DayNumber/里程碑/勤勉号。 import moment from 'moment'; import Miaoguo from '../src/model/miaoguo.js'; import constantClass from '../src/util/constant/index.js'; import { stringUtils } from '../src/util/stringClass.js'; const startDate = new Date(Date.now() - 60 * 86400000).toISOString().slice(0, 10); const dayKey = (x) => moment(x.CreateTime).format('YYYY-MM-DD'); const score = (x) => (x.IsShare > 0 ? 4 : 0) + (x.MilestoneDayNumber > 0 ? 2 : 0) + (x.AphorismID > 0 ? 1 : 0); // 1) 找出缺失的练习日 const missing = await Miaoguo.RunSql([startDate], ` SELECT mc.UserID, DATE_FORMAT(r.CreateTime,'%Y-%m-%d') d, COUNT(DISTINCT r.CardID) CardCount, SUM(r.Duration) Duration FROM MiaoguoLearningRecord r INNER JOIN MiaoguoCard mc ON mc.MiaoguoCardID = r.CardID LEFT JOIN MiaoguoTask t ON t.UserID = mc.UserID AND DATE(t.CreateTime) = DATE(r.CreateTime) WHERE r.Flag=0 AND r.CreateTime >= ? AND t.ID IS NULL GROUP BY mc.UserID, DATE_FORMAT(r.CreateTime,'%Y-%m-%d') ORDER BY mc.UserID, d;`); console.log('待补齐的练习日:', missing.length, '涉及用户:', [...new Set(missing.map(r => r.UserID))].length); if (missing.length === 0) process.exit(0); const byUser = new Map(); for (const r of missing) { if (!byUser.has(r.UserID)) byUser.set(r.UserID, []); byUser.get(r.UserID).push(r); } // 2) 插入缺失行(带存在性保护) let inserted = 0; for (const [userID, days] of byUser) { for (const d of days) { const createTime = d.d + ' 00:00:00'; await Miaoguo.RunSql([userID, createTime, d.CardCount, d.Duration, userID, createTime], "insert into MiaoguoTask (UserID,CreateTime,CardTask,Duration,IsShare,Flag) " + "select ?,?,?,?,0,0 from dual where not exists " + "(select UserID,CreateTime from MiaoguoTask where UserID=? and CreateTime=?) limit 1;"); inserted++; } console.log(`U${userID}: 补插 ${days.length} 天 ->`, days.map(x => x.d).join(',')); } console.log('补插完成,共', inserted, '天。\n'); // 3) 对受影响用户完整重算(与夜间重算一致) const users = [...byUser.keys()].map(Number); const placeholders = users.map(() => '?').join(','); await Miaoguo.RunSql(users, `update MiaoguoTask set MilestoneID=0,MilestoneDayNumber=0,HardworkingNumber=0 where UserID in (${placeholders});`); const milestone = constantClass.GetMilestone(); for (const userID of users) { const tasks = await Miaoguo.RunSql([userID], "SELECT * FROM MiaoguoTask where CardTask>0 and UserID=? order by CreateTime, ID desc;"); if (!tasks || tasks.length === 0) continue; const dayBest = new Map(); for (const t of tasks) { const key = dayKey(t); const best = dayBest.get(key); if (!best) dayBest.set(key, t); else if (score(t) > score(best) || (score(t) === score(best) && t.ID > best.ID)) dayBest.set(key, t); } for (const t of tasks) { if (dayBest.get(dayKey(t)).ID !== t.ID) await Miaoguo.RunSql([t.ID], "delete from MiaoguoTask where ID=?;"); } const dayTasks = [...dayBest.values()].sort((a, b) => a.CreateTime < b.CreateTime ? -1 : a.CreateTime > b.CreateTime ? 1 : a.ID - b.ID); 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(`U${userID}: 重算完成,共 ${dayTasks.length} 天 (1..${dayTasks.length})`); } console.log('\n全部补齐完成。'); process.exit(0);