feat: 添加操作日志、危险操作确认和业务关联功能
1. 新增操作日志记录功能,记录关键操作 2. 实现危险操作确认对话框,防止误删 3. 添加业务和库房管理模块 4. 支持设备标记为空闲状态 5. 完善API文档和健康检查 6. 优化前端删除操作的确认流程 7. 添加Swagger API文档支持 8. 实现设备与业务的关联功能 9. 改进设备模型,添加空闲相关字段 10. 优化用户、角色管理操作日志
This commit is contained in:
@@ -0,0 +1,862 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Op } = require('sequelize');
|
||||
const Device = require('../models/Device');
|
||||
const Rack = require('../models/Rack');
|
||||
const Room = require('../models/Room');
|
||||
const { logDeviceOperation } = require('../utils/operationLogger');
|
||||
|
||||
async function generateIdleDeviceId() {
|
||||
const devices = await Device.findAll({
|
||||
where: {
|
||||
deviceId: {
|
||||
[Op.like]: 'DEV%'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let maxNumber = 0;
|
||||
devices.forEach(device => {
|
||||
const match = device.deviceId.match(/^DEV(\d+)$/);
|
||||
if (match) {
|
||||
const num = parseInt(match[1], 10);
|
||||
if (num > maxNumber) {
|
||||
maxNumber = num;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const newNumber = maxNumber + 1;
|
||||
return `DEV${String(newNumber).padStart(4, '0')}`;
|
||||
}
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { keyword, sourceType, idleReason, page = 1, pageSize = 10 } = req.query;
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
const where = { isIdle: true };
|
||||
|
||||
if (keyword) {
|
||||
where[Op.or] = [
|
||||
{ deviceId: { [Op.like]: `%${keyword}%` } },
|
||||
{ name: { [Op.like]: `%${keyword}%` } },
|
||||
{ serialNumber: { [Op.like]: `%${keyword}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
if (sourceType && sourceType !== 'all') {
|
||||
where.sourceType = sourceType;
|
||||
}
|
||||
|
||||
if (idleReason) {
|
||||
where.idleReason = { [Op.like]: `%${idleReason}%` };
|
||||
}
|
||||
|
||||
const { count, rows } = await Device.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: Rack,
|
||||
attributes: ['rackId', 'name', 'roomId'],
|
||||
include: [{
|
||||
model: Room,
|
||||
attributes: ['roomId', 'name']
|
||||
}]
|
||||
}
|
||||
],
|
||||
offset: parseInt(offset),
|
||||
limit: parseInt(pageSize),
|
||||
order: [['idleDate', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
total: count,
|
||||
idleDevices: rows,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取空闲设备列表失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:deviceId', async (req, res) => {
|
||||
try {
|
||||
const device = await Device.findOne({
|
||||
where: { deviceId: req.params.deviceId, isIdle: true },
|
||||
include: [
|
||||
{
|
||||
model: Rack,
|
||||
attributes: ['rackId', 'name', 'roomId'],
|
||||
include: [{
|
||||
model: Room,
|
||||
attributes: ['roomId', 'name']
|
||||
}]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({ error: '空闲设备不存在' });
|
||||
}
|
||||
|
||||
res.json(device);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { name, type, model, serialNumber, powerConsumption, idleReason, warehouseId, description, rackId, position } = req.body;
|
||||
|
||||
let { deviceId } = req.body;
|
||||
|
||||
if (!deviceId || deviceId.trim() === '') {
|
||||
deviceId = await generateIdleDeviceId();
|
||||
}
|
||||
|
||||
const existingDevice = await Device.findByPk(deviceId);
|
||||
if (existingDevice) {
|
||||
return res.status(400).json({ error: '设备ID已存在' });
|
||||
}
|
||||
|
||||
if (rackId) {
|
||||
const rack = await Rack.findByPk(rackId);
|
||||
if (!rack) {
|
||||
return res.status(404).json({ error: '机柜不存在' });
|
||||
}
|
||||
}
|
||||
|
||||
const device = await Device.create({
|
||||
deviceId,
|
||||
name: name || '',
|
||||
type: type || 'other',
|
||||
model: model || '',
|
||||
serialNumber: serialNumber || '',
|
||||
powerConsumption: powerConsumption || 0,
|
||||
status: 'offline',
|
||||
isIdle: true,
|
||||
idleDate: new Date(),
|
||||
idleReason: idleReason || '',
|
||||
warehouseId: warehouseId || null,
|
||||
rackId: rackId || null,
|
||||
position: position || null,
|
||||
sourceType: warehouseId ? 'warehouse' : (rackId ? 'rack' : 'rack'),
|
||||
description: description || ''
|
||||
});
|
||||
|
||||
await logDeviceOperation('create', `新增空闲设备【${device.name || deviceId}】`, {
|
||||
targetId: device.deviceId,
|
||||
targetName: device.name,
|
||||
afterState: device.toJSON(),
|
||||
req,
|
||||
metadata: { sourceType: device.sourceType, type: 'idle_device_create' }
|
||||
});
|
||||
|
||||
res.status(201).json(device);
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/from-device/:deviceId', async (req, res) => {
|
||||
const t = await require('../db').sequelize.transaction();
|
||||
try {
|
||||
const { idleReason } = req.body;
|
||||
const { deviceId } = req.params;
|
||||
|
||||
const device = await Device.findByPk(deviceId, { transaction: t });
|
||||
if (!device) {
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '设备不存在' });
|
||||
}
|
||||
|
||||
if (device.isIdle) {
|
||||
await t.rollback();
|
||||
return res.status(400).json({ error: '设备已经标记为空闲设备' });
|
||||
}
|
||||
|
||||
await device.update({
|
||||
isIdle: true,
|
||||
idleDate: new Date(),
|
||||
idleReason: idleReason || `从设备管理转入`,
|
||||
sourceType: 'rack'
|
||||
}, { transaction: t });
|
||||
|
||||
await t.commit();
|
||||
|
||||
await logDeviceOperation('to_idle', `设备【${device.name}】转入空闲设备`, {
|
||||
targetId: device.deviceId,
|
||||
targetName: device.name,
|
||||
beforeState: { ...device.toJSON(), isIdle: false },
|
||||
afterState: { ...device.toJSON(), isIdle: true },
|
||||
req,
|
||||
metadata: { idleReason, type: 'device_to_idle' }
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: '设备已转入空闲设备',
|
||||
device: device.toJSON()
|
||||
});
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
console.error('设备转入空闲设备失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/batch-from-devices', async (req, res) => {
|
||||
const t = await require('../db').sequelize.transaction();
|
||||
try {
|
||||
const { deviceIds, idleReason } = req.body;
|
||||
|
||||
if (!deviceIds || !Array.isArray(deviceIds) || deviceIds.length === 0) {
|
||||
await t.rollback();
|
||||
return res.status(400).json({ error: '请提供有效的设备ID列表' });
|
||||
}
|
||||
|
||||
const devices = await Device.findAll({
|
||||
where: { deviceId: { [Op.in]: deviceIds } },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
const notIdleDevices = devices.filter(d => !d.isIdle);
|
||||
const alreadyIdleDevices = devices.filter(d => d.isIdle);
|
||||
|
||||
if (notIdleDevices.length > 0) {
|
||||
await Device.update(
|
||||
{
|
||||
isIdle: true,
|
||||
idleDate: new Date(),
|
||||
idleReason: idleReason || `批量转入`
|
||||
},
|
||||
{
|
||||
where: { deviceId: { [Op.in]: notIdleDevices.map(d => d.deviceId) } },
|
||||
transaction: t
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await t.commit();
|
||||
|
||||
await logDeviceOperation('batch_to_idle', `批量将 ${notIdleDevices.length} 台设备转入空闲设备`, {
|
||||
targetId: deviceIds.join(','),
|
||||
targetName: `${notIdleDevices.length}台设备`,
|
||||
req,
|
||||
metadata: { idleReason, type: 'batch_device_to_idle' }
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: `成功将 ${notIdleDevices.length} 台设备转入空闲设备`,
|
||||
total: devices.length,
|
||||
updated: notIdleDevices.length,
|
||||
skipped: alreadyIdleDevices.length
|
||||
});
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
console.error('批量转入空闲设备失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/batch-restore', async (req, res) => {
|
||||
const t = await require('../db').sequelize.transaction();
|
||||
try {
|
||||
const { devices } = req.body;
|
||||
|
||||
console.log('========== batch-restore 开始 ==========');
|
||||
console.log('原始请求 body:', JSON.stringify(req.body));
|
||||
console.log('devices 参数:', devices);
|
||||
|
||||
if (!devices || !Array.isArray(devices) || devices.length === 0) {
|
||||
await t.rollback();
|
||||
console.log('错误: devices 参数无效');
|
||||
return res.status(400).json({ error: '请提供有效的设备列表' });
|
||||
}
|
||||
|
||||
const deviceIds = devices.map(d => d.deviceId).filter(Boolean);
|
||||
console.log('提取的 deviceIds:', deviceIds);
|
||||
|
||||
if (deviceIds.length === 0) {
|
||||
await t.rollback();
|
||||
console.log('错误: deviceIds 为空');
|
||||
return res.status(400).json({ error: '设备ID不能为空' });
|
||||
}
|
||||
|
||||
console.log('开始查询设备,条件:', { deviceId: { [Op.in]: deviceIds }, isIdle: true });
|
||||
|
||||
const idleDevices = await Device.findAll({
|
||||
where: { deviceId: { [Op.in]: deviceIds }, isIdle: true },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
console.log('查询到的空闲设备数量:', idleDevices.length);
|
||||
if (idleDevices.length > 0) {
|
||||
console.log('查询到的设备ID:', idleDevices.map(d => d.deviceId));
|
||||
}
|
||||
|
||||
if (idleDevices.length === 0) {
|
||||
console.log('没有找到空闲设备,检查设备是否存在:');
|
||||
const allDevices = await Device.findAll({
|
||||
where: { deviceId: { [Op.in]: deviceIds } },
|
||||
transaction: t
|
||||
});
|
||||
console.log('设备表中存在的设备数量:', allDevices.length);
|
||||
if (allDevices.length > 0) {
|
||||
console.log('存在的设备及其 isIdle 状态:', allDevices.map(d => ({ deviceId: d.deviceId, isIdle: d.isIdle })));
|
||||
}
|
||||
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '没有找到空闲设备' });
|
||||
}
|
||||
|
||||
let restoredCount = 0;
|
||||
const results = [];
|
||||
|
||||
for (const device of idleDevices) {
|
||||
const deviceConfig = devices.find(d => d.deviceId === device.deviceId);
|
||||
if (!deviceConfig) continue;
|
||||
|
||||
const targetRackId = deviceConfig.targetRackId;
|
||||
const targetPosition = deviceConfig.targetPosition;
|
||||
|
||||
if (!targetRackId) {
|
||||
results.push({
|
||||
deviceId: device.deviceId,
|
||||
name: device.name,
|
||||
status: 'skipped',
|
||||
reason: '未指定目标机柜'
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetRack = await Rack.findByPk(targetRackId, { transaction: t });
|
||||
if (!targetRack) {
|
||||
results.push({
|
||||
deviceId: device.deviceId,
|
||||
name: device.name,
|
||||
status: 'failed',
|
||||
reason: '目标机柜不存在'
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const height = device.height || 1;
|
||||
const position = targetPosition || 1;
|
||||
|
||||
const checkResult = await checkPositionAvailable(targetRackId, position, height, null, t);
|
||||
if (!checkResult.available) {
|
||||
results.push({
|
||||
deviceId: device.deviceId,
|
||||
name: device.name,
|
||||
status: 'failed',
|
||||
reason: `U位${position}已被占用`
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
await device.update({
|
||||
isIdle: false,
|
||||
idleDate: null,
|
||||
idleReason: null,
|
||||
rackId: targetRackId,
|
||||
position: position,
|
||||
warehouseId: null,
|
||||
sourceType: 'rack',
|
||||
status: 'offline'
|
||||
}, { transaction: t });
|
||||
|
||||
await targetRack.update({
|
||||
currentPower: targetRack.currentPower + (device.powerConsumption || 0)
|
||||
}, { transaction: t });
|
||||
|
||||
restoredCount++;
|
||||
results.push({
|
||||
deviceId: device.deviceId,
|
||||
name: device.name,
|
||||
status: 'success',
|
||||
targetRack: targetRack.name,
|
||||
targetPosition: position
|
||||
});
|
||||
}
|
||||
|
||||
await t.commit();
|
||||
|
||||
const successCount = results.filter(r => r.status === 'success').length;
|
||||
const failedCount = results.filter(r => r.status === 'failed').length;
|
||||
const skippedCount = results.filter(r => r.status === 'skipped').length;
|
||||
|
||||
await logDeviceOperation('batch_restore', `批量上架 ${successCount} 台空闲设备`, {
|
||||
targetId: deviceIds.join(','),
|
||||
targetName: `${successCount}台设备`,
|
||||
req,
|
||||
metadata: { results, type: 'batch_idle_device_restore' }
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: `成功上架 ${successCount} 台设备`,
|
||||
total: idleDevices.length,
|
||||
restored: successCount,
|
||||
failed: failedCount,
|
||||
skipped: skippedCount,
|
||||
details: results
|
||||
});
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
console.error('批量上架设备失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:deviceId/shelve', async (req, res) => {
|
||||
const t = await require('../db').sequelize.transaction();
|
||||
try {
|
||||
const { deviceId } = req.params;
|
||||
const { name, type, model, serialNumber, height, powerConsumption, rackId, position, description } = req.body;
|
||||
|
||||
const device = await Device.findOne({
|
||||
where: { deviceId, isIdle: true },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '空闲设备不存在' });
|
||||
}
|
||||
|
||||
if (!rackId) {
|
||||
await t.rollback();
|
||||
return res.status(400).json({ error: '请选择目标机柜' });
|
||||
}
|
||||
|
||||
const targetRack = await Rack.findByPk(rackId, { transaction: t });
|
||||
if (!targetRack) {
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '目标机柜不存在' });
|
||||
}
|
||||
|
||||
const deviceHeight = height || device.height || 1;
|
||||
const positionCheck = await checkPositionAvailable(rackId, position, deviceHeight, deviceId, t);
|
||||
if (!positionCheck.available) {
|
||||
await t.rollback();
|
||||
return res.status(400).json({ error: positionCheck.reason });
|
||||
}
|
||||
|
||||
const beforeState = device.toJSON();
|
||||
|
||||
await device.update({
|
||||
name: name || device.name,
|
||||
type: type || device.type,
|
||||
model: model || device.model,
|
||||
serialNumber: serialNumber || device.serialNumber,
|
||||
height: deviceHeight,
|
||||
powerConsumption: powerConsumption || device.powerConsumption || 0,
|
||||
rackId: rackId,
|
||||
position: position,
|
||||
description: description || device.description,
|
||||
isIdle: false,
|
||||
idleDate: null,
|
||||
idleReason: null,
|
||||
warehouseId: null,
|
||||
sourceType: 'rack',
|
||||
status: 'running'
|
||||
}, { transaction: t });
|
||||
|
||||
await targetRack.update({
|
||||
currentPower: targetRack.currentPower + (powerConsumption || device.powerConsumption || 0)
|
||||
}, { transaction: t });
|
||||
|
||||
await t.commit();
|
||||
|
||||
const updatedDevice = await Device.findByPk(deviceId, {
|
||||
include: [
|
||||
{ model: Rack, include: [Room] }
|
||||
]
|
||||
});
|
||||
|
||||
await logDeviceOperation('shelve', `空闲设备【${device.name}】上架到机柜【${targetRack.name}】U${position}`, {
|
||||
targetId: device.deviceId,
|
||||
targetName: device.name,
|
||||
beforeState: { ...beforeState, isIdle: true },
|
||||
afterState: updatedDevice.toJSON(),
|
||||
req,
|
||||
metadata: { rackId, position, type: 'idle_device_shelve' }
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: '设备上架成功',
|
||||
device: updatedDevice
|
||||
});
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
console.error('设备上架失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:deviceId', async (req, res) => {
|
||||
try {
|
||||
const device = await Device.findOne({
|
||||
where: { deviceId: req.params.deviceId, isIdle: true }
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({ error: '空闲设备不存在' });
|
||||
}
|
||||
|
||||
const beforeState = device.toJSON();
|
||||
const allowedFields = ['name', 'type', 'model', 'idleReason', 'description', 'powerConsumption'];
|
||||
|
||||
allowedFields.forEach(field => {
|
||||
if (req.body[field] !== undefined) {
|
||||
device[field] = req.body[field];
|
||||
}
|
||||
});
|
||||
|
||||
if (req.body.warehouseId !== undefined) {
|
||||
device.warehouseId = req.body.warehouseId || null;
|
||||
device.sourceType = req.body.warehouseId ? 'warehouse' : 'rack';
|
||||
}
|
||||
|
||||
if (req.body.rackId !== undefined) {
|
||||
if (req.body.rackId) {
|
||||
const rack = await Rack.findByPk(req.body.rackId);
|
||||
if (!rack) {
|
||||
return res.status(404).json({ error: '机柜不存在' });
|
||||
}
|
||||
}
|
||||
device.rackId = req.body.rackId || null;
|
||||
if (!req.body.warehouseId) {
|
||||
device.sourceType = 'rack';
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.position !== undefined) {
|
||||
device.position = req.body.position || null;
|
||||
}
|
||||
|
||||
await device.save();
|
||||
|
||||
await logDeviceOperation('update', `更新空闲设备【${device.name}】`, {
|
||||
targetId: device.deviceId,
|
||||
targetName: device.name,
|
||||
beforeState,
|
||||
afterState: device.toJSON(),
|
||||
req,
|
||||
metadata: { type: 'idle_device_update' }
|
||||
});
|
||||
|
||||
res.json(device);
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:deviceId/restore', async (req, res) => {
|
||||
const t = await require('../db').sequelize.transaction();
|
||||
try {
|
||||
const { targetRackId, targetPosition } = req.body;
|
||||
const { deviceId } = req.params;
|
||||
|
||||
const device = await Device.findOne({
|
||||
where: { deviceId, isIdle: true },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '空闲设备不存在' });
|
||||
}
|
||||
|
||||
if (!targetRackId || !targetPosition) {
|
||||
await t.rollback();
|
||||
return res.status(400).json({ error: '恢复设备需要指定目标机柜和位置' });
|
||||
}
|
||||
|
||||
const targetRack = await Rack.findByPk(targetRackId, { transaction: t });
|
||||
if (!targetRack) {
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '目标机柜不存在' });
|
||||
}
|
||||
|
||||
const positionCheck = await checkPositionAvailable(targetRackId, targetPosition, device.height || 1, deviceId, t);
|
||||
if (!positionCheck.available) {
|
||||
await t.rollback();
|
||||
return res.status(400).json({ error: positionCheck.reason });
|
||||
}
|
||||
|
||||
await device.update({
|
||||
isIdle: false,
|
||||
idleDate: null,
|
||||
idleReason: null,
|
||||
rackId: targetRackId,
|
||||
position: targetPosition,
|
||||
warehouseId: null,
|
||||
sourceType: 'rack',
|
||||
status: 'offline'
|
||||
}, { transaction: t });
|
||||
|
||||
await targetRack.update({
|
||||
currentPower: targetRack.currentPower + (device.powerConsumption || 0)
|
||||
}, { transaction: t });
|
||||
|
||||
await t.commit();
|
||||
|
||||
const updatedDevice = await Device.findByPk(deviceId, {
|
||||
include: [
|
||||
{ model: Rack, include: [Room] }
|
||||
]
|
||||
});
|
||||
|
||||
await logDeviceOperation('restore', `空闲设备【${device.name}】恢复到机柜【${targetRack.name}】U${targetPosition}`, {
|
||||
targetId: device.deviceId,
|
||||
targetName: device.name,
|
||||
beforeState: { ...device.toJSON(), isIdle: true },
|
||||
afterState: updatedDevice.toJSON(),
|
||||
req,
|
||||
metadata: { targetRackId, targetPosition, type: 'idle_device_restore' }
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: '设备已恢复到设备管理',
|
||||
device: updatedDevice
|
||||
});
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
console.error('恢复设备失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/batch-restore', async (req, res) => {
|
||||
const t = await require('../db').sequelize.transaction();
|
||||
try {
|
||||
const { devices } = req.body;
|
||||
|
||||
console.log('========== batch-restore 开始 ==========');
|
||||
console.log('原始请求 body:', JSON.stringify(req.body));
|
||||
console.log('devices 参数:', devices);
|
||||
|
||||
if (!devices || !Array.isArray(devices) || devices.length === 0) {
|
||||
await t.rollback();
|
||||
console.log('错误: devices 参数无效');
|
||||
return res.status(400).json({ error: '请提供有效的设备列表' });
|
||||
}
|
||||
|
||||
const deviceIds = devices.map(d => d.deviceId).filter(Boolean);
|
||||
console.log('提取的 deviceIds:', deviceIds);
|
||||
console.log('deviceIds 类型:', typeof deviceIds, Array.isArray(deviceIds));
|
||||
|
||||
if (deviceIds.length === 0) {
|
||||
await t.rollback();
|
||||
console.log('错误: deviceIds 为空');
|
||||
return res.status(400).json({ error: '设备ID不能为空' });
|
||||
}
|
||||
|
||||
console.log('开始查询设备,条件:', { deviceId: { [Op.in]: deviceIds }, isIdle: true });
|
||||
|
||||
const idleDevices = await Device.findAll({
|
||||
where: { deviceId: { [Op.in]: deviceIds }, isIdle: true },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
console.log('查询到的空闲设备数量:', idleDevices.length);
|
||||
if (idleDevices.length > 0) {
|
||||
console.log('查询到的设备ID:', idleDevices.map(d => d.deviceId));
|
||||
}
|
||||
|
||||
if (idleDevices.length === 0) {
|
||||
console.log('没有找到空闲设备,检查设备是否存在:');
|
||||
const allDevices = await Device.findAll({
|
||||
where: { deviceId: { [Op.in]: deviceIds } },
|
||||
transaction: t
|
||||
});
|
||||
console.log('设备表中存在的设备数量:', allDevices.length);
|
||||
if (allDevices.length > 0) {
|
||||
console.log('存在的设备及其 isIdle 状态:', allDevices.map(d => ({ deviceId: d.deviceId, isIdle: d.isIdle })));
|
||||
}
|
||||
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '没有找到空闲设备' });
|
||||
}
|
||||
|
||||
let restoredCount = 0;
|
||||
const results = [];
|
||||
|
||||
for (const device of idleDevices) {
|
||||
const deviceConfig = devices.find(d => d.deviceId === device.deviceId);
|
||||
if (!deviceConfig) continue;
|
||||
|
||||
const targetRackId = deviceConfig.targetRackId;
|
||||
const targetPosition = deviceConfig.targetPosition;
|
||||
|
||||
if (!targetRackId) {
|
||||
results.push({
|
||||
deviceId: device.deviceId,
|
||||
name: device.name,
|
||||
status: 'skipped',
|
||||
reason: '未指定目标机柜'
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetRack = await Rack.findByPk(targetRackId, { transaction: t });
|
||||
if (!targetRack) {
|
||||
results.push({
|
||||
deviceId: device.deviceId,
|
||||
name: device.name,
|
||||
status: 'failed',
|
||||
reason: '目标机柜不存在'
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const height = device.height || 1;
|
||||
const position = targetPosition || 1;
|
||||
|
||||
const checkResult = await checkPositionAvailable(targetRackId, position, height, null, t);
|
||||
if (!checkResult.available) {
|
||||
results.push({
|
||||
deviceId: device.deviceId,
|
||||
name: device.name,
|
||||
status: 'failed',
|
||||
reason: `U位${position}已被占用`
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
await device.update({
|
||||
isIdle: false,
|
||||
idleDate: null,
|
||||
idleReason: null,
|
||||
rackId: targetRackId,
|
||||
position: position,
|
||||
warehouseId: null,
|
||||
sourceType: 'rack',
|
||||
status: 'offline'
|
||||
}, { transaction: t });
|
||||
|
||||
await targetRack.update({
|
||||
currentPower: targetRack.currentPower + (device.powerConsumption || 0)
|
||||
}, { transaction: t });
|
||||
|
||||
restoredCount++;
|
||||
results.push({
|
||||
deviceId: device.deviceId,
|
||||
name: device.name,
|
||||
status: 'success',
|
||||
targetRack: targetRack.name,
|
||||
targetPosition: position
|
||||
});
|
||||
}
|
||||
|
||||
await t.commit();
|
||||
|
||||
const successCount = results.filter(r => r.status === 'success').length;
|
||||
const failedCount = results.filter(r => r.status === 'failed').length;
|
||||
const skippedCount = results.filter(r => r.status === 'skipped').length;
|
||||
|
||||
await logDeviceOperation('batch_restore', `批量上架 ${successCount} 台空闲设备`, {
|
||||
targetId: deviceIds.join(','),
|
||||
targetName: `${successCount}台设备`,
|
||||
req,
|
||||
metadata: { results, type: 'batch_idle_device_restore' }
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: `成功上架 ${successCount} 台设备`,
|
||||
total: idleDevices.length,
|
||||
restored: successCount,
|
||||
failed: failedCount,
|
||||
skipped: skippedCount,
|
||||
details: results
|
||||
});
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
console.error('批量上架设备失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:deviceId', async (req, res) => {
|
||||
const t = await require('../db').sequelize.transaction();
|
||||
try {
|
||||
const device = await Device.findOne({
|
||||
where: { deviceId: req.params.deviceId, isIdle: true },
|
||||
transaction: t
|
||||
});
|
||||
|
||||
if (!device) {
|
||||
await t.rollback();
|
||||
return res.status(404).json({ error: '空闲设备不存在' });
|
||||
}
|
||||
|
||||
const beforeState = device.toJSON();
|
||||
|
||||
await device.destroy({ transaction: t });
|
||||
|
||||
await t.commit();
|
||||
|
||||
await logDeviceOperation('delete', `删除空闲设备【${device.name || device.deviceId}】`, {
|
||||
targetId: device.deviceId,
|
||||
targetName: device.name,
|
||||
beforeState,
|
||||
req,
|
||||
metadata: { type: 'idle_device_delete' }
|
||||
});
|
||||
|
||||
res.json({ message: '空闲设备删除成功' });
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
console.error('删除空闲设备失败:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
async function checkPositionAvailable(rackId, position, height, excludeDeviceId = null, transaction = null) {
|
||||
if (!position || position <= 0) {
|
||||
return { available: true, reason: null };
|
||||
}
|
||||
|
||||
const deviceHeight = height || 1;
|
||||
const startU = position;
|
||||
const endU = position + deviceHeight - 1;
|
||||
|
||||
const queryOptions = {
|
||||
where: {
|
||||
rackId: rackId,
|
||||
position: { [Op.ne]: null },
|
||||
isIdle: false
|
||||
},
|
||||
attributes: ['deviceId', 'position', 'height']
|
||||
};
|
||||
|
||||
if (transaction) {
|
||||
queryOptions.transaction = transaction;
|
||||
}
|
||||
|
||||
const existingDevices = await Device.findAll(queryOptions);
|
||||
|
||||
for (const d of existingDevices) {
|
||||
if (excludeDeviceId && d.deviceId === excludeDeviceId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existStart = d.position;
|
||||
const existEnd = d.position + (d.height || 1) - 1;
|
||||
|
||||
if (!(endU < existStart || startU > existEnd)) {
|
||||
return {
|
||||
available: false,
|
||||
reason: `U位冲突:机柜中已有设备 ${d.deviceId} 占用 U${existStart}${existEnd !== existStart ? '-' + existEnd : ''}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { available: true, reason: null };
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user