Update 2026-06-23 10:27:47

This commit is contained in:
yi
2026-06-23 10:27:47 +08:00
parent d0761503b7
commit add1275d1c
17 changed files with 1041 additions and 635 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ COPY package.json ./
COPY package-lock.json* yarn.lock* ./
# 安装生产依赖(不安装 devDependencies
RUN npm install --omit=dev --ignore-scripts
RUN npm install --omit=dev
# 复制后端代码
COPY . .
+2 -1
View File
@@ -15,7 +15,8 @@
"test": "jest --runInBand",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:operation-logs": "jest tests/operationLog.model.test.js tests/operationLogger.test.js tests/operationLogs.api.test.js --runInBand"
"test:operation-logs": "jest tests/operationLog.model.test.js tests/operationLogger.test.js tests/operationLogs.api.test.js --runInBand",
"seed": "node scripts/seed-example-data.js"
},
"dependencies": {
"all": "^0.0.0",
+359 -35
View File
@@ -1,62 +1,386 @@
/**
* 示例数据种子脚本
* 为云睿资产管理系统添加初始示例数据
* 为 IDC 设备资产管理系统写入演示数据(幂等,可重复执行)
*
* 本地:node scripts/seed-example-data.js
* Dockerdocker exec idc_assest-backend node scripts/seed-example-data.js
*/
require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
const bcrypt = require('bcryptjs');
const { sequelize } = require('../db');
const Room = require('../models/Room');
const Rack = require('../models/Rack');
const Device = require('../models/Device');
const Business = require('../models/Business');
const DeviceBusiness = require('../models/DeviceBusiness');
const Warehouse = require('../models/Warehouse');
const User = require('../models/User');
const Role = require('../models/Role');
const { generateId } = require('../utils/idGenerator');
const UserRole = require('../models/UserRole');
const ConsumableCategory = require('../models/ConsumableCategory');
const Consumable = require('../models/Consumable');
const Ticket = require('../models/Ticket');
const SEED_MARKER_ROOM = '北京通州一号机房';
const DEMO_ADMIN = { username: 'admin', password: 'admin123', realName: '系统管理员', email: 'admin@example.com' };
const DEFAULT_ROLES = [
{
roleId: 'role_admin',
roleName: '管理员',
roleCode: 'admin',
description: '系统管理员,拥有所有权限',
permissions: ['*'],
status: 'active',
sort: 1,
},
{
roleId: 'role_operator',
roleName: '运维人员',
roleCode: 'operator',
description: '负责日常运维操作',
permissions: ['devices:read', 'devices:write', 'racks:read', 'rooms:read', 'consumables:read', 'consumables:write'],
status: 'active',
sort: 2,
},
{
roleId: 'role_viewer',
roleName: '只读用户',
roleCode: 'viewer',
description: '仅能查看数据',
permissions: ['devices:read', 'racks:read', 'rooms:read', 'consumables:read'],
status: 'active',
sort: 3,
},
];
async function upsertRole(roleData) {
const [role] = await Role.findOrCreate({ where: { roleCode: roleData.roleCode }, defaults: roleData });
if (role.roleId !== roleData.roleId) {
await role.update(roleData);
}
return role;
}
async function ensureAdminUser() {
for (const roleData of DEFAULT_ROLES) {
await upsertRole(roleData);
}
const adminRole = await Role.findOne({ where: { roleCode: 'admin' } });
const hashedPassword = await bcrypt.hash(DEMO_ADMIN.password, 10);
const [admin] = await User.findOrCreate({
where: { username: DEMO_ADMIN.username },
defaults: {
password: hashedPassword,
email: DEMO_ADMIN.email,
realName: DEMO_ADMIN.realName,
status: 'active',
},
});
const existingLink = await UserRole.findOne({ where: { UserId: admin.userId, RoleId: adminRole.roleId } });
if (!existingLink) {
await UserRole.create({ UserId: admin.userId, RoleId: adminRole.roleId });
}
return admin;
}
async function seedData() {
try {
console.log('开始同步数据库结构...');
console.log('连接数据库...');
await sequelize.authenticate();
await sequelize.sync();
console.log('数据库结构同步成功');
// 1. 创建示例机房
console.log('正在创建示例机房...');
const room = await Room.create({
name: '北京通州一号机房',
location: '北京市通州区马驹桥',
area: 500,
capacity: 100,
description: '公司核心数据中心,具备双路供电及精密空调系统。'
const marker = await Room.findOne({ where: { name: SEED_MARKER_ROOM } });
if (marker && process.env.SEED_FORCE !== '1') {
console.log(`示例数据已存在(机房: ${SEED_MARKER_ROOM}),跳过写入。`);
console.log('如需强制重新写入,请设置环境变量 SEED_FORCE=1');
const admin = await ensureAdminUser();
console.log(`演示账号: ${DEMO_ADMIN.username} / ${DEMO_ADMIN.password} (${admin.userId})`);
process.exit(0);
}
console.log('开始写入示例数据...\n');
const admin = await ensureAdminUser();
console.log(`✓ 角色与管理员账号 (${DEMO_ADMIN.username} / ${DEMO_ADMIN.password})`);
const [room1] = await Room.findOrCreate({
where: { name: SEED_MARKER_ROOM },
defaults: {
location: '北京市通州区马驹桥',
area: 500,
capacity: 100,
status: 'active',
description: '公司核心数据中心,具备双路供电及精密空调系统。',
gridRows: 8,
gridCols: 12,
},
});
console.log(`已创建机房: ${room.name} (${room.roomId})`);
// 2. 创建示例业务
console.log('正在创建示例业务...');
const biz = await Business.create({
businessId: 'BIZ001',
name: '云端计算核心',
description: '负责核心云端计算资源的分配与管理。',
contact: '王经理',
phone: '13800000000'
const [room2] = await Room.findOrCreate({
where: { name: '上海浦东二号机房' },
defaults: {
location: '上海市浦东新区张江高科',
area: 320,
capacity: 60,
status: 'active',
description: '华东区域灾备机房,承载核心业务冗余部署。',
gridRows: 6,
gridCols: 10,
},
});
console.log(`已创建业务: ${biz.name}`);
console.log(`✓ 机房: ${room1.name}, ${room2.name}`);
// 3. 创建示例机柜
console.log('正在创建示例机柜...');
const rack = await Rack.create({
rackId: 'RACK001',
name: 'A-01',
roomId: room.roomId,
uCount: 42,
maxPower: 8000,
status: 'active',
description: '标准 42U 机柜,位于 A 排 01 位。'
const rackDefs = [
{ rackId: 'RCK_SEED_A01', name: 'A-01', roomId: room1.roomId, height: 42, maxPower: 8000, rowPos: 1, colPos: 1 },
{ rackId: 'RCK_SEED_A02', name: 'A-02', roomId: room1.roomId, height: 42, maxPower: 8000, rowPos: 1, colPos: 2 },
{ rackId: 'RCK_SEED_B01', name: 'B-01', roomId: room1.roomId, height: 42, maxPower: 6000, rowPos: 2, colPos: 1 },
{ rackId: 'RCK_SEED_C01', name: 'C-01', roomId: room2.roomId, height: 42, maxPower: 8000, rowPos: 1, colPos: 1 },
];
const racks = {};
for (const def of rackDefs) {
const [rack] = await Rack.findOrCreate({ where: { rackId: def.rackId }, defaults: def });
racks[def.name] = rack;
}
console.log(`✓ 机柜: ${rackDefs.map(r => r.name).join(', ')}`);
const businessDefs = [
{ businessId: 'BIZ_SEED_001', name: '云端计算核心', description: '负责核心云端计算资源的分配与管理。' },
{ businessId: 'BIZ_SEED_002', name: '企业办公平台', description: 'OA、邮件、协作等企业内部应用。' },
{ businessId: 'BIZ_SEED_003', name: '数据分析服务', description: '大数据分析与报表平台。' },
];
const businesses = {};
for (const def of businessDefs) {
const [biz] = await Business.findOrCreate({ where: { businessId: def.businessId }, defaults: def });
businesses[def.businessId] = biz;
}
console.log(`✓ 业务: ${businessDefs.map(b => b.name).join(', ')}`);
const [warehouse] = await Warehouse.findOrCreate({
where: { name: '通州备件库房' },
defaults: {
location: '北京通州一号机房 B 区',
capacity: 200,
status: 'active',
description: '存放待上架及退役备件设备。',
},
});
console.log(`已创建机柜: ${rack.name}`);
console.log(`✓ 库房: ${warehouse.name}`);
console.log('\n示例数据加载成功!');
const deviceDefs = [
{
deviceId: 'DEV_SEED_001',
name: 'Web-01',
type: 'server',
model: 'Dell PowerEdge R750',
serialNumber: 'SN-DELL-R750-001',
rackId: racks['A-01'].rackId,
position: 1,
height: 2,
powerConsumption: 450,
status: 'online',
ipAddress: '10.10.1.101',
description: '核心 Web 应用服务器',
businessId: 'BIZ_SEED_001',
},
{
deviceId: 'DEV_SEED_002',
name: 'DB-01',
type: 'server',
model: 'Dell PowerEdge R750',
serialNumber: 'SN-DELL-R750-002',
rackId: racks['A-01'].rackId,
position: 3,
height: 2,
powerConsumption: 520,
status: 'online',
ipAddress: '10.10.1.102',
description: 'MySQL 主库',
businessId: 'BIZ_SEED_001',
},
{
deviceId: 'DEV_SEED_003',
name: 'SW-Core-01',
type: 'switch',
model: 'Huawei CE6857',
serialNumber: 'SN-HW-CE6857-001',
rackId: racks['A-01'].rackId,
position: 40,
height: 1,
powerConsumption: 180,
status: 'online',
ipAddress: '10.10.0.1',
description: '核心交换机',
businessId: 'BIZ_SEED_001',
},
{
deviceId: 'DEV_SEED_004',
name: 'OA-App-01',
type: 'server',
model: 'Lenovo SR650',
serialNumber: 'SN-LENOVO-SR650-001',
rackId: racks['A-02'].rackId,
position: 5,
height: 2,
powerConsumption: 380,
status: 'online',
ipAddress: '10.20.1.50',
description: 'OA 应用服务器',
businessId: 'BIZ_SEED_002',
},
{
deviceId: 'DEV_SEED_005',
name: 'BI-Node-01',
type: 'server',
model: 'HPE ProLiant DL380',
serialNumber: 'SN-HPE-DL380-001',
rackId: racks['B-01'].rackId,
position: 10,
height: 2,
powerConsumption: 490,
status: 'maintenance',
ipAddress: '10.30.1.10',
description: '数据分析计算节点',
businessId: 'BIZ_SEED_003',
},
{
deviceId: 'DEV_SEED_006',
name: 'DR-Storage-01',
type: 'storage',
model: 'NetApp FAS2750',
serialNumber: 'SN-NETAPP-FAS2750-001',
rackId: racks['C-01'].rackId,
position: 20,
height: 4,
powerConsumption: 600,
status: 'online',
ipAddress: '10.40.1.20',
description: '灾备存储阵列',
businessId: 'BIZ_SEED_001',
},
{
deviceId: 'DEV_SEED_007',
name: 'Spare-R750',
type: 'server',
model: 'Dell PowerEdge R750',
serialNumber: 'SN-DELL-R750-SPARE',
warehouseId: warehouse.warehouseId,
sourceType: 'warehouse',
height: 2,
powerConsumption: 0,
status: 'offline',
description: '待上架备用服务器',
},
];
const devices = {};
for (const def of deviceDefs) {
const { businessId, ...deviceData } = def;
const [device] = await Device.findOrCreate({ where: { deviceId: def.deviceId }, defaults: deviceData });
devices[def.deviceId] = device;
if (businessId) {
await DeviceBusiness.findOrCreate({
where: { deviceId: def.deviceId, businessId },
defaults: { isPrimary: true },
});
}
}
console.log(`✓ 设备: ${deviceDefs.length} 台(含 1 台库房备件)`);
const consumableCategories = [
{ name: '线缆', description: '网络与电源线缆', sortOrder: 1 },
{ name: '光模块', description: 'SFP/SFP+/QSFP 光模块', sortOrder: 2 },
{ name: '硬盘', description: '企业级硬盘与 SSD', sortOrder: 3 },
];
for (const cat of consumableCategories) {
await ConsumableCategory.findOrCreate({ where: { name: cat.name }, defaults: cat });
}
const consumableDefs = [
{ consumableId: 'CON_SEED_001', name: '六类网线 3m', category: '线缆', unit: '根', currentStock: 120, minStock: 20, unitPrice: 15.0, location: '通州备件库房 A 架' },
{ consumableId: 'CON_SEED_002', name: 'SFP+ 10G 光模块', category: '光模块', unit: '个', currentStock: 32, minStock: 10, unitPrice: 280.0, location: '通州备件库房 B 架' },
{ consumableId: 'CON_SEED_003', name: '1.92TB SSD', category: '硬盘', unit: '块', currentStock: 8, minStock: 5, unitPrice: 1800.0, location: '通州备件库房 C 架' },
];
for (const def of consumableDefs) {
await Consumable.findOrCreate({ where: { consumableId: def.consumableId }, defaults: def });
}
console.log(`✓ 耗材: ${consumableDefs.length}`);
const ticketDefs = [
{
ticketId: 'TKT_SEED_001',
title: 'DB-01 磁盘 IO 延迟偏高',
deviceId: 'DEV_SEED_002',
deviceName: 'DB-01',
deviceModel: 'Dell PowerEdge R750',
serialNumber: 'SN-DELL-R750-002',
faultCategory: '性能问题',
priority: 'high',
status: 'in_progress',
description: '数据库主库磁盘 IO 等待时间持续超过 50ms,需排查存储性能。',
reporterId: admin.userId,
reporterName: admin.realName,
assigneeId: admin.userId,
assigneeName: admin.realName,
location: '北京通州一号机房 / A-01',
},
{
ticketId: 'TKT_SEED_002',
title: 'BI-Node-01 计划维护',
deviceId: 'DEV_SEED_005',
deviceName: 'BI-Node-01',
deviceModel: 'HPE ProLiant DL380',
serialNumber: 'SN-HPE-DL380-001',
faultCategory: '例行维护',
priority: 'low',
status: 'pending',
description: '月度固件升级与内存巡检。',
reporterId: admin.userId,
reporterName: admin.realName,
location: '北京通州一号机房 / B-01',
},
{
ticketId: 'TKT_SEED_003',
title: '核心交换机端口异常',
deviceId: 'DEV_SEED_003',
deviceName: 'SW-Core-01',
deviceModel: 'Huawei CE6857',
serialNumber: 'SN-HW-CE6857-001',
faultCategory: '网络故障',
priority: 'critical',
status: 'completed',
description: 'Gi0/0/24 端口频繁 flapping,已更换光模块。',
reporterId: admin.userId,
reporterName: admin.realName,
assigneeId: admin.userId,
assigneeName: admin.realName,
location: '北京通州一号机房 / A-01',
resolution: '更换 SFP+ 光模块后端口恢复正常。',
completionDate: new Date(),
},
];
for (const def of ticketDefs) {
await Ticket.findOrCreate({ where: { ticketId: def.ticketId }, defaults: def });
}
console.log(`✓ 工单: ${ticketDefs.length}`);
console.log('\n示例数据写入完成!');
console.log('────────────────────────────────────');
console.log(`登录账号: ${DEMO_ADMIN.username}`);
console.log(`登录密码: ${DEMO_ADMIN.password}`);
console.log(`前端地址: http://localhost:${process.env.FRONTEND_PORT || 12000}`);
console.log('────────────────────────────────────');
process.exit(0);
} catch (error) {
console.error('示例数据加载失败:', error);
console.error('示例数据写入失败:', error);
process.exit(1);
}
}
+3 -3
View File
@@ -168,10 +168,10 @@ const logger = createLogger({
],
});
if (process.env.NODE_ENV !== 'production') {
if (process.env.NODE_ENV !== 'production' || process.env.CONSOLE_LOG === 'true') {
logger.add(new transports.Console({
format: consoleFormat,
level: 'debug',
format: process.env.NODE_ENV === 'production' ? jsonFormat : consoleFormat,
level: LOG_LEVEL,
}));
}