Migrate project from typing_practiceweb
This commit is contained in:
230
scripts/migrate-complete.ts
Normal file
230
scripts/migrate-complete.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user