315 lines
8.1 KiB
JavaScript
315 lines
8.1 KiB
JavaScript
/**
|
||
* 数据库迁移管理器
|
||
* @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 <name> 创建新的迁移文件
|
||
up, migrate 应用所有待处理的迁移
|
||
down, rollback 回滚最近的一次迁移
|
||
to <migration> 回滚到指定的迁移(不包括该迁移)
|
||
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
|
||
}; |