/** * 数据库迁移管理器 * @file migration-manager.js * @description 管理数据库迁移,支持版本控制和回滚 */ const fs = require('fs'); const path = require('path'); const { sequelize } = require('../config/database-simple'); const { QueryTypes } = require('sequelize'); // 迁移文件目录 const MIGRATIONS_DIR = path.join(__dirname, '../migrations'); // 确保迁移目录存在 if (!fs.existsSync(MIGRATIONS_DIR)) { fs.mkdirSync(MIGRATIONS_DIR, { recursive: true }); } // 创建迁移表(如果不存在) async function createMigrationTable() { await sequelize.query(` CREATE TABLE IF NOT EXISTS migrations ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); } // 获取已应用的迁移 async function getAppliedMigrations() { await createMigrationTable(); const migrations = await sequelize.query( 'SELECT name FROM migrations ORDER BY id ASC', { type: QueryTypes.SELECT } ); return migrations.map(migration => migration.name); } // 获取所有迁移文件 function getAllMigrations() { return fs.readdirSync(MIGRATIONS_DIR) .filter(file => file.endsWith('.js')) .sort(); // 按文件名排序,通常是时间戳前缀 } // 应用迁移 async function applyMigration(migrationName) { const migration = require(path.join(MIGRATIONS_DIR, migrationName)); console.log(`正在应用迁移: ${migrationName}`); // 开始事务 const transaction = await sequelize.transaction(); try { // 执行迁移的 up 方法 await migration.up(sequelize.getQueryInterface(), sequelize); // 记录迁移已应用 await sequelize.query( 'INSERT INTO migrations (name) VALUES (:name)', { replacements: { name: migrationName }, transaction } ); // 提交事务 await transaction.commit(); console.log(`迁移应用成功: ${migrationName}`); } catch (error) { // 回滚事务 await transaction.rollback(); console.error(`迁移应用失败: ${migrationName}`, error); throw error; } } // 回滚迁移 async function revertMigration(migrationName) { const migration = require(path.join(MIGRATIONS_DIR, migrationName)); console.log(`正在回滚迁移: ${migrationName}`); // 开始事务 const transaction = await sequelize.transaction(); try { // 执行迁移的 down 方法 await migration.down(sequelize.getQueryInterface(), sequelize); // 删除迁移记录 await sequelize.query( 'DELETE FROM migrations WHERE name = :name', { replacements: { name: migrationName }, transaction } ); // 提交事务 await transaction.commit(); console.log(`迁移回滚成功: ${migrationName}`); } catch (error) { // 回滚事务 await transaction.rollback(); console.error(`迁移回滚失败: ${migrationName}`, error); throw error; } } // 运行待处理的迁移 async function runPendingMigrations() { const appliedMigrations = await getAppliedMigrations(); const allMigrations = getAllMigrations(); // 找出未应用的迁移 const pendingMigrations = allMigrations.filter( migration => !appliedMigrations.includes(migration) ); if (pendingMigrations.length === 0) { console.log('没有待处理的迁移'); return; } console.log(`发现 ${pendingMigrations.length} 个待处理的迁移`); // 按顺序应用每个待处理的迁移 for (const migration of pendingMigrations) { await applyMigration(migration); } console.log('所有待处理的迁移已应用'); } // 回滚最近的迁移 async function rollbackLastMigration() { const appliedMigrations = await getAppliedMigrations(); if (appliedMigrations.length === 0) { console.log('没有可回滚的迁移'); return; } const lastMigration = appliedMigrations[appliedMigrations.length - 1]; await revertMigration(lastMigration); } // 回滚到特定迁移 async function rollbackToMigration(targetMigration) { const appliedMigrations = await getAppliedMigrations(); if (appliedMigrations.length === 0) { console.log('没有可回滚的迁移'); return; } const targetIndex = appliedMigrations.indexOf(targetMigration); if (targetIndex === -1) { console.error(`目标迁移 ${targetMigration} 未找到或未应用`); return; } // 从最新的迁移开始,回滚到目标迁移之后的所有迁移 const migrationsToRollback = appliedMigrations.slice(targetIndex + 1).reverse(); for (const migration of migrationsToRollback) { await revertMigration(migration); } console.log(`已回滚到迁移: ${targetMigration}`); } // 创建新的迁移文件 function createMigration(name) { const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace('T', '').split('.')[0]; const fileName = `${timestamp}_${name}.js`; const filePath = path.join(MIGRATIONS_DIR, fileName); const template = `/** * 迁移: ${name} * 创建时间: ${new Date().toISOString()} */ 'use strict'; module.exports = { up: async (queryInterface, Sequelize) => { // 在此处添加迁移代码(创建表、添加列等) // 例如: // await queryInterface.createTable('users', { // id: { // type: Sequelize.INTEGER, // primaryKey: true, // autoIncrement: true // }, // name: Sequelize.STRING, // createdAt: { // type: Sequelize.DATE, // defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), // field: 'created_at' // }, // updatedAt: { // type: Sequelize.DATE, // defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), // field: 'updated_at' // } // }); }, down: async (queryInterface, Sequelize) => { // 在此处添加回滚代码(删除表、删除列等) // 例如: // await queryInterface.dropTable('users'); } }; `; fs.writeFileSync(filePath, template); console.log(`已创建迁移文件: ${fileName}`); return fileName; } // 命令行接口 async function main() { const args = process.argv.slice(2); const command = args[0]; try { switch (command) { case 'create': if (!args[1]) { console.error('请提供迁移名称'); process.exit(1); } createMigration(args[1]); break; case 'up': case 'migrate': await runPendingMigrations(); break; case 'down': case 'rollback': await rollbackLastMigration(); break; case 'to': if (!args[1]) { console.error('请提供目标迁移名称'); process.exit(1); } await rollbackToMigration(args[1]); break; case 'status': const applied = await getAppliedMigrations(); const all = getAllMigrations(); console.log('已应用的迁移:'); applied.forEach(m => console.log(` - ${m}`)); console.log('\n待处理的迁移:'); all.filter(m => !applied.includes(m)) .forEach(m => console.log(` - ${m}`)); break; default: console.log(` 数据库迁移管理器 用法: node migration-manager.js <命令> [参数] 命令: create 创建新的迁移文件 up, migrate 应用所有待处理的迁移 down, rollback 回滚最近的一次迁移 to 回滚到指定的迁移(不包括该迁移) status 显示迁移状态 `); } } catch (error) { console.error('迁移操作失败:', error); process.exit(1); } finally { // 关闭数据库连接 await sequelize.close(); } } // 如果直接运行此脚本,则执行main函数 if (require.main === module) { main().catch(err => { console.error('未处理的错误:', err); process.exit(1); }); } module.exports = { createMigrationTable, getAppliedMigrations, getAllMigrations, applyMigration, revertMigration, runPendingMigrations, rollbackLastMigration, rollbackToMigration, createMigration };