142 lines
3.8 KiB
JavaScript
142 lines
3.8 KiB
JavaScript
const sqlite3 = require('sqlite3').verbose();
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const dbPath = path.join(__dirname, '..', 'data', 'database.sqlite');
|
|
const migrationsDir = __dirname;
|
|
|
|
class MigrationManager {
|
|
constructor() {
|
|
this.db = new sqlite3.Database(dbPath);
|
|
}
|
|
|
|
// 初始化迁移表
|
|
async initMigrationTable() {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(`
|
|
CREATE TABLE IF NOT EXISTS migrations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
version INTEGER NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`, (err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
// 获取已执行的迁移版本
|
|
async getExecutedMigrations() {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.all('SELECT version FROM migrations ORDER BY version', (err, rows) => {
|
|
if (err) reject(err);
|
|
else resolve(rows.map(r => r.version));
|
|
});
|
|
});
|
|
}
|
|
|
|
// 记录迁移执行
|
|
async recordMigration(version, name) {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(
|
|
'INSERT INTO migrations (version, name) VALUES (?, ?)',
|
|
[version, name],
|
|
(err) => {
|
|
if (err) reject(err);
|
|
else resolve();
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
// 执行SQL语句
|
|
async executeSql(sql) {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(sql, (err) => {
|
|
if (err) {
|
|
// 忽略字段已存在的错误
|
|
if (err.message.includes('duplicate column name')) {
|
|
resolve();
|
|
} else {
|
|
reject(err);
|
|
}
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 加载所有迁移文件
|
|
loadMigrations() {
|
|
const files = fs.readdirSync(migrationsDir)
|
|
.filter(f => f.match(/^\d{4}-.*\.js$/) && f !== 'index.js')
|
|
.sort();
|
|
|
|
return files.map(file => {
|
|
const version = parseInt(file.split('-')[0]);
|
|
const name = file.replace(/^\d{4}-/, '').replace('.js', '');
|
|
const migration = require(path.join(migrationsDir, file));
|
|
return { version, name, file, migration };
|
|
});
|
|
}
|
|
|
|
// 执行迁移
|
|
async runMigrations() {
|
|
try {
|
|
console.log('🔄 开始数据库迁移...\n');
|
|
|
|
// 初始化迁移表
|
|
await this.initMigrationTable();
|
|
|
|
// 获取已执行的迁移
|
|
const executed = await this.getExecutedMigrations();
|
|
console.log(`📊 已执行迁移: ${executed.length} 个`);
|
|
|
|
// 加载所有迁移文件
|
|
const migrations = this.loadMigrations();
|
|
console.log(`📁 发现迁移文件: ${migrations.length} 个\n`);
|
|
|
|
// 执行未执行的迁移
|
|
let count = 0;
|
|
for (const { version, name, file, migration } of migrations) {
|
|
if (!executed.includes(version)) {
|
|
console.log(`⏳ 执行迁移 [${version}] ${name}...`);
|
|
|
|
try {
|
|
await migration.up(this);
|
|
await this.recordMigration(version, name);
|
|
console.log(`✅ 迁移 [${version}] ${name} 完成\n`);
|
|
count++;
|
|
} catch (error) {
|
|
console.error(`❌ 迁移 [${version}] ${name} 失败:`, error.message);
|
|
throw error;
|
|
}
|
|
} else {
|
|
console.log(`⏭️ 跳过已执行的迁移 [${version}] ${name}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\n✨ 迁移完成!共执行 ${count} 个新迁移`);
|
|
|
|
} catch (error) {
|
|
console.error('\n❌ 迁移失败:', error);
|
|
throw error;
|
|
} finally {
|
|
this.db.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 如果直接运行此文件
|
|
if (require.main === module) {
|
|
const manager = new MigrationManager();
|
|
manager.runMigrations()
|
|
.then(() => process.exit(0))
|
|
.catch(() => process.exit(1));
|
|
}
|
|
|
|
module.exports = MigrationManager;
|