refactor: 统一代码风格并迁移至 ESLint 新配置
style(backend): 格式化模型文件代码 style(frontend): 调整组件代码格式 chore: 删除旧 ESLint 配置并添加新配置 refactor(backend): 重构模型定义语法 style: 统一箭头函数和对象属性简写
This commit is contained in:
@@ -3,34 +3,42 @@ const { sequelize } = require('../db');
|
||||
async function addIsSystemColumn() {
|
||||
try {
|
||||
console.log('开始添加 isSystem 列到 deviceFields 表...');
|
||||
|
||||
|
||||
// 检查列是否已存在
|
||||
const tableInfo = await sequelize.query(
|
||||
"PRAGMA table_info(deviceFields)",
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const tableInfo = await sequelize.query('PRAGMA table_info(deviceFields)', {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
});
|
||||
|
||||
const hasIsSystemColumn = tableInfo.some(col => col.name === 'isSystem');
|
||||
|
||||
|
||||
if (hasIsSystemColumn) {
|
||||
console.log('isSystem 列已存在,跳过添加');
|
||||
} else {
|
||||
// 添加 isSystem 列
|
||||
await sequelize.query(
|
||||
"ALTER TABLE deviceFields ADD COLUMN isSystem BOOLEAN DEFAULT 0",
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
await sequelize.query('ALTER TABLE deviceFields ADD COLUMN isSystem BOOLEAN DEFAULT 0', {
|
||||
type: sequelize.QueryTypes.RAW,
|
||||
});
|
||||
console.log('isSystem 列添加成功');
|
||||
}
|
||||
|
||||
|
||||
// 更新现有数据:将核心字段标记为系统字段
|
||||
const systemFields = [
|
||||
'deviceId', 'name', 'type', 'model', 'serialNumber',
|
||||
'rackId', 'position', 'height', 'powerConsumption',
|
||||
'status', 'purchaseDate', 'warrantyExpiry',
|
||||
'ipAddress', 'description'
|
||||
'deviceId',
|
||||
'name',
|
||||
'type',
|
||||
'model',
|
||||
'serialNumber',
|
||||
'rackId',
|
||||
'position',
|
||||
'height',
|
||||
'powerConsumption',
|
||||
'status',
|
||||
'purchaseDate',
|
||||
'warrantyExpiry',
|
||||
'ipAddress',
|
||||
'description',
|
||||
];
|
||||
|
||||
|
||||
for (const fieldName of systemFields) {
|
||||
await sequelize.query(
|
||||
`UPDATE deviceFields SET isSystem = 1 WHERE fieldName = '${fieldName}'`,
|
||||
@@ -38,7 +46,7 @@ async function addIsSystemColumn() {
|
||||
);
|
||||
}
|
||||
console.log('系统字段标记完成');
|
||||
|
||||
|
||||
console.log('数据库迁移完成');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
|
||||
@@ -16,61 +16,61 @@ async function migrate() {
|
||||
userId: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
primaryKey: true,
|
||||
allowNull: false
|
||||
allowNull: false,
|
||||
},
|
||||
username: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false
|
||||
allowNull: false,
|
||||
},
|
||||
email: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
},
|
||||
phone: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
},
|
||||
realName: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
},
|
||||
avatar: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
},
|
||||
status: {
|
||||
type: sequelize.Sequelize.ENUM('active', 'inactive', 'locked', 'pending'),
|
||||
defaultValue: 'active'
|
||||
defaultValue: 'active',
|
||||
},
|
||||
lastLoginTime: {
|
||||
type: sequelize.Sequelize.DATE,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
},
|
||||
lastLoginIp: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
},
|
||||
loginCount: {
|
||||
type: sequelize.Sequelize.INTEGER,
|
||||
defaultValue: 0
|
||||
defaultValue: 0,
|
||||
},
|
||||
remark: {
|
||||
type: sequelize.Sequelize.TEXT,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
},
|
||||
createdAt: {
|
||||
type: sequelize.Sequelize.DATE,
|
||||
allowNull: false
|
||||
allowNull: false,
|
||||
},
|
||||
updatedAt: {
|
||||
type: sequelize.Sequelize.DATE,
|
||||
allowNull: false
|
||||
}
|
||||
allowNull: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 2. 复制数据
|
||||
|
||||
@@ -104,14 +104,12 @@ async function migrateSQLite() {
|
||||
{ name: 'idx_archive_consumable_id', fields: 'consumableId' },
|
||||
{ name: 'idx_archive_archive_id', fields: 'archiveId' },
|
||||
{ name: 'idx_archive_deleted_at', fields: 'deletedAt' },
|
||||
{ name: 'idx_archive_consumable_deleted', fields: 'consumableId, deletedAt' }
|
||||
{ name: 'idx_archive_consumable_deleted', fields: 'consumableId, deletedAt' },
|
||||
];
|
||||
|
||||
for (const idx of indexes) {
|
||||
try {
|
||||
await sequelize.query(
|
||||
`CREATE INDEX ${idx.name} ON consumable_log_archives(${idx.fields})`
|
||||
);
|
||||
await sequelize.query(`CREATE INDEX ${idx.name} ON consumable_log_archives(${idx.fields})`);
|
||||
console.log(`✓ 创建索引: ${idx.name}`);
|
||||
} catch (err) {
|
||||
console.log(`! 创建索引失败: ${idx.name}`, err.message);
|
||||
|
||||
@@ -120,7 +120,17 @@ async function updateExistingLogs() {
|
||||
|
||||
try {
|
||||
const consumables = await Consumable.findAll({
|
||||
attributes: ['consumableId', 'category', 'unit', 'unitPrice', 'supplier', 'location', 'minStock', 'maxStock', 'status']
|
||||
attributes: [
|
||||
'consumableId',
|
||||
'category',
|
||||
'unit',
|
||||
'unitPrice',
|
||||
'supplier',
|
||||
'location',
|
||||
'minStock',
|
||||
'maxStock',
|
||||
'status',
|
||||
],
|
||||
});
|
||||
|
||||
const consumableMap = new Map();
|
||||
@@ -133,14 +143,14 @@ async function updateExistingLogs() {
|
||||
location: c.location,
|
||||
minStock: c.minStock,
|
||||
maxStock: c.maxStock,
|
||||
status: c.status
|
||||
status: c.status,
|
||||
});
|
||||
});
|
||||
|
||||
const logs = await ConsumableLog.findAll({
|
||||
where: {
|
||||
consumableSnapshot: null
|
||||
}
|
||||
consumableSnapshot: null,
|
||||
},
|
||||
});
|
||||
|
||||
let updatedCount = 0;
|
||||
@@ -155,10 +165,9 @@ async function updateExistingLogs() {
|
||||
console.log(`✓ 更新了 ${updatedCount} 条日志的快照信息`);
|
||||
|
||||
const deletedLogsCount = await ConsumableLog.count({
|
||||
where: { operationType: 'delete' }
|
||||
where: { operationType: 'delete' },
|
||||
});
|
||||
console.log(`✓ 当前有 ${deletedLogsCount} 条删除类型日志`);
|
||||
|
||||
} catch (error) {
|
||||
console.log('! 更新现有日志数据时出错:', error.message);
|
||||
}
|
||||
|
||||
@@ -16,18 +16,15 @@ async function migrate() {
|
||||
|
||||
if (actualDbType === 'sqlite') {
|
||||
// SQLite: 检查字段是否存在
|
||||
const tableInfo = await sequelize.query(
|
||||
"PRAGMA table_info(consumables)",
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const tableInfo = await sequelize.query('PRAGMA table_info(consumables)', {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
});
|
||||
|
||||
const hasVersion = tableInfo.some(col => col.name === 'version');
|
||||
|
||||
|
||||
if (!hasVersion) {
|
||||
console.log('添加 version 字段...');
|
||||
await sequelize.query(
|
||||
"ALTER TABLE consumables ADD COLUMN version INTEGER DEFAULT 0"
|
||||
);
|
||||
await sequelize.query('ALTER TABLE consumables ADD COLUMN version INTEGER DEFAULT 0');
|
||||
console.log('version 字段添加成功');
|
||||
} else {
|
||||
console.log('version 字段已存在,跳过');
|
||||
@@ -38,19 +35,16 @@ async function migrate() {
|
||||
"SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='consumables'",
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
|
||||
const hasUpdatedAtIndex = indexes.some(idx => idx.name === 'consumables_updatedAt');
|
||||
|
||||
|
||||
if (!hasUpdatedAtIndex) {
|
||||
console.log('添加 updatedAt 索引...');
|
||||
await sequelize.query(
|
||||
"CREATE INDEX consumables_updatedAt ON consumables(updatedAt)"
|
||||
);
|
||||
await sequelize.query('CREATE INDEX consumables_updatedAt ON consumables(updatedAt)');
|
||||
console.log('updatedAt 索引添加成功');
|
||||
} else {
|
||||
console.log('updatedAt 索引已存在,跳过');
|
||||
}
|
||||
|
||||
} else if (actualDbType === 'mysql') {
|
||||
// MySQL: 检查并添加字段
|
||||
try {
|
||||
@@ -70,9 +64,7 @@ async function migrate() {
|
||||
// 添加索引
|
||||
try {
|
||||
console.log('添加 updatedAt 索引...');
|
||||
await sequelize.query(
|
||||
"CREATE INDEX idx_consumables_updatedAt ON consumables(updatedAt)"
|
||||
);
|
||||
await sequelize.query('CREATE INDEX idx_consumables_updatedAt ON consumables(updatedAt)');
|
||||
console.log('updatedAt 索引添加成功');
|
||||
} catch (err) {
|
||||
if (err.message.includes('Duplicate key')) {
|
||||
@@ -85,9 +77,7 @@ async function migrate() {
|
||||
|
||||
// 初始化现有数据的 version 值
|
||||
console.log('初始化现有数据的 version 值...');
|
||||
await sequelize.query(
|
||||
"UPDATE consumables SET version = 0 WHERE version IS NULL"
|
||||
);
|
||||
await sequelize.query('UPDATE consumables SET version = 0 WHERE version IS NULL');
|
||||
console.log('version 值初始化完成');
|
||||
|
||||
console.log('迁移完成!');
|
||||
|
||||
@@ -8,19 +8,19 @@ async function migrate() {
|
||||
console.log(' IDC管理系统 - 数据库迁移脚本 v2.0 ');
|
||||
console.log('========================================');
|
||||
console.log('');
|
||||
|
||||
|
||||
try {
|
||||
console.log('🔍 检测数据库类型...');
|
||||
const dbType = sequelize.getDialect();
|
||||
console.log(` 数据库类型: ${dbType}`);
|
||||
console.log('');
|
||||
|
||||
|
||||
console.log('📋 开始迁移...');
|
||||
console.log(' 1. 创建 network_cards 表');
|
||||
console.log(' 2. 为 device_ports 添加 nic_id 字段');
|
||||
console.log(' 3. 创建相关索引');
|
||||
console.log('');
|
||||
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
await migrateSQLite();
|
||||
} else if (dbType === 'mysql') {
|
||||
@@ -29,25 +29,26 @@ async function migrate() {
|
||||
console.log(`⚠️ 不支持的数据库类型: ${dbType}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
console.log('');
|
||||
console.log('✅ 迁移完成!');
|
||||
console.log('');
|
||||
console.log('📊 验证迁移结果...');
|
||||
|
||||
const [tables] = await sequelize.query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
|
||||
|
||||
const [tables] = await sequelize.query(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
||||
);
|
||||
console.log(` 数据库表: ${tables.map(t => t.name).join(', ')}`);
|
||||
|
||||
|
||||
const portCount = await DevicePort.count();
|
||||
const cardCount = await NetworkCard.count();
|
||||
console.log(` device_ports: ${portCount} 条记录`);
|
||||
console.log(` network_cards: ${cardCount} 条记录`);
|
||||
|
||||
|
||||
console.log('');
|
||||
console.log('========================================');
|
||||
console.log(' 迁移成功完成!🎉');
|
||||
console.log('========================================');
|
||||
|
||||
} catch (error) {
|
||||
console.error('');
|
||||
console.error('❌ 迁移失败:', error.message);
|
||||
@@ -62,33 +63,32 @@ async function migrate() {
|
||||
async function migrateSQLite() {
|
||||
console.log('');
|
||||
console.log('🔄 执行 SQLite 迁移...');
|
||||
|
||||
|
||||
await sequelize.query('PRAGMA foreign_keys = OFF');
|
||||
|
||||
|
||||
try {
|
||||
console.log(' → 删除旧的 device_ports 表...');
|
||||
await sequelize.query('DROP TABLE IF EXISTS `device_ports`');
|
||||
|
||||
|
||||
console.log(' → 删除旧的 network_cards 表...');
|
||||
await sequelize.query('DROP TABLE IF EXISTS `network_cards`');
|
||||
|
||||
|
||||
console.log(' → 同步 DevicePort 模型...');
|
||||
await DevicePort.sync({ force: false });
|
||||
|
||||
|
||||
console.log(' → 同步 NetworkCard 模型...');
|
||||
await NetworkCard.sync({ force: false });
|
||||
|
||||
|
||||
console.log(' → 同步 Device 模型(确保外键关系)...');
|
||||
await Device.sync({ force: false });
|
||||
|
||||
|
||||
console.log(' → 重新同步 DevicePort 模型(含外键)...');
|
||||
await DevicePort.sync({ force: true });
|
||||
|
||||
|
||||
console.log(' → 重新同步 NetworkCard 模型...');
|
||||
await NetworkCard.sync({ force: false });
|
||||
|
||||
|
||||
await sequelize.query('PRAGMA foreign_keys = ON');
|
||||
|
||||
} catch (error) {
|
||||
await sequelize.query('PRAGMA foreign_keys = ON');
|
||||
throw error;
|
||||
@@ -98,10 +98,10 @@ async function migrateSQLite() {
|
||||
async function migrateMySQL() {
|
||||
console.log('');
|
||||
console.log('🔄 执行 MySQL 迁移...');
|
||||
|
||||
|
||||
const tableName = 'network_cards';
|
||||
console.log(` → 创建表: ${tableName}`);
|
||||
|
||||
|
||||
const createTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
||||
\`nic_id\` VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||
@@ -120,15 +120,14 @@ async function migrateMySQL() {
|
||||
UNIQUE INDEX \`idx_device_name\` (\`device_id\`, \`name\`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`;
|
||||
|
||||
|
||||
await sequelize.query(createTableSQL);
|
||||
|
||||
|
||||
console.log(' → 检查 nic_id 字段是否存在...');
|
||||
const [columns] = await sequelize.query(
|
||||
"SHOW COLUMNS FROM `device_ports` LIKE 'nic_id'",
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const [columns] = await sequelize.query("SHOW COLUMNS FROM `device_ports` LIKE 'nic_id'", {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
});
|
||||
|
||||
if (columns.length === 0) {
|
||||
console.log(' → 添加 nic_id 字段...');
|
||||
await sequelize.query(
|
||||
@@ -137,7 +136,7 @@ async function migrateMySQL() {
|
||||
} else {
|
||||
console.log(' → nic_id 字段已存在,跳过');
|
||||
}
|
||||
|
||||
|
||||
console.log(' → 创建 nic_id 索引...');
|
||||
try {
|
||||
await sequelize.query('CREATE INDEX `idx_port_nic_id` ON `device_ports`(`nic_id`)');
|
||||
@@ -148,11 +147,11 @@ async function migrateMySQL() {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(' → 同步模型以确保关系正确...');
|
||||
await NetworkCard.sync({ force: false });
|
||||
await DevicePort.sync({ force: false });
|
||||
|
||||
|
||||
console.log(' → 添加外键约束...');
|
||||
try {
|
||||
await sequelize.query(
|
||||
|
||||
@@ -55,10 +55,9 @@ async function removeSQLiteForeignKey() {
|
||||
console.log('SQLite 不支持直接删除外键,需要重建表');
|
||||
|
||||
// 检查外键是否存在
|
||||
const fks = await sequelize.query(
|
||||
`PRAGMA foreign_key_list(consumable_logs);`,
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
const fks = await sequelize.query(`PRAGMA foreign_key_list(consumable_logs);`, {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
});
|
||||
|
||||
if (!fks || fks.length === 0) {
|
||||
console.log('✓ 没有外键约束需要移除');
|
||||
@@ -68,17 +67,21 @@ async function removeSQLiteForeignKey() {
|
||||
console.log('发现外键约束:', fks);
|
||||
|
||||
// SQLite 不支持 ALTER TABLE DROP FOREIGN KEY,需要重建表
|
||||
await sequelize.transaction(async (transaction) => {
|
||||
await sequelize.transaction(async transaction => {
|
||||
// 获取表结构
|
||||
const columns = await sequelize.query(
|
||||
`PRAGMA table_info(consumable_logs);`,
|
||||
{ type: sequelize.QueryTypes.SELECT, transaction }
|
||||
const columns = await sequelize.query(`PRAGMA table_info(consumable_logs);`, {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
transaction,
|
||||
});
|
||||
|
||||
console.log(
|
||||
'\n当前表列:',
|
||||
columns.map(c => c.name)
|
||||
);
|
||||
|
||||
console.log('\n当前表列:', columns.map(c => c.name));
|
||||
|
||||
// 创建新表(不包含外键约束)
|
||||
await sequelize.query(`
|
||||
await sequelize.query(
|
||||
`
|
||||
CREATE TABLE consumable_logs_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
consumableId VARCHAR(255) NOT NULL,
|
||||
@@ -101,20 +104,25 @@ async function removeSQLiteForeignKey() {
|
||||
isConsumableDeleted BOOLEAN DEFAULT 0,
|
||||
consumableSnapshot TEXT
|
||||
)
|
||||
`, { transaction });
|
||||
`,
|
||||
{ transaction }
|
||||
);
|
||||
|
||||
console.log('✓ 创建新表成功');
|
||||
|
||||
// 复制数据
|
||||
await sequelize.query(`
|
||||
await sequelize.query(
|
||||
`
|
||||
INSERT INTO consumable_logs_new
|
||||
SELECT * FROM consumable_logs
|
||||
`, { transaction });
|
||||
|
||||
const countResult = await sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM consumable_logs_new`,
|
||||
{ type: sequelize.QueryTypes.SELECT, transaction }
|
||||
`,
|
||||
{ transaction }
|
||||
);
|
||||
|
||||
const countResult = await sequelize.query(`SELECT COUNT(*) as count FROM consumable_logs_new`, {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
transaction,
|
||||
});
|
||||
console.log(`✓ 复制了 ${countResult[0].count} 条数据`);
|
||||
|
||||
// 删除旧表
|
||||
@@ -122,7 +130,9 @@ async function removeSQLiteForeignKey() {
|
||||
console.log('✓ 删除旧表成功');
|
||||
|
||||
// 重命名新表
|
||||
await sequelize.query(`ALTER TABLE consumable_logs_new RENAME TO consumable_logs`, { transaction });
|
||||
await sequelize.query(`ALTER TABLE consumable_logs_new RENAME TO consumable_logs`, {
|
||||
transaction,
|
||||
});
|
||||
console.log('✓ 重命名新表成功');
|
||||
|
||||
// 创建索引
|
||||
@@ -133,7 +143,7 @@ async function removeSQLiteForeignKey() {
|
||||
{ name: 'consumable_logs_consumable_id_created_at', fields: ['consumableId', 'createdAt'] },
|
||||
{ name: 'consumable_logs_original_log_id', fields: ['originalLogId'] },
|
||||
{ name: 'consumable_logs_is_editable', fields: ['isEditable'] },
|
||||
{ name: 'idx_consumable_logs_is_deleted', fields: ['isConsumableDeleted'] }
|
||||
{ name: 'idx_consumable_logs_is_deleted', fields: ['isConsumableDeleted'] },
|
||||
];
|
||||
|
||||
for (const idx of indexes) {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
/**
|
||||
* 命令行备份脚本
|
||||
* 用于独立执行数据备份,支持环境迁移
|
||||
*
|
||||
*
|
||||
* 使用方法:
|
||||
* node scripts/backup.js [options]
|
||||
*
|
||||
*
|
||||
* 选项:
|
||||
* --output, -o 指定备份文件输出路径
|
||||
* --description 备份描述
|
||||
@@ -67,7 +67,7 @@ async function runBackup() {
|
||||
|
||||
try {
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
|
||||
|
||||
const { createBackup, getBackupPath } = require('../utils/backup');
|
||||
const { sequelize } = require('../db');
|
||||
|
||||
@@ -76,7 +76,7 @@ async function runBackup() {
|
||||
console.log('数据库连接成功\n');
|
||||
|
||||
const backupPath = options.output ? path.dirname(options.output) : getBackupPath();
|
||||
|
||||
|
||||
console.log('备份配置:');
|
||||
console.log(` 输出路径: ${backupPath}`);
|
||||
console.log(` 包含文件: ${options.includeFiles ? '是' : '否'}`);
|
||||
|
||||
@@ -8,22 +8,23 @@ async function ensureSchema() {
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
// Check if nicId column exists in device_ports
|
||||
const [columns] = await sequelize.query("PRAGMA table_info(device_ports)");
|
||||
const [columns] = await sequelize.query('PRAGMA table_info(device_ports)');
|
||||
const hasNicId = columns.some(col => col.name === 'nicId');
|
||||
|
||||
if (!hasNicId) {
|
||||
console.log('Adding nicId column to device_ports...');
|
||||
await sequelize.query('ALTER TABLE device_ports ADD COLUMN nicId VARCHAR(255) NULL REFERENCES network_cards(nicId)');
|
||||
await sequelize.query(
|
||||
'ALTER TABLE device_ports ADD COLUMN nicId VARCHAR(255) NULL REFERENCES network_cards(nicId)'
|
||||
);
|
||||
console.log('Added nicId column.');
|
||||
} else {
|
||||
console.log('nicId column already exists in device_ports.');
|
||||
}
|
||||
} else if (dbType === 'mysql') {
|
||||
const [columns] = await sequelize.query(
|
||||
"SHOW COLUMNS FROM `device_ports` LIKE 'nicId'",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const [columns] = await sequelize.query("SHOW COLUMNS FROM `device_ports` LIKE 'nicId'", {
|
||||
type: QueryTypes.SELECT,
|
||||
});
|
||||
|
||||
if (columns.length === 0) {
|
||||
console.log('Adding nicId column to device_ports...');
|
||||
await sequelize.query(
|
||||
|
||||
@@ -4,98 +4,91 @@ const path = require('path');
|
||||
const templateData = [
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': 'IDC2-SERVER-01',
|
||||
'所属机房名称': 'IDC2',
|
||||
机柜名称: 'IDC2-SERVER-01',
|
||||
所属机房名称: 'IDC2',
|
||||
'高度(U)': 42,
|
||||
'最大功率(W)': 5000,
|
||||
'状态': 'active'
|
||||
状态: 'active',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': 'IDC2-SERVER-02',
|
||||
'所属机房名称': 'IDC2',
|
||||
机柜名称: 'IDC2-SERVER-02',
|
||||
所属机房名称: 'IDC2',
|
||||
'高度(U)': 42,
|
||||
'最大功率(W)': 5000,
|
||||
'状态': 'active'
|
||||
状态: 'active',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': 'IDC2-SERVER-03',
|
||||
'所属机房名称': 'IDC2',
|
||||
机柜名称: 'IDC2-SERVER-03',
|
||||
所属机房名称: 'IDC2',
|
||||
'高度(U)': 42,
|
||||
'最大功率(W)': 5000,
|
||||
'状态': 'active'
|
||||
状态: 'active',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': 'IDC4-NETWORK-01',
|
||||
'所属机房名称': 'IDC4',
|
||||
机柜名称: 'IDC4-NETWORK-01',
|
||||
所属机房名称: 'IDC4',
|
||||
'高度(U)': 48,
|
||||
'最大功率(W)': 8000,
|
||||
'状态': 'active'
|
||||
状态: 'active',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': 'IDC4-NETWORK-02',
|
||||
'所属机房名称': 'IDC4',
|
||||
机柜名称: 'IDC4-NETWORK-02',
|
||||
所属机房名称: 'IDC4',
|
||||
'高度(U)': 48,
|
||||
'最大功率(W)': 8000,
|
||||
'状态': 'maintenance'
|
||||
状态: 'maintenance',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': 'IDC5-STORAGE-01',
|
||||
'所属机房名称': 'IDC5',
|
||||
机柜名称: 'IDC5-STORAGE-01',
|
||||
所属机房名称: 'IDC5',
|
||||
'高度(U)': 42,
|
||||
'最大功率(W)': 10000,
|
||||
'状态': 'active'
|
||||
状态: 'active',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': 'IDC5-STORAGE-02',
|
||||
'所属机房名称': 'IDC5',
|
||||
机柜名称: 'IDC5-STORAGE-02',
|
||||
所属机房名称: 'IDC5',
|
||||
'高度(U)': 42,
|
||||
'最大功率(W)': 10000,
|
||||
'状态': 'inactive'
|
||||
状态: 'inactive',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': 'IDC7-SERVER-01',
|
||||
'所属机房名称': 'IDC7',
|
||||
机柜名称: 'IDC7-SERVER-01',
|
||||
所属机房名称: 'IDC7',
|
||||
'高度(U)': 36,
|
||||
'最大功率(W)': 6000,
|
||||
'状态': 'active'
|
||||
状态: 'active',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': '古荡-机柜-01',
|
||||
'所属机房名称': '古荡机房1-1',
|
||||
机柜名称: '古荡-机柜-01',
|
||||
所属机房名称: '古荡机房1-1',
|
||||
'高度(U)': 42,
|
||||
'最大功率(W)': 5000,
|
||||
'状态': 'active'
|
||||
状态: 'active',
|
||||
},
|
||||
{
|
||||
'机柜ID(留空自动生成)': '',
|
||||
'机柜名称': '古荡-机柜-02',
|
||||
'所属机房名称': '古荡机房1-1',
|
||||
机柜名称: '古荡-机柜-02',
|
||||
所属机房名称: '古荡机房1-1',
|
||||
'高度(U)': 42,
|
||||
'最大功率(W)': 5000,
|
||||
'状态': 'active'
|
||||
}
|
||||
状态: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
const wb = XLSX.utils.book_new();
|
||||
|
||||
const ws = XLSX.utils.json_to_sheet(templateData);
|
||||
|
||||
ws['!cols'] = [
|
||||
{ wch: 20 },
|
||||
{ wch: 20 },
|
||||
{ wch: 15 },
|
||||
{ wch: 10 },
|
||||
{ wch: 15 },
|
||||
{ wch: 15 }
|
||||
];
|
||||
ws['!cols'] = [{ wch: 20 }, { wch: 20 }, { wch: 15 }, { wch: 10 }, { wch: 15 }, { wch: 15 }];
|
||||
|
||||
XLSX.utils.book_append_sheet(wb, ws, '机柜导入模板');
|
||||
|
||||
@@ -107,7 +100,9 @@ console.log(`共 ${templateData.length} 条测试数据`);
|
||||
console.log('\n字段说明:');
|
||||
console.log('- 机柜ID(留空自动生成): 留空则系统自动生成唯一ID');
|
||||
console.log('- 机柜名称: 必填,机柜的唯一标识名称');
|
||||
console.log('- 所属机房名称: 必填,系统现有机房: IDC2, IDC4, IDC5, IDC7, 古荡机房1-1, 三墩机房1-1, 地市机房, IDCT');
|
||||
console.log(
|
||||
'- 所属机房名称: 必填,系统现有机房: IDC2, IDC4, IDC5, IDC7, 古荡机房1-1, 三墩机房1-1, 地市机房, IDCT'
|
||||
);
|
||||
console.log('- 高度(U): 必填,机柜的标准高度(1-50U)');
|
||||
console.log('- 最大功率(W): 必填,机柜的最大承载功率');
|
||||
console.log('- 状态: active(在用)/maintenance(维护中)/inactive(停用)');
|
||||
console.log('- 状态: active(在用)/maintenance(维护中)/inactive(停用)');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 数据库初始化脚本
|
||||
* 用于生产环境首次部署时创建表结构
|
||||
*
|
||||
*
|
||||
* 注意:此脚本为独立运行脚本,与 server.js 的初始化逻辑重复
|
||||
* 如果 server.js 已能正常完成初始化,则无需单独运行此脚本
|
||||
* 如需单独运行:node backend/scripts/init-database.js
|
||||
@@ -26,33 +26,194 @@ const InventoryRecord = require('../models/InventoryRecord');
|
||||
// 默认系统设置(包含中文描述)
|
||||
const defaultSettings = [
|
||||
// 全局配置
|
||||
{ settingKey: 'site_name', settingValue: JSON.stringify('机柜管理系统'), settingType: 'string', category: 'general', description: '网站名称', isEditable: true },
|
||||
{ settingKey: 'site_logo', settingValue: JSON.stringify(''), settingType: 'string', category: 'general', description: '网站Logo URL', isEditable: true },
|
||||
{ settingKey: 'timezone', settingValue: JSON.stringify('Asia/Shanghai'), settingType: 'string', category: 'general', description: '时区设置', isEditable: true },
|
||||
{ settingKey: 'date_format', settingValue: JSON.stringify('YYYY-MM-DD'), settingType: 'string', category: 'general', description: '日期格式', isEditable: true },
|
||||
{ settingKey: 'session_timeout', settingValue: JSON.stringify(30), settingType: 'number', category: 'general', description: '登录有效期(分钟)', isEditable: true },
|
||||
{ settingKey: 'idle_timeout', settingValue: JSON.stringify(10), settingType: 'number', category: 'general', description: '用户空闲超时时间(分钟)', isEditable: true },
|
||||
{ settingKey: 'idle_warning_time', settingValue: JSON.stringify(10), settingType: 'number', category: 'general', description: '空闲超时前警告时间(秒)', isEditable: false },
|
||||
{ settingKey: 'max_login_attempts', settingValue: JSON.stringify(5), settingType: 'number', category: 'general', description: '最大登录尝试次数', isEditable: true },
|
||||
{ settingKey: 'maintenance_mode', settingValue: JSON.stringify(false), settingType: 'boolean', category: 'general', description: '维护模式', isEditable: true },
|
||||
{
|
||||
settingKey: 'site_name',
|
||||
settingValue: JSON.stringify('机柜管理系统'),
|
||||
settingType: 'string',
|
||||
category: 'general',
|
||||
description: '网站名称',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'site_logo',
|
||||
settingValue: JSON.stringify(''),
|
||||
settingType: 'string',
|
||||
category: 'general',
|
||||
description: '网站Logo URL',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'timezone',
|
||||
settingValue: JSON.stringify('Asia/Shanghai'),
|
||||
settingType: 'string',
|
||||
category: 'general',
|
||||
description: '时区设置',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'date_format',
|
||||
settingValue: JSON.stringify('YYYY-MM-DD'),
|
||||
settingType: 'string',
|
||||
category: 'general',
|
||||
description: '日期格式',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'session_timeout',
|
||||
settingValue: JSON.stringify(30),
|
||||
settingType: 'number',
|
||||
category: 'general',
|
||||
description: '登录有效期(分钟)',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'idle_timeout',
|
||||
settingValue: JSON.stringify(10),
|
||||
settingType: 'number',
|
||||
category: 'general',
|
||||
description: '用户空闲超时时间(分钟)',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'idle_warning_time',
|
||||
settingValue: JSON.stringify(10),
|
||||
settingType: 'number',
|
||||
category: 'general',
|
||||
description: '空闲超时前警告时间(秒)',
|
||||
isEditable: false,
|
||||
},
|
||||
{
|
||||
settingKey: 'max_login_attempts',
|
||||
settingValue: JSON.stringify(5),
|
||||
settingType: 'number',
|
||||
category: 'general',
|
||||
description: '最大登录尝试次数',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'maintenance_mode',
|
||||
settingValue: JSON.stringify(false),
|
||||
settingType: 'boolean',
|
||||
category: 'general',
|
||||
description: '维护模式',
|
||||
isEditable: true,
|
||||
},
|
||||
|
||||
// 外观设置
|
||||
{ settingKey: 'primary_color', settingValue: JSON.stringify('#667eea'), settingType: 'string', category: 'appearance', description: '主题主色调', isEditable: true },
|
||||
{ settingKey: 'secondary_color', settingValue: JSON.stringify('#764ba2'), settingType: 'string', category: 'appearance', description: '主题辅助色调', isEditable: true },
|
||||
{ settingKey: 'compact_mode', settingValue: JSON.stringify(false), settingType: 'boolean', category: 'appearance', description: '紧凑模式', isEditable: true },
|
||||
{ settingKey: 'sidebar_collapsed', settingValue: JSON.stringify(false), settingType: 'boolean', category: 'appearance', description: '侧边栏默认折叠', isEditable: true },
|
||||
{ settingKey: 'table_row_height', settingValue: JSON.stringify('default'), settingType: 'string', category: 'appearance', description: '表格行高: small/default/middle/large', isEditable: true },
|
||||
{ settingKey: 'animation_enabled', settingValue: JSON.stringify(true), settingType: 'boolean', category: 'appearance', description: '启用动画效果', isEditable: true },
|
||||
{
|
||||
settingKey: 'primary_color',
|
||||
settingValue: JSON.stringify('#667eea'),
|
||||
settingType: 'string',
|
||||
category: 'appearance',
|
||||
description: '主题主色调',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'secondary_color',
|
||||
settingValue: JSON.stringify('#764ba2'),
|
||||
settingType: 'string',
|
||||
category: 'appearance',
|
||||
description: '主题辅助色调',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'compact_mode',
|
||||
settingValue: JSON.stringify(false),
|
||||
settingType: 'boolean',
|
||||
category: 'appearance',
|
||||
description: '紧凑模式',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'sidebar_collapsed',
|
||||
settingValue: JSON.stringify(false),
|
||||
settingType: 'boolean',
|
||||
category: 'appearance',
|
||||
description: '侧边栏默认折叠',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'table_row_height',
|
||||
settingValue: JSON.stringify('default'),
|
||||
settingType: 'string',
|
||||
category: 'appearance',
|
||||
description: '表格行高: small/default/middle/large',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'animation_enabled',
|
||||
settingValue: JSON.stringify(true),
|
||||
settingType: 'boolean',
|
||||
category: 'appearance',
|
||||
description: '启用动画效果',
|
||||
isEditable: true,
|
||||
},
|
||||
|
||||
// 关于页面
|
||||
{ settingKey: 'app_version', settingValue: JSON.stringify('1.0.0'), settingType: 'string', category: 'about', description: '应用版本', isEditable: false },
|
||||
{ settingKey: 'company_name', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '公司/组织名称', isEditable: true },
|
||||
{ settingKey: 'contact_email', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '联系邮箱', isEditable: true },
|
||||
{ settingKey: 'contact_phone', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '联系电话', isEditable: true },
|
||||
{ settingKey: 'company_address', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '公司地址', isEditable: true },
|
||||
{ settingKey: 'system_description', settingValue: JSON.stringify('机柜管理系统 - 专业的数据中心设备管理解决方案'), settingType: 'string', category: 'about', description: '系统描述', isEditable: true },
|
||||
{ settingKey: 'privacy_policy', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '隐私政策URL', isEditable: true },
|
||||
{ settingKey: 'terms_of_service', settingValue: JSON.stringify(''), settingType: 'string', category: 'about', description: '服务条款URL', isEditable: true },
|
||||
{
|
||||
settingKey: 'app_version',
|
||||
settingValue: JSON.stringify('1.0.0'),
|
||||
settingType: 'string',
|
||||
category: 'about',
|
||||
description: '应用版本',
|
||||
isEditable: false,
|
||||
},
|
||||
{
|
||||
settingKey: 'company_name',
|
||||
settingValue: JSON.stringify(''),
|
||||
settingType: 'string',
|
||||
category: 'about',
|
||||
description: '公司/组织名称',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'contact_email',
|
||||
settingValue: JSON.stringify(''),
|
||||
settingType: 'string',
|
||||
category: 'about',
|
||||
description: '联系邮箱',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'contact_phone',
|
||||
settingValue: JSON.stringify(''),
|
||||
settingType: 'string',
|
||||
category: 'about',
|
||||
description: '联系电话',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'company_address',
|
||||
settingValue: JSON.stringify(''),
|
||||
settingType: 'string',
|
||||
category: 'about',
|
||||
description: '公司地址',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'system_description',
|
||||
settingValue: JSON.stringify('机柜管理系统 - 专业的数据中心设备管理解决方案'),
|
||||
settingType: 'string',
|
||||
category: 'about',
|
||||
description: '系统描述',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'privacy_policy',
|
||||
settingValue: JSON.stringify(''),
|
||||
settingType: 'string',
|
||||
category: 'about',
|
||||
description: '隐私政策URL',
|
||||
isEditable: true,
|
||||
},
|
||||
{
|
||||
settingKey: 'terms_of_service',
|
||||
settingValue: JSON.stringify(''),
|
||||
settingType: 'string',
|
||||
category: 'about',
|
||||
description: '服务条款URL',
|
||||
isEditable: true,
|
||||
},
|
||||
];
|
||||
|
||||
async function initDatabase() {
|
||||
@@ -65,29 +226,29 @@ async function initDatabase() {
|
||||
|
||||
// 同步所有模型(按依赖顺序)
|
||||
console.log('开始同步模型...');
|
||||
|
||||
|
||||
// 基础模型
|
||||
await User.sync({ alter: true });
|
||||
await Room.sync({ alter: true });
|
||||
await Rack.sync({ alter: true });
|
||||
await Device.sync({ alter: true });
|
||||
await FaultCategory.sync({ alter: true });
|
||||
|
||||
|
||||
// 耗材相关模型
|
||||
await ConsumableCategory.sync({ alter: true });
|
||||
await Consumable.sync({ alter: true });
|
||||
await ConsumableLog.sync({ alter: true });
|
||||
await ConsumableRecord.sync({ alter: true });
|
||||
await ConsumableLogArchive.sync({ alter: true });
|
||||
|
||||
|
||||
// 盘点相关模型
|
||||
await InventoryPlan.sync({ alter: true });
|
||||
await InventoryTask.sync({ alter: true });
|
||||
await InventoryRecord.sync({ alter: true });
|
||||
|
||||
|
||||
// 系统设置
|
||||
await SystemSetting.sync({ alter: true });
|
||||
|
||||
|
||||
console.log('数据库表结构同步完成');
|
||||
|
||||
// 初始化或更新系统设置
|
||||
|
||||
+134
-113
@@ -28,73 +28,73 @@ const migrations = [
|
||||
{
|
||||
name: 'v2.0 - 网卡和端口表',
|
||||
description: '创建 network_cards 表,为 device_ports 添加 nic_id 字段',
|
||||
migrate: migrateV2
|
||||
migrate: migrateV2,
|
||||
},
|
||||
{
|
||||
name: '用户表 pending 状态',
|
||||
description: '为用户表添加 pending 状态支持',
|
||||
migrate: migratePendingStatus
|
||||
migrate: migratePendingStatus,
|
||||
},
|
||||
{
|
||||
name: '耗材乐观锁',
|
||||
description: '为 consumables 表添加 version 字段',
|
||||
migrate: migrateConsumableVersion
|
||||
migrate: migrateConsumableVersion,
|
||||
},
|
||||
{
|
||||
name: '耗材操作日志表结构',
|
||||
description: '添加 isEditable、originalLogId 等修改记录字段',
|
||||
migrate: migrateConsumableLogs
|
||||
migrate: migrateConsumableLogs,
|
||||
},
|
||||
{
|
||||
name: '耗材日志解耦',
|
||||
description: '添加 isConsumableDeleted 和 consumableSnapshot 字段',
|
||||
migrate: migrateConsumableLogDecouple
|
||||
migrate: migrateConsumableLogDecouple,
|
||||
},
|
||||
{
|
||||
name: '移除日志外键约束',
|
||||
description: '移除 consumable_logs 表的外键约束,防止级联删除',
|
||||
migrate: removeConsumableLogFK
|
||||
migrate: removeConsumableLogFK,
|
||||
},
|
||||
{
|
||||
name: '耗材日志归档表',
|
||||
description: '创建 consumable_log_archives 归档表',
|
||||
migrate: migrateConsumableLogArchive
|
||||
migrate: migrateConsumableLogArchive,
|
||||
},
|
||||
{
|
||||
name: '耗材SN序列号字段',
|
||||
description: '为 consumables、consumable_records、consumable_logs 添加 snList 字段',
|
||||
migrate: migrateSnList
|
||||
migrate: migrateSnList,
|
||||
},
|
||||
{
|
||||
name: '设备型号字段可空',
|
||||
description: '将 devices 表 model 字段改为可空,支持非必填',
|
||||
migrate: migrateDeviceModelField
|
||||
migrate: migrateDeviceModelField,
|
||||
},
|
||||
{
|
||||
name: '设备字段配置同步',
|
||||
description: '同步前后端字段必填配置',
|
||||
migrate: migrateDeviceFieldsConfig
|
||||
migrate: migrateDeviceFieldsConfig,
|
||||
},
|
||||
{
|
||||
name: '设备表字段可空',
|
||||
description: '将设备表所有字段改为可空,由应用层验证控制',
|
||||
migrate: migrateDeviceFieldsNullable
|
||||
migrate: migrateDeviceFieldsNullable,
|
||||
},
|
||||
{
|
||||
name: '暂存设备自定义字段',
|
||||
description: '为 pending_devices 表添加 customFields 字段,支持自定义字段存储',
|
||||
migrate: migratePendingDeviceCustomFields
|
||||
migrate: migratePendingDeviceCustomFields,
|
||||
},
|
||||
{
|
||||
name: '空闲设备与业务关联',
|
||||
description: '创建 businesses、warehouses、device_business 表,为 devices 添加空闲设备字段',
|
||||
migrate: migrateIdleDeviceAndBusiness
|
||||
migrate: migrateIdleDeviceAndBusiness,
|
||||
},
|
||||
{
|
||||
name: '设备字段系统标记',
|
||||
description: '为 deviceFields 表添加 isSystem 字段,标记系统字段不可删除',
|
||||
migrate: migrateDeviceFieldsIsSystem
|
||||
}
|
||||
migrate: migrateDeviceFieldsIsSystem,
|
||||
},
|
||||
];
|
||||
|
||||
async function runMigrations() {
|
||||
@@ -149,25 +149,23 @@ async function runMigrations() {
|
||||
|
||||
async function getTableColumns(tableName) {
|
||||
const dialect = sequelize.getDialect();
|
||||
|
||||
|
||||
if (dialect === 'sqlite') {
|
||||
const tableInfo = await sequelize.query(
|
||||
`PRAGMA table_info(${tableName})`,
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
const tableInfo = await sequelize.query(`PRAGMA table_info(${tableName})`, {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
});
|
||||
return tableInfo.map(col => col.name);
|
||||
} else {
|
||||
const tableInfo = await sequelize.query(
|
||||
`SHOW COLUMNS FROM ${tableName}`,
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
const tableInfo = await sequelize.query(`SHOW COLUMNS FROM ${tableName}`, {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
});
|
||||
return tableInfo.map(col => col.Field);
|
||||
}
|
||||
}
|
||||
|
||||
async function tableExists(tableName) {
|
||||
const dialect = sequelize.getDialect();
|
||||
|
||||
|
||||
if (dialect === 'sqlite') {
|
||||
const tables = await sequelize.query(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
||||
@@ -175,22 +173,23 @@ async function tableExists(tableName) {
|
||||
);
|
||||
return tables.length > 0;
|
||||
} else {
|
||||
const tables = await sequelize.query(
|
||||
"SHOW TABLES LIKE ?",
|
||||
{ replacements: [tableName], type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
const tables = await sequelize.query('SHOW TABLES LIKE ?', {
|
||||
replacements: [tableName],
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
});
|
||||
return tables.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
async function addColumnIfNotExists(tableName, columnName, columnDef) {
|
||||
const columns = await getTableColumns(tableName);
|
||||
|
||||
|
||||
if (!columns.includes(columnName)) {
|
||||
const dialect = sequelize.getDialect();
|
||||
const sql = dialect === 'sqlite'
|
||||
? `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnDef}`
|
||||
: `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnDef}`;
|
||||
const sql =
|
||||
dialect === 'sqlite'
|
||||
? `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnDef}`
|
||||
: `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnDef}`;
|
||||
await sequelize.query(sql);
|
||||
console.log(` ${tableName} 表添加 ${columnName} 字段成功`);
|
||||
} else {
|
||||
@@ -210,32 +209,32 @@ async function migrateV2() {
|
||||
id: {
|
||||
type: sequelize.Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
autoIncrement: true,
|
||||
},
|
||||
nicId: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
unique: true,
|
||||
},
|
||||
name: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false
|
||||
allowNull: false,
|
||||
},
|
||||
macAddress: {
|
||||
type: sequelize.Sequelize.STRING
|
||||
type: sequelize.Sequelize.STRING,
|
||||
},
|
||||
ipAddress: {
|
||||
type: sequelize.Sequelize.STRING
|
||||
type: sequelize.Sequelize.STRING,
|
||||
},
|
||||
deviceId: {
|
||||
type: sequelize.Sequelize.STRING
|
||||
type: sequelize.Sequelize.STRING,
|
||||
},
|
||||
status: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
defaultValue: 'active'
|
||||
defaultValue: 'active',
|
||||
},
|
||||
createdAt: sequelize.Sequelize.DATE,
|
||||
updatedAt: sequelize.Sequelize.DATE
|
||||
updatedAt: sequelize.Sequelize.DATE,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -276,12 +275,11 @@ async function migrateConsumableLogDecouple() {
|
||||
|
||||
async function removeConsumableLogFK() {
|
||||
const dialect = sequelize.getDialect();
|
||||
|
||||
|
||||
if (dialect === 'sqlite') {
|
||||
const fks = await sequelize.query(
|
||||
`PRAGMA foreign_key_list(consumable_logs);`,
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
const fks = await sequelize.query(`PRAGMA foreign_key_list(consumable_logs);`, {
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
});
|
||||
|
||||
if (!fks || fks.length === 0) {
|
||||
return;
|
||||
@@ -327,7 +325,9 @@ async function removeConsumableLogFK() {
|
||||
await sequelize.query(`CREATE INDEX idx_logs_consumable_id ON consumable_logs(consumableId)`);
|
||||
await sequelize.query(`CREATE INDEX idx_logs_operation_type ON consumable_logs(operationType)`);
|
||||
await sequelize.query(`CREATE INDEX idx_logs_created_at ON consumable_logs(createdAt)`);
|
||||
await sequelize.query(`CREATE INDEX idx_logs_is_consumable_deleted ON consumable_logs(isConsumableDeleted)`);
|
||||
await sequelize.query(
|
||||
`CREATE INDEX idx_logs_is_consumable_deleted ON consumable_logs(isConsumableDeleted)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,80 +341,86 @@ async function migrateConsumableLogArchive() {
|
||||
id: {
|
||||
type: sequelize.Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
autoIncrement: true,
|
||||
},
|
||||
archiveId: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
unique: true,
|
||||
},
|
||||
consumableId: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false
|
||||
allowNull: false,
|
||||
},
|
||||
consumableName: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
allowNull: false
|
||||
allowNull: false,
|
||||
},
|
||||
consumableSnapshot: {
|
||||
type: sequelize.Sequelize.TEXT
|
||||
type: sequelize.Sequelize.TEXT,
|
||||
},
|
||||
totalOperations: {
|
||||
type: sequelize.Sequelize.INTEGER,
|
||||
defaultValue: 0
|
||||
defaultValue: 0,
|
||||
},
|
||||
firstOperationAt: {
|
||||
type: sequelize.Sequelize.DATE
|
||||
type: sequelize.Sequelize.DATE,
|
||||
},
|
||||
lastOperationAt: {
|
||||
type: sequelize.Sequelize.DATE
|
||||
type: sequelize.Sequelize.DATE,
|
||||
},
|
||||
totalInQuantity: {
|
||||
type: sequelize.Sequelize.INTEGER,
|
||||
defaultValue: 0
|
||||
defaultValue: 0,
|
||||
},
|
||||
totalOutQuantity: {
|
||||
type: sequelize.Sequelize.INTEGER,
|
||||
defaultValue: 0
|
||||
defaultValue: 0,
|
||||
},
|
||||
finalStock: {
|
||||
type: sequelize.Sequelize.INTEGER,
|
||||
defaultValue: 0
|
||||
defaultValue: 0,
|
||||
},
|
||||
deletedBy: {
|
||||
type: sequelize.Sequelize.STRING
|
||||
type: sequelize.Sequelize.STRING,
|
||||
},
|
||||
deletedAt: {
|
||||
type: sequelize.Sequelize.DATE
|
||||
type: sequelize.Sequelize.DATE,
|
||||
},
|
||||
deleteReason: {
|
||||
type: sequelize.Sequelize.STRING
|
||||
type: sequelize.Sequelize.STRING,
|
||||
},
|
||||
createdAt: {
|
||||
type: sequelize.Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: sequelize.Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
defaultValue: sequelize.Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
updatedAt: {
|
||||
type: sequelize.Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: sequelize.Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
}
|
||||
defaultValue: sequelize.Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
|
||||
if (dbDialect === 'sqlite') {
|
||||
await sequelize.query(`CREATE INDEX idx_archive_consumable_id ON consumable_log_archives(consumableId)`);
|
||||
await sequelize.query(`CREATE INDEX idx_archive_archive_id ON consumable_log_archives(archiveId)`);
|
||||
await sequelize.query(`CREATE INDEX idx_archive_deleted_at ON consumable_log_archives(deletedAt)`);
|
||||
await sequelize.query(
|
||||
`CREATE INDEX idx_archive_consumable_id ON consumable_log_archives(consumableId)`
|
||||
);
|
||||
await sequelize.query(
|
||||
`CREATE INDEX idx_archive_archive_id ON consumable_log_archives(archiveId)`
|
||||
);
|
||||
await sequelize.query(
|
||||
`CREATE INDEX idx_archive_deleted_at ON consumable_log_archives(deletedAt)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateSnList() {
|
||||
const tables = ['consumables', 'consumable_records', 'consumable_logs'];
|
||||
|
||||
|
||||
for (const table of tables) {
|
||||
if (await tableExists(table)) {
|
||||
const columnDef = dbDialect === 'sqlite' ? "TEXT DEFAULT '[]'" : "JSON";
|
||||
const columnDef = dbDialect === 'sqlite' ? "TEXT DEFAULT '[]'" : 'JSON';
|
||||
await addColumnIfNotExists(table, 'snList', columnDef);
|
||||
} else {
|
||||
console.log(` ${table} 表不存在,跳过`);
|
||||
@@ -429,11 +435,9 @@ async function migrateDeviceModelField() {
|
||||
}
|
||||
|
||||
const dialect = sequelize.getDialect();
|
||||
|
||||
|
||||
if (dialect === 'mysql') {
|
||||
await sequelize.query(
|
||||
'ALTER TABLE devices MODIFY COLUMN model VARCHAR(255) NULL'
|
||||
);
|
||||
await sequelize.query('ALTER TABLE devices MODIFY COLUMN model VARCHAR(255) NULL');
|
||||
console.log(' devices 表 model 字段已改为可空');
|
||||
} else if (dialect === 'sqlite') {
|
||||
const columns = await getTableColumns('devices');
|
||||
@@ -441,7 +445,7 @@ async function migrateDeviceModelField() {
|
||||
console.log(' model_old 字段已存在,跳过迁移');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await sequelize.query('ALTER TABLE devices RENAME COLUMN model TO model_old');
|
||||
await sequelize.query('ALTER TABLE devices ADD COLUMN model VARCHAR(255)');
|
||||
await sequelize.query('UPDATE devices SET model = model_old');
|
||||
@@ -452,14 +456,14 @@ async function migrateDeviceModelField() {
|
||||
|
||||
async function migrateDeviceFieldsConfig() {
|
||||
const DeviceField = require('../models/DeviceField');
|
||||
|
||||
|
||||
const updates = [
|
||||
{ fieldName: 'model', required: false },
|
||||
{ fieldName: 'powerConsumption', required: true },
|
||||
{ fieldName: 'purchaseDate', required: false },
|
||||
{ fieldName: 'warrantyExpiry', required: false },
|
||||
];
|
||||
|
||||
|
||||
for (const update of updates) {
|
||||
const field = await DeviceField.findOne({ where: { fieldName: update.fieldName } });
|
||||
if (field && field.required !== update.required) {
|
||||
@@ -475,20 +479,20 @@ async function migrateDeviceFieldsConfig() {
|
||||
|
||||
async function migrateDeviceFieldsNullable() {
|
||||
const dialect = sequelize.getDialect();
|
||||
|
||||
|
||||
if (dialect === 'mysql') {
|
||||
const alterCommands = [
|
||||
"ALTER TABLE devices MODIFY COLUMN name VARCHAR(255) NULL",
|
||||
"ALTER TABLE devices MODIFY COLUMN type VARCHAR(255) NULL",
|
||||
"ALTER TABLE devices MODIFY COLUMN model VARCHAR(255) NULL",
|
||||
"ALTER TABLE devices MODIFY COLUMN serialNumber VARCHAR(255) NULL",
|
||||
"ALTER TABLE devices MODIFY COLUMN rackId VARCHAR(255) NULL",
|
||||
"ALTER TABLE devices MODIFY COLUMN position INTEGER NULL",
|
||||
"ALTER TABLE devices MODIFY COLUMN height INTEGER NULL",
|
||||
"ALTER TABLE devices MODIFY COLUMN powerConsumption FLOAT NULL",
|
||||
"ALTER TABLE devices MODIFY COLUMN customFields JSON NULL"
|
||||
'ALTER TABLE devices MODIFY COLUMN name VARCHAR(255) NULL',
|
||||
'ALTER TABLE devices MODIFY COLUMN type VARCHAR(255) NULL',
|
||||
'ALTER TABLE devices MODIFY COLUMN model VARCHAR(255) NULL',
|
||||
'ALTER TABLE devices MODIFY COLUMN serialNumber VARCHAR(255) NULL',
|
||||
'ALTER TABLE devices MODIFY COLUMN rackId VARCHAR(255) NULL',
|
||||
'ALTER TABLE devices MODIFY COLUMN position INTEGER NULL',
|
||||
'ALTER TABLE devices MODIFY COLUMN height INTEGER NULL',
|
||||
'ALTER TABLE devices MODIFY COLUMN powerConsumption FLOAT NULL',
|
||||
'ALTER TABLE devices MODIFY COLUMN customFields JSON NULL',
|
||||
];
|
||||
|
||||
|
||||
for (const sql of alterCommands) {
|
||||
try {
|
||||
await sequelize.query(sql);
|
||||
@@ -499,21 +503,20 @@ async function migrateDeviceFieldsNullable() {
|
||||
}
|
||||
}
|
||||
console.log(' devices 表字段已改为可空');
|
||||
|
||||
} else if (dialect === 'sqlite') {
|
||||
const columns = await getTableColumns('devices');
|
||||
const hasNullableFlag = columns.includes('_nullable_migration_done');
|
||||
|
||||
|
||||
if (hasNullableFlag) {
|
||||
console.log(' 已完成可空迁移,跳过');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await sequelize.query('PRAGMA foreign_keys = OFF');
|
||||
|
||||
|
||||
try {
|
||||
await sequelize.query('DROP TABLE IF EXISTS devices_new');
|
||||
|
||||
|
||||
await sequelize.query(`
|
||||
CREATE TABLE devices_new (
|
||||
deviceId VARCHAR(255) PRIMARY KEY NOT NULL UNIQUE,
|
||||
@@ -536,7 +539,7 @@ async function migrateDeviceFieldsNullable() {
|
||||
_nullable_migration_done INTEGER DEFAULT 1
|
||||
)
|
||||
`);
|
||||
|
||||
|
||||
await sequelize.query(`
|
||||
INSERT INTO devices_new (
|
||||
deviceId, name, type, model, serialNumber, rackId, position, height,
|
||||
@@ -549,15 +552,15 @@ async function migrateDeviceFieldsNullable() {
|
||||
description, customFields, createdAt, updatedAt
|
||||
FROM devices
|
||||
`);
|
||||
|
||||
|
||||
await sequelize.query('DROP TABLE devices');
|
||||
await sequelize.query('ALTER TABLE devices_new RENAME TO devices');
|
||||
|
||||
|
||||
await sequelize.query('CREATE INDEX IF NOT EXISTS idx_devices_status ON devices(status)');
|
||||
await sequelize.query('CREATE INDEX IF NOT EXISTS idx_devices_type ON devices(type)');
|
||||
await sequelize.query('CREATE INDEX IF NOT EXISTS idx_devices_rackId ON devices(rackId)');
|
||||
await sequelize.query('CREATE INDEX IF NOT EXISTS idx_devices_name ON devices(name)');
|
||||
|
||||
|
||||
console.log(' devices 表字段已改为可空');
|
||||
} finally {
|
||||
await sequelize.query('PRAGMA foreign_keys = ON');
|
||||
@@ -571,7 +574,7 @@ async function migratePendingDeviceCustomFields() {
|
||||
return;
|
||||
}
|
||||
|
||||
const columnDef = dbDialect === 'sqlite' ? "JSON DEFAULT '{}'" : "JSON";
|
||||
const columnDef = dbDialect === 'sqlite' ? "JSON DEFAULT '{}'" : 'JSON';
|
||||
await addColumnIfNotExists('pending_devices', 'customFields', columnDef);
|
||||
}
|
||||
|
||||
@@ -605,10 +608,9 @@ async function migrateDeviceFieldsIsSystem() {
|
||||
} else {
|
||||
const columns = await getTableColumns('deviceFields');
|
||||
if (!columns.includes('isSystem')) {
|
||||
await sequelize.query(
|
||||
"ALTER TABLE deviceFields ADD COLUMN isSystem BOOLEAN DEFAULT 0",
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
await sequelize.query('ALTER TABLE deviceFields ADD COLUMN isSystem BOOLEAN DEFAULT 0', {
|
||||
type: sequelize.QueryTypes.RAW,
|
||||
});
|
||||
console.log(' deviceFields 表添加 isSystem 字段成功');
|
||||
} else {
|
||||
console.log(' deviceFields 表 isSystem 字段已存在,跳过');
|
||||
@@ -616,16 +618,25 @@ async function migrateDeviceFieldsIsSystem() {
|
||||
}
|
||||
|
||||
const systemFields = [
|
||||
'deviceId', 'name', 'type', 'model', 'serialNumber',
|
||||
'rackId', 'position', 'height', 'powerConsumption',
|
||||
'status', 'purchaseDate', 'warrantyExpiry'
|
||||
'deviceId',
|
||||
'name',
|
||||
'type',
|
||||
'model',
|
||||
'serialNumber',
|
||||
'rackId',
|
||||
'position',
|
||||
'height',
|
||||
'powerConsumption',
|
||||
'status',
|
||||
'purchaseDate',
|
||||
'warrantyExpiry',
|
||||
];
|
||||
|
||||
for (const fieldName of systemFields) {
|
||||
await sequelize.query(
|
||||
`UPDATE deviceFields SET isSystem = 1 WHERE fieldName = ?`,
|
||||
{ replacements: [fieldName], type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
await sequelize.query(`UPDATE deviceFields SET isSystem = 1 WHERE fieldName = ?`, {
|
||||
replacements: [fieldName],
|
||||
type: sequelize.QueryTypes.RAW,
|
||||
});
|
||||
console.log(` 标记系统字段: ${fieldName}`);
|
||||
}
|
||||
|
||||
@@ -643,14 +654,19 @@ async function migrateIdleDeviceAndBusiness() {
|
||||
try {
|
||||
if (!(await tableExists('businesses'))) {
|
||||
await queryInterface.createTable('businesses', {
|
||||
businessId: { type: sequelize.Sequelize.STRING, primaryKey: true, allowNull: false, unique: true },
|
||||
businessId: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
name: { type: sequelize.Sequelize.STRING, allowNull: false },
|
||||
description: { type: sequelize.Sequelize.TEXT },
|
||||
status: { type: sequelize.Sequelize.ENUM('active', 'offline'), defaultValue: 'active' },
|
||||
offlineDate: { type: sequelize.Sequelize.DATE },
|
||||
offlineReason: { type: sequelize.Sequelize.STRING },
|
||||
createdAt: { type: sequelize.Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: sequelize.Sequelize.DATE, allowNull: false }
|
||||
updatedAt: { type: sequelize.Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
console.log(' businesses 表创建成功');
|
||||
} else {
|
||||
@@ -659,14 +675,19 @@ async function migrateIdleDeviceAndBusiness() {
|
||||
|
||||
if (!(await tableExists('warehouses'))) {
|
||||
await queryInterface.createTable('warehouses', {
|
||||
warehouseId: { type: sequelize.Sequelize.STRING, primaryKey: true, allowNull: false, unique: true },
|
||||
warehouseId: {
|
||||
type: sequelize.Sequelize.STRING,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
name: { type: sequelize.Sequelize.STRING, allowNull: false },
|
||||
location: { type: sequelize.Sequelize.STRING },
|
||||
capacity: { type: sequelize.Sequelize.INTEGER, defaultValue: 100 },
|
||||
status: { type: sequelize.Sequelize.ENUM('active', 'inactive'), defaultValue: 'active' },
|
||||
description: { type: sequelize.Sequelize.TEXT },
|
||||
createdAt: { type: sequelize.Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: sequelize.Sequelize.DATE, allowNull: false }
|
||||
updatedAt: { type: sequelize.Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
console.log(' warehouses 表创建成功');
|
||||
} else {
|
||||
@@ -680,7 +701,7 @@ async function migrateIdleDeviceAndBusiness() {
|
||||
businessId: { type: sequelize.Sequelize.STRING, allowNull: false },
|
||||
isPrimary: { type: sequelize.Sequelize.BOOLEAN, defaultValue: false },
|
||||
createdAt: { type: sequelize.Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: sequelize.Sequelize.DATE, allowNull: false }
|
||||
updatedAt: { type: sequelize.Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
console.log(' device_business 表创建成功');
|
||||
} else {
|
||||
|
||||
+11
-11
@@ -3,10 +3,10 @@
|
||||
/**
|
||||
* 命令行恢复脚本
|
||||
* 用于独立执行数据恢复,支持跨环境迁移
|
||||
*
|
||||
*
|
||||
* 使用方法:
|
||||
* node scripts/restore.js <backup-file> [options]
|
||||
*
|
||||
*
|
||||
* 选项:
|
||||
* --skip-users 跳过用户数据恢复
|
||||
* --skip-files 跳过文件恢复
|
||||
@@ -76,7 +76,7 @@ async function runRestore() {
|
||||
console.log('========================================\n');
|
||||
|
||||
const backupFile = path.resolve(options.file);
|
||||
|
||||
|
||||
if (!fs.existsSync(backupFile)) {
|
||||
console.error(`错误: 备份文件不存在: ${backupFile}`);
|
||||
process.exit(1);
|
||||
@@ -84,7 +84,7 @@ async function runRestore() {
|
||||
|
||||
try {
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
|
||||
|
||||
const { restoreBackup, validateBackupFile } = require('../utils/backup');
|
||||
const { sequelize } = require('../db');
|
||||
|
||||
@@ -94,7 +94,7 @@ async function runRestore() {
|
||||
|
||||
console.log('验证备份文件...');
|
||||
const validation = await validateBackupFile(backupFile);
|
||||
|
||||
|
||||
if (!validation.valid) {
|
||||
console.error(`错误: 备份文件验证失败: ${validation.error}`);
|
||||
process.exit(1);
|
||||
@@ -124,17 +124,17 @@ async function runRestore() {
|
||||
}
|
||||
|
||||
console.log('开始恢复数据...\n');
|
||||
|
||||
|
||||
const result = await restoreBackup(backupFile, {
|
||||
overwriteExisting: options.overwrite,
|
||||
skipTables,
|
||||
skipFiles: options.skipFiles,
|
||||
onProgress: (tableName, status, count) => {
|
||||
const statusMap = {
|
||||
'restored': '✓ 已恢复',
|
||||
'skipped': '○ 已跳过',
|
||||
'empty': '- 无数据',
|
||||
'error': '✗ 错误',
|
||||
restored: '✓ 已恢复',
|
||||
skipped: '○ 已跳过',
|
||||
empty: '- 无数据',
|
||||
error: '✗ 错误',
|
||||
};
|
||||
const statusText = statusMap[status] || status;
|
||||
const countText = count ? ` (${count} 条)` : '';
|
||||
@@ -162,7 +162,7 @@ async function runRestore() {
|
||||
}
|
||||
|
||||
console.log('\n提示: 请重启后端服务以确保所有数据生效');
|
||||
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,26 +3,32 @@ const { sequelize } = require('../db');
|
||||
async function updateSystemFields() {
|
||||
try {
|
||||
console.log('开始调整系统字段标记...');
|
||||
|
||||
|
||||
// 核心系统字段(数据库必填字段,不可删除)
|
||||
const coreSystemFields = [
|
||||
'deviceId', 'name', 'type', 'model', 'serialNumber',
|
||||
'rackId', 'position', 'height', 'powerConsumption',
|
||||
'status', 'purchaseDate', 'warrantyExpiry'
|
||||
'deviceId',
|
||||
'name',
|
||||
'type',
|
||||
'model',
|
||||
'serialNumber',
|
||||
'rackId',
|
||||
'position',
|
||||
'height',
|
||||
'powerConsumption',
|
||||
'status',
|
||||
'purchaseDate',
|
||||
'warrantyExpiry',
|
||||
];
|
||||
|
||||
|
||||
// 可选字段(非系统字段,可删除)
|
||||
const optionalFields = [
|
||||
'ipAddress', 'description', 'owner', 'department', 'assetId', 'brand'
|
||||
];
|
||||
|
||||
const optionalFields = ['ipAddress', 'description', 'owner', 'department', 'assetId', 'brand'];
|
||||
|
||||
// 先将所有字段设为非系统字段
|
||||
await sequelize.query(
|
||||
`UPDATE deviceFields SET isSystem = 0`,
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
await sequelize.query(`UPDATE deviceFields SET isSystem = 0`, {
|
||||
type: sequelize.QueryTypes.RAW,
|
||||
});
|
||||
console.log('已重置所有字段为非系统字段');
|
||||
|
||||
|
||||
// 标记核心系统字段
|
||||
for (const fieldName of coreSystemFields) {
|
||||
await sequelize.query(
|
||||
@@ -31,7 +37,7 @@ async function updateSystemFields() {
|
||||
);
|
||||
console.log(`标记为核心系统字段: ${fieldName}`);
|
||||
}
|
||||
|
||||
|
||||
// 确保可选字段为非系统字段
|
||||
for (const fieldName of optionalFields) {
|
||||
await sequelize.query(
|
||||
@@ -40,11 +46,11 @@ async function updateSystemFields() {
|
||||
);
|
||||
console.log(`标记为可选字段: ${fieldName}`);
|
||||
}
|
||||
|
||||
|
||||
console.log('\n系统字段调整完成!');
|
||||
console.log('核心系统字段(不可删除):', coreSystemFields.join(', '));
|
||||
console.log('可选字段(可删除):', optionalFields.join(', '));
|
||||
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('更新失败:', error);
|
||||
|
||||
@@ -3,15 +3,25 @@ const { sequelize } = require('../db');
|
||||
async function updateSystemFields() {
|
||||
try {
|
||||
console.log('开始更新系统字段标记...');
|
||||
|
||||
|
||||
// 系统字段列表
|
||||
const systemFields = [
|
||||
'deviceId', 'name', 'type', 'model', 'serialNumber',
|
||||
'rackId', 'position', 'height', 'powerConsumption',
|
||||
'status', 'purchaseDate', 'warrantyExpiry',
|
||||
'ipAddress', 'description'
|
||||
'deviceId',
|
||||
'name',
|
||||
'type',
|
||||
'model',
|
||||
'serialNumber',
|
||||
'rackId',
|
||||
'position',
|
||||
'height',
|
||||
'powerConsumption',
|
||||
'status',
|
||||
'purchaseDate',
|
||||
'warrantyExpiry',
|
||||
'ipAddress',
|
||||
'description',
|
||||
];
|
||||
|
||||
|
||||
// 更新系统字段标记
|
||||
for (const fieldName of systemFields) {
|
||||
const [result] = await sequelize.query(
|
||||
@@ -20,7 +30,7 @@ async function updateSystemFields() {
|
||||
);
|
||||
console.log(`标记系统字段: ${fieldName}`);
|
||||
}
|
||||
|
||||
|
||||
console.log('系统字段标记更新完成');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user