282 lines
6.9 KiB
JavaScript
282 lines
6.9 KiB
JavaScript
|
|
/**
|
|||
|
|
* 数据库种子数据管理器
|
|||
|
|
* @file seed-manager.js
|
|||
|
|
* @description 管理数据库种子数据,用于初始化和测试
|
|||
|
|
*/
|
|||
|
|
require('dotenv').config();
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
const { sequelize } = require('../config/database-simple');
|
|||
|
|
const { QueryTypes } = require('sequelize');
|
|||
|
|
|
|||
|
|
// 种子文件目录
|
|||
|
|
const SEEDS_DIR = path.join(__dirname, '../seeds');
|
|||
|
|
|
|||
|
|
// 确保种子目录存在
|
|||
|
|
if (!fs.existsSync(SEEDS_DIR)) {
|
|||
|
|
fs.mkdirSync(SEEDS_DIR, { recursive: true });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建种子记录表(如果不存在)
|
|||
|
|
async function createSeedTable() {
|
|||
|
|
await sequelize.query(`
|
|||
|
|
CREATE TABLE IF NOT EXISTS seeds (
|
|||
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|||
|
|
name VARCHAR(255) NOT NULL UNIQUE,
|
|||
|
|
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|||
|
|
);
|
|||
|
|
`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取已应用的种子
|
|||
|
|
async function getAppliedSeeds() {
|
|||
|
|
await createSeedTable();
|
|||
|
|
|
|||
|
|
const seeds = await sequelize.query(
|
|||
|
|
'SELECT name FROM seeds ORDER BY id ASC',
|
|||
|
|
{ type: QueryTypes.SELECT }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
return seeds.map(seed => seed.name);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取所有种子文件
|
|||
|
|
function getAllSeeds() {
|
|||
|
|
return fs.readdirSync(SEEDS_DIR)
|
|||
|
|
.filter(file => file.endsWith('.js'))
|
|||
|
|
.sort(); // 按文件名排序
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 应用种子数据
|
|||
|
|
async function applySeed(seedName) {
|
|||
|
|
const seed = require(path.join(SEEDS_DIR, seedName));
|
|||
|
|
|
|||
|
|
console.log(`正在应用种子数据: ${seedName}`);
|
|||
|
|
|
|||
|
|
// 开始事务
|
|||
|
|
const transaction = await sequelize.transaction();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 执行种子的 up 方法
|
|||
|
|
await seed.up(sequelize.getQueryInterface(), sequelize);
|
|||
|
|
|
|||
|
|
// 记录种子已应用
|
|||
|
|
await sequelize.query(
|
|||
|
|
'INSERT INTO seeds (name) VALUES (:name)',
|
|||
|
|
{
|
|||
|
|
replacements: { name: seedName },
|
|||
|
|
transaction
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 提交事务
|
|||
|
|
await transaction.commit();
|
|||
|
|
console.log(`种子数据应用成功: ${seedName}`);
|
|||
|
|
} catch (error) {
|
|||
|
|
// 回滚事务
|
|||
|
|
await transaction.rollback();
|
|||
|
|
console.error(`种子数据应用失败: ${seedName}`, error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 回滚种子数据
|
|||
|
|
async function revertSeed(seedName) {
|
|||
|
|
const seed = require(path.join(SEEDS_DIR, seedName));
|
|||
|
|
|
|||
|
|
console.log(`正在回滚种子数据: ${seedName}`);
|
|||
|
|
|
|||
|
|
// 开始事务
|
|||
|
|
const transaction = await sequelize.transaction();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 执行种子的 down 方法
|
|||
|
|
await seed.down(sequelize.getQueryInterface(), sequelize);
|
|||
|
|
|
|||
|
|
// 删除种子记录
|
|||
|
|
await sequelize.query(
|
|||
|
|
'DELETE FROM seeds WHERE name = :name',
|
|||
|
|
{
|
|||
|
|
replacements: { name: seedName },
|
|||
|
|
transaction
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 提交事务
|
|||
|
|
await transaction.commit();
|
|||
|
|
console.log(`种子数据回滚成功: ${seedName}`);
|
|||
|
|
} catch (error) {
|
|||
|
|
// 回滚事务
|
|||
|
|
await transaction.rollback();
|
|||
|
|
console.error(`种子数据回滚失败: ${seedName}`, error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 运行所有种子数据
|
|||
|
|
async function runAllSeeds() {
|
|||
|
|
const appliedSeeds = await getAppliedSeeds();
|
|||
|
|
const allSeeds = getAllSeeds();
|
|||
|
|
|
|||
|
|
// 找出未应用的种子
|
|||
|
|
const pendingSeeds = allSeeds.filter(
|
|||
|
|
seed => !appliedSeeds.includes(seed)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (pendingSeeds.length === 0) {
|
|||
|
|
console.log('没有待处理的种子数据');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`发现 ${pendingSeeds.length} 个待处理的种子数据`);
|
|||
|
|
|
|||
|
|
// 按顺序应用每个待处理的种子
|
|||
|
|
for (const seed of pendingSeeds) {
|
|||
|
|
await applySeed(seed);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('所有待处理的种子数据已应用');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 回滚所有种子数据
|
|||
|
|
async function revertAllSeeds() {
|
|||
|
|
const appliedSeeds = await getAppliedSeeds();
|
|||
|
|
|
|||
|
|
if (appliedSeeds.length === 0) {
|
|||
|
|
console.log('没有可回滚的种子数据');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从最新的种子开始,回滚所有种子
|
|||
|
|
const seedsToRevert = [...appliedSeeds].reverse();
|
|||
|
|
|
|||
|
|
for (const seed of seedsToRevert) {
|
|||
|
|
await revertSeed(seed);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('所有种子数据已回滚');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建新的种子文件
|
|||
|
|
function createSeed(name) {
|
|||
|
|
const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace('T', '').split('.')[0];
|
|||
|
|
const fileName = `${timestamp}_${name}.js`;
|
|||
|
|
const filePath = path.join(SEEDS_DIR, fileName);
|
|||
|
|
|
|||
|
|
const template = `/**
|
|||
|
|
* 种子数据: ${name}
|
|||
|
|
* 创建时间: ${new Date().toISOString()}
|
|||
|
|
*/
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
up: async (queryInterface, Sequelize) => {
|
|||
|
|
// 在此处添加种子数据
|
|||
|
|
// 例如:
|
|||
|
|
// await queryInterface.bulkInsert('users', [
|
|||
|
|
// {
|
|||
|
|
// username: 'admin',
|
|||
|
|
// email: 'admin@example.com',
|
|||
|
|
// password: '$2b$10$rVHMOB./a2mFmE4EEdI3QO4f./bN3LYb.dpDvtX9gRUM9gNwspj1a', // 123456
|
|||
|
|
// created_at: new Date(),
|
|||
|
|
// updated_at: new Date()
|
|||
|
|
// },
|
|||
|
|
// {
|
|||
|
|
// username: 'user',
|
|||
|
|
// email: 'user@example.com',
|
|||
|
|
// password: '$2b$10$rVHMOB./a2mFmE4EEdI3QO4f./bN3LYb.dpDvtX9gRUM9gNwspj1a', // 123456
|
|||
|
|
// created_at: new Date(),
|
|||
|
|
// updated_at: new Date()
|
|||
|
|
// }
|
|||
|
|
// ]);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
down: async (queryInterface, Sequelize) => {
|
|||
|
|
// 在此处添加回滚代码
|
|||
|
|
// 例如:
|
|||
|
|
// await queryInterface.bulkDelete('users', null, {});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
createSeed(args[1]);
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'run':
|
|||
|
|
await runAllSeeds();
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'revert':
|
|||
|
|
await revertAllSeeds();
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'status':
|
|||
|
|
const applied = await getAppliedSeeds();
|
|||
|
|
const all = getAllSeeds();
|
|||
|
|
|
|||
|
|
console.log('已应用的种子数据:');
|
|||
|
|
applied.forEach(s => console.log(` - ${s}`));
|
|||
|
|
|
|||
|
|
console.log('\n待处理的种子数据:');
|
|||
|
|
all.filter(s => !applied.includes(s))
|
|||
|
|
.forEach(s => console.log(` - ${s}`));
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
console.log(`
|
|||
|
|
数据库种子数据管理器
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
node seed-manager.js <命令> [参数]
|
|||
|
|
|
|||
|
|
命令:
|
|||
|
|
create <name> 创建新的种子文件
|
|||
|
|
run 应用所有待处理的种子数据
|
|||
|
|
revert 回滚所有种子数据
|
|||
|
|
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 = {
|
|||
|
|
createSeedTable,
|
|||
|
|
getAppliedSeeds,
|
|||
|
|
getAllSeeds,
|
|||
|
|
applySeed,
|
|||
|
|
revertSeed,
|
|||
|
|
runAllSeeds,
|
|||
|
|
revertAllSeeds,
|
|||
|
|
createSeed
|
|||
|
|
};
|