/** * 示例数据种子脚本 * 为 云睿资产管理系统写入演示数据(幂等,可重复执行) * * 本地:node scripts/seed-example-data.js * Docker:docker exec yunrui_asset-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 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('连接数据库...'); await sequelize.authenticate(); await sequelize.sync(); 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, }, }); const [room2] = await Room.findOrCreate({ where: { name: '上海浦东二号机房' }, defaults: { location: '上海市浦东新区张江高科', area: 320, capacity: 60, status: 'active', description: '华东区域灾备机房,承载核心业务冗余部署。', gridRows: 6, gridCols: 10, }, }); console.log(`✓ 机房: ${room1.name}, ${room2.name}`); 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(`✓ 库房: ${warehouse.name}`); 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); process.exit(1); } } seedData();