Files
d1kt/scripts/migrate-complete.ts

231 lines
7.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import mongoose from 'mongoose';
import dotenv from 'dotenv';
import path from 'path';
// 加载环境变量
dotenv.config({ path: path.resolve(__dirname, '../server/.env') });
// 定义模型类型接口
interface IModeStats {
streak?: number;
totalCorrect?: number;
totalWrong?: number;
mastered?: boolean;
inWrongBook?: boolean;
lastTestedAt?: Date;
lastMasteredAt?: Date;
}
interface IWordRecord {
_id: mongoose.Types.ObjectId;
user: mongoose.Types.ObjectId;
word: mongoose.Types.ObjectId;
multipleChoice?: IModeStats;
audioToEnglish?: IModeStats;
chineseToEnglish?: IModeStats;
isFullyMastered?: boolean;
lastFullyMasteredAt?: Date;
createdAt?: Date;
[key: string]: any;
}
// 定义模型结构
const ModeStatsSchema = new mongoose.Schema({
streak: { type: Number, default: 0 },
totalCorrect: { type: Number, default: 0 },
totalWrong: { type: Number, default: 0 },
mastered: { type: Boolean, default: false },
inWrongBook: { type: Boolean, default: false },
lastTestedAt: Date,
lastMasteredAt: Date
}, { _id: false });
const WordRecordSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
word: { type: mongoose.Schema.Types.ObjectId, ref: 'Word', required: true },
multipleChoice: { type: ModeStatsSchema, default: () => ({}) },
audioToEnglish: { type: ModeStatsSchema, default: () => ({}) },
chineseToEnglish: { type: ModeStatsSchema, default: () => ({}) },
isFullyMastered: { type: Boolean, default: false },
lastFullyMasteredAt: Date,
createdAt: { type: Date, default: Date.now }
}, { strict: false });
// 注册模型
const WordRecord = mongoose.model<IWordRecord>('WordRecord', WordRecordSchema);
// 获取数据库URI
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/typeskill';
console.log('使用数据库连接:', MONGODB_URI);
// 连接数据库
mongoose.connect(MONGODB_URI)
.then(() => {
console.log('数据库连接成功,开始检查数据...');
return migrateWordRecords();
})
.then(() => {
console.log('处理完成!');
mongoose.connection.close();
process.exit(0);
})
.catch(err => {
console.error('处理失败:', err);
mongoose.connection.close();
process.exit(1);
});
async function migrateWordRecords() {
try {
// 获取所有记录
const records = await WordRecord.find({}).lean();
console.log(`共找到 ${records.length} 条记录`);
// 准备批量更新
const updateOperations = [];
let needUpdateCount = 0;
// 检查每个记录
for (const record of records) {
const modes = ['chineseToEnglish', 'audioToEnglish', 'multipleChoice'];
// 检查是否所有模式都存在并计算掌握状态
let allModesExist = true;
let allMastered = true;
for (const mode of modes) {
if (!record[mode] || typeof record[mode] !== 'object') {
allModesExist = false;
allMastered = false;
break;
}
if (record[mode].mastered !== true) {
allMastered = false;
}
}
// 计算是否完全掌握
const shouldBeFullyMastered = allModesExist && allMastered;
// 计算最新掌握时间和最新测试时间
let latestMasteredDate = null;
let latestTestedDate = null;
if (shouldBeFullyMastered) {
for (const mode of modes) {
const modeMasteredDate = record[mode]?.lastMasteredAt;
if (modeMasteredDate && (!latestMasteredDate || new Date(modeMasteredDate) > new Date(latestMasteredDate))) {
latestMasteredDate = modeMasteredDate;
}
const modeTestedDate = record[mode]?.lastTestedAt;
if (modeTestedDate && (!latestTestedDate || new Date(modeTestedDate) > new Date(latestTestedDate))) {
latestTestedDate = modeTestedDate;
}
}
}
// 取最大值
let lastFullyMasteredAt = null;
if (shouldBeFullyMastered) {
if (latestMasteredDate && latestTestedDate) {
lastFullyMasteredAt = new Date(latestMasteredDate) > new Date(latestTestedDate)
? latestMasteredDate : latestTestedDate;
} else if (latestMasteredDate) {
lastFullyMasteredAt = latestMasteredDate;
} else if (latestTestedDate) {
lastFullyMasteredAt = latestTestedDate;
}
}
// 检查是否需要更新
let needsUpdate = false;
// 情况1: isFullyMastered字段与计算结果不一致
if (record.isFullyMastered !== shouldBeFullyMastered) {
needsUpdate = true;
}
// 情况2: 应该完全掌握但缺少lastFullyMasteredAt字段或值不正确
if (shouldBeFullyMastered &&
(!record.lastFullyMasteredAt ||
(lastFullyMasteredAt && new Date(record.lastFullyMasteredAt).getTime() !== new Date(lastFullyMasteredAt).getTime()))) {
needsUpdate = true;
}
// 如果需要更新,添加到批量操作
if (needsUpdate) {
needUpdateCount++;
const updateOperation = {
updateOne: {
filter: { _id: record._id },
update: {
$set: {
isFullyMastered: shouldBeFullyMastered,
...(lastFullyMasteredAt ? { lastFullyMasteredAt: new Date(lastFullyMasteredAt) } : {})
}
}
}
};
updateOperations.push(updateOperation);
}
}
console.log(`检查完成,发现 ${needUpdateCount} 条记录需要更新`);
// 执行批量更新
if (updateOperations.length > 0) {
const result = await WordRecord.bulkWrite(updateOperations);
console.log(`成功更新 ${result.modifiedCount} 条记录`);
} else {
console.log('没有记录需要更新');
}
// 验证更新后的状态
const missingFieldCount = await WordRecord.countDocuments({
isFullyMastered: { $exists: false }
});
if (missingFieldCount > 0) {
console.log(`警告:仍有 ${missingFieldCount} 条记录缺少isFullyMastered字段`);
} else {
console.log('所有记录现在都包含isFullyMastered字段');
}
// 检查掌握一致性
const inconsistentRecords = await WordRecord.find({
$or: [
// 情况1: 三种模式都掌握了但isFullyMastered为false
{
'chineseToEnglish.mastered': true,
'audioToEnglish.mastered': true,
'multipleChoice.mastered': true,
isFullyMastered: false
},
// 情况2: 至少一种模式未掌握但isFullyMastered为true
{
$or: [
{ 'chineseToEnglish.mastered': { $ne: true } },
{ 'audioToEnglish.mastered': { $ne: true } },
{ 'multipleChoice.mastered': { $ne: true } }
],
isFullyMastered: true
}
]
}).limit(5);
if (inconsistentRecords.length > 0) {
console.log(`警告:发现 ${inconsistentRecords.length} 条记录的掌握状态不一致显示前5条`);
inconsistentRecords.forEach(r => {
console.log(`ID: ${r._id}, Word: ${r.word}, Modes: ${r.chineseToEnglish?.mastered}, ${r.audioToEnglish?.mastered}, ${r.multipleChoice?.mastered}, isFullyMastered: ${r.isFullyMastered}`);
});
} else {
console.log('所有记录的掌握状态一致');
}
} catch (error) {
console.error('处理过程中发生错误:', error);
throw error;
}
}