Files
nxxmdata/backend/scripts/migration-manager.js
2025-08-25 15:00:46 +08:00

315 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 数据库迁移管理器
* @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
};