quanyoumi/backend/migrations/index.js

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;