feat(设备管理): 添加U位冲突检测功能并优化服务重启逻辑
This commit is contained in:
+327
-12
@@ -33,6 +33,107 @@ Room.hasMany(Rack, { foreignKey: 'roomId' });
|
|||||||
|
|
||||||
const PREVIEW_COUNT = 20;
|
const PREVIEW_COUNT = 20;
|
||||||
|
|
||||||
|
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 }
|
||||||
|
},
|
||||||
|
attributes: ['deviceId', 'position', 'height']
|
||||||
|
};
|
||||||
|
|
||||||
|
if (transaction) {
|
||||||
|
queryOptions.transaction = transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingDevices = await Device.findAll(queryOptions);
|
||||||
|
|
||||||
|
for (const device of existingDevices) {
|
||||||
|
if (excludeDeviceId && device.deviceId === excludeDeviceId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existStart = device.position;
|
||||||
|
const existEnd = device.position + (device.height || 1) - 1;
|
||||||
|
|
||||||
|
if (!(endU < existStart || startU > existEnd)) {
|
||||||
|
return {
|
||||||
|
available: false,
|
||||||
|
reason: `U位冲突:机柜中已有设备 ${device.deviceId} 占用 U${existStart}${existEnd !== existStart ? '-' + existEnd : ''},与当前位置范围 U${startU}-U${endU} 冲突`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { available: true, reason: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkBatchPositions(rackId, devices, excludeDeviceIds = [], transaction = null) {
|
||||||
|
const conflicts = [];
|
||||||
|
const sortedDevices = [...devices].sort((a, b) => a.position - b.position);
|
||||||
|
|
||||||
|
for (let i = 0; i < sortedDevices.length; i++) {
|
||||||
|
const device = sortedDevices[i];
|
||||||
|
if (!device.position || device.position <= 0) continue;
|
||||||
|
|
||||||
|
const startU = device.position;
|
||||||
|
const endU = device.position + (device.height || 1) - 1;
|
||||||
|
|
||||||
|
for (let j = i + 1; j < sortedDevices.length; j++) {
|
||||||
|
const other = sortedDevices[j];
|
||||||
|
if (!other.position || other.position <= 0) continue;
|
||||||
|
|
||||||
|
const otherStart = other.position;
|
||||||
|
const otherEnd = other.position + (other.height || 1) - 1;
|
||||||
|
|
||||||
|
if (!(endU < otherStart || startU > otherEnd)) {
|
||||||
|
conflicts.push(`导入数据内部冲突:设备 ${device.deviceId || '新设备'}(U${startU}-U${endU}) 与 设备 ${other.deviceId || '新设备'}(U${otherStart}-U${otherEnd}) U位重叠`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryOptions = {
|
||||||
|
where: {
|
||||||
|
rackId: rackId,
|
||||||
|
position: { [Op.ne]: null }
|
||||||
|
},
|
||||||
|
attributes: ['deviceId', 'position', 'height']
|
||||||
|
};
|
||||||
|
|
||||||
|
if (transaction) {
|
||||||
|
queryOptions.transaction = transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingDevices = await Device.findAll(queryOptions);
|
||||||
|
|
||||||
|
for (const existing of existingDevices) {
|
||||||
|
if (excludeDeviceIds.includes(existing.deviceId)) continue;
|
||||||
|
|
||||||
|
const existStart = existing.position;
|
||||||
|
const existEnd = existing.position + (existing.height || 1) - 1;
|
||||||
|
|
||||||
|
for (const device of devices) {
|
||||||
|
if (!device.position || device.position <= 0) continue;
|
||||||
|
|
||||||
|
const startU = device.position;
|
||||||
|
const endU = device.position + (device.height || 1) - 1;
|
||||||
|
|
||||||
|
if (!(endU < existStart || startU > existEnd)) {
|
||||||
|
conflicts.push(`与已有设备冲突:机柜中已有设备 ${existing.deviceId} 占用 U${existStart}${existEnd !== existStart ? '-' + existEnd : ''},与导入设备 ${device.deviceId || '新设备'}(U${startU}-U${endU}) 冲突`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conflicts;
|
||||||
|
}
|
||||||
|
|
||||||
router.post('/import-preview', async (req, res) => {
|
router.post('/import-preview', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
if (!req.files || !req.files.csvFile) {
|
if (!req.files || !req.files.csvFile) {
|
||||||
@@ -89,6 +190,7 @@ router.post('/import-preview', async (req, res) => {
|
|||||||
const previewData = [];
|
const previewData = [];
|
||||||
const allDeviceIds = new Set();
|
const allDeviceIds = new Set();
|
||||||
const allSerialNumbers = new Set();
|
const allSerialNumbers = new Set();
|
||||||
|
const rackDevicesMap = new Map();
|
||||||
|
|
||||||
stats.total = results.length;
|
stats.total = results.length;
|
||||||
|
|
||||||
@@ -224,6 +326,21 @@ router.post('/import-preview', async (req, res) => {
|
|||||||
if (rowErrors.length === 0) {
|
if (rowErrors.length === 0) {
|
||||||
stats.valid++;
|
stats.valid++;
|
||||||
parsedRow._hasError = false;
|
parsedRow._hasError = false;
|
||||||
|
|
||||||
|
const rackId = rackLocationMap.get(`${roomName?.trim()}_${rackName?.trim()}`);
|
||||||
|
if (rackId && position) {
|
||||||
|
const posNum = parseInt(position);
|
||||||
|
const heightNum = parseInt(height) || 1;
|
||||||
|
if (!rackDevicesMap.has(rackId)) {
|
||||||
|
rackDevicesMap.set(rackId, []);
|
||||||
|
}
|
||||||
|
rackDevicesMap.get(rackId).push({
|
||||||
|
rowNum,
|
||||||
|
deviceId: getFieldValue('deviceId') || null,
|
||||||
|
position: posNum,
|
||||||
|
height: heightNum
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
stats.invalid++;
|
stats.invalid++;
|
||||||
parsedRow._hasError = true;
|
parsedRow._hasError = true;
|
||||||
@@ -255,6 +372,68 @@ router.post('/import-preview', async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const positionConflictRows = new Set();
|
||||||
|
|
||||||
|
for (const [rackId, devices] of rackDevicesMap) {
|
||||||
|
const existingDevices = await Device.findAll({
|
||||||
|
where: { rackId, position: { [Op.ne]: null } },
|
||||||
|
attributes: ['deviceId', 'position', 'height']
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const newDevice of devices) {
|
||||||
|
const startU = newDevice.position;
|
||||||
|
const endU = newDevice.position + newDevice.height - 1;
|
||||||
|
let hasConflict = false;
|
||||||
|
|
||||||
|
for (const existing of existingDevices) {
|
||||||
|
const existStart = existing.position;
|
||||||
|
const existEnd = existing.position + (existing.height || 1) - 1;
|
||||||
|
|
||||||
|
if (!(endU < existStart || startU > existEnd)) {
|
||||||
|
const conflictMsg = `U位冲突:机柜中已有设备 ${existing.deviceId} 占用 U${existStart}${existEnd !== existStart ? '-' + existEnd : ''},与第 ${newDevice.rowNum} 行设备(导入)占用 U${startU}${endU !== startU ? '-' + endU : ''} 冲突`;
|
||||||
|
stats.errors.push({ row: newDevice.rowNum, errors: [conflictMsg] });
|
||||||
|
hasConflict = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasConflict) {
|
||||||
|
for (const otherDevice of devices) {
|
||||||
|
if (otherDevice === newDevice) continue;
|
||||||
|
|
||||||
|
const otherStart = otherDevice.position;
|
||||||
|
const otherEnd = otherDevice.position + otherDevice.height - 1;
|
||||||
|
|
||||||
|
if (!(endU < otherStart || startU > otherEnd)) {
|
||||||
|
const conflictMsg = `U位冲突:第 ${newDevice.rowNum} 行设备与第 ${otherDevice.rowNum} 行设备在 U位上重叠 (U${startU}-U${endU} vs U${otherStart}-U${otherEnd})`;
|
||||||
|
stats.errors.push({ row: newDevice.rowNum, errors: [conflictMsg] });
|
||||||
|
hasConflict = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasConflict) {
|
||||||
|
positionConflictRows.add(newDevice.rowNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positionConflictRows.size > 0) {
|
||||||
|
stats.invalid += positionConflictRows.size;
|
||||||
|
stats.valid -= positionConflictRows.size;
|
||||||
|
|
||||||
|
for (const rowNum of positionConflictRows) {
|
||||||
|
const previewItem = previewData.find(item => item._rowNum === rowNum);
|
||||||
|
if (previewItem) {
|
||||||
|
previewItem._hasError = true;
|
||||||
|
const existingErrors = previewItem._errors || [];
|
||||||
|
const positionErrors = stats.errors.filter(e => e.row === rowNum).flatMap(e => e.errors);
|
||||||
|
previewItem._errors = [...existingErrors, ...positionErrors];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fieldList = deviceFields
|
const fieldList = deviceFields
|
||||||
.filter(field => field.visible && field.fieldName !== 'deviceId')
|
.filter(field => field.visible && field.fieldName !== 'deviceId')
|
||||||
.map(field => ({
|
.map(field => ({
|
||||||
@@ -431,14 +610,23 @@ router.post('/', validateBody(createDeviceSchema), async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const deviceData = { ...req.body };
|
const deviceData = { ...req.body };
|
||||||
|
|
||||||
// 如果没有提供deviceId或为空,则自动生成
|
|
||||||
if (!deviceData.deviceId || deviceData.deviceId.trim() === '') {
|
if (!deviceData.deviceId || deviceData.deviceId.trim() === '') {
|
||||||
deviceData.deviceId = await generateDeviceId();
|
deviceData.deviceId = await generateDeviceId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deviceData.rackId && deviceData.position) {
|
||||||
|
const positionCheck = await checkPositionAvailable(
|
||||||
|
deviceData.rackId,
|
||||||
|
deviceData.position,
|
||||||
|
deviceData.height
|
||||||
|
);
|
||||||
|
if (!positionCheck.available) {
|
||||||
|
return res.status(400).json({ error: positionCheck.reason });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const device = await Device.create(deviceData);
|
const device = await Device.create(deviceData);
|
||||||
|
|
||||||
// 更新机柜当前功率
|
|
||||||
const rack = await Rack.findByPk(deviceData.rackId);
|
const rack = await Rack.findByPk(deviceData.rackId);
|
||||||
if (rack) {
|
if (rack) {
|
||||||
await rack.update({
|
await rack.update({
|
||||||
@@ -977,6 +1165,74 @@ router.post('/import', async (req, res) => {
|
|||||||
stats.errors.push({ row: rowNum, error: error.message, data: row });
|
stats.errors.push({ row: rowNum, error: error.message, data: row });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (validDevices.length > 0) {
|
||||||
|
const rackDevicesMap = new Map();
|
||||||
|
for (const device of validDevices) {
|
||||||
|
if (device.rackId && device.position > 0) {
|
||||||
|
if (!rackDevicesMap.has(device.rackId)) {
|
||||||
|
rackDevicesMap.set(device.rackId, []);
|
||||||
|
}
|
||||||
|
rackDevicesMap.get(device.rackId).push(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [rackId, devices] of rackDevicesMap) {
|
||||||
|
const existingDevices = await Device.findAll({
|
||||||
|
where: { rackId, position: { [Op.ne]: null } },
|
||||||
|
attributes: ['deviceId', 'position', 'height'],
|
||||||
|
transaction: t
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const newDevice of devices) {
|
||||||
|
const startU = newDevice.position;
|
||||||
|
const endU = newDevice.position + newDevice.height - 1;
|
||||||
|
|
||||||
|
let hasConflict = false;
|
||||||
|
|
||||||
|
for (const existing of existingDevices) {
|
||||||
|
const existStart = existing.position;
|
||||||
|
const existEnd = existing.position + (existing.height || 1) - 1;
|
||||||
|
|
||||||
|
if (!(endU < existStart || startU > existEnd)) {
|
||||||
|
stats.failed++;
|
||||||
|
stats.errors.push({
|
||||||
|
row: 0,
|
||||||
|
error: `U位冲突:机柜中已有设备 ${existing.deviceId} 占用 U${existStart}${existEnd !== existStart ? '-' + existEnd : ''},与导入设备 ${newDevice.deviceId}(U${startU}-U${endU}) 冲突`,
|
||||||
|
data: { deviceId: newDevice.deviceId }
|
||||||
|
});
|
||||||
|
hasConflict = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasConflict) {
|
||||||
|
for (const otherDevice of devices) {
|
||||||
|
if (otherDevice === newDevice) continue;
|
||||||
|
|
||||||
|
const otherStart = otherDevice.position;
|
||||||
|
const otherEnd = otherDevice.position + otherDevice.height - 1;
|
||||||
|
|
||||||
|
if (!(endU < otherStart || startU > otherEnd)) {
|
||||||
|
stats.failed++;
|
||||||
|
stats.errors.push({
|
||||||
|
row: 0,
|
||||||
|
error: `U位冲突:导入数据内部冲突,设备 ${newDevice.deviceId}(U${startU}-U${endU}) 与设备 ${otherDevice.deviceId}(U${otherStart}-U${otherEnd}) U位重叠`,
|
||||||
|
data: { deviceId: newDevice.deviceId }
|
||||||
|
});
|
||||||
|
hasConflict = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasConflict) {
|
||||||
|
const idx = validDevices.indexOf(newDevice);
|
||||||
|
if (idx > -1) validDevices.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 【优化3】批量查询已存在的设备ID和序列号(单次查询)
|
// 【优化3】批量查询已存在的设备ID和序列号(单次查询)
|
||||||
if (validDevices.length > 0) {
|
if (validDevices.length > 0) {
|
||||||
@@ -1161,41 +1417,100 @@ router.put('/batch-status', async (req, res) => {
|
|||||||
router.put('/batch-move', async (req, res) => {
|
router.put('/batch-move', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { deviceIds, targetRackId, startPosition } = req.body;
|
const { deviceIds, targetRackId, startPosition } = req.body;
|
||||||
|
|
||||||
if (!deviceIds || !Array.isArray(deviceIds) || deviceIds.length === 0) {
|
if (!deviceIds || !Array.isArray(deviceIds) || deviceIds.length === 0) {
|
||||||
return res.status(400).json({ error: '请提供有效的设备ID列表' });
|
return res.status(400).json({ error: '请提供有效的设备ID列表' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetRackId) {
|
if (!targetRackId) {
|
||||||
return res.status(400).json({ error: '请提供目标机柜ID' });
|
return res.status(400).json({ error: '请提供目标机柜ID' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证目标机柜是否存在
|
|
||||||
const targetRack = await Rack.findByPk(targetRackId);
|
const targetRack = await Rack.findByPk(targetRackId);
|
||||||
if (!targetRack) {
|
if (!targetRack) {
|
||||||
return res.status(404).json({ error: '目标机柜不存在' });
|
return res.status(404).json({ error: '目标机柜不存在' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量更新设备位置
|
const devicesToMove = await Device.findAll({
|
||||||
|
where: { deviceId: { [Op.in]: deviceIds } },
|
||||||
|
attributes: ['deviceId', 'position', 'height']
|
||||||
|
});
|
||||||
|
|
||||||
|
const deviceHeightMap = new Map(devicesToMove.map(d => [d.deviceId, d.height || 1]));
|
||||||
|
|
||||||
|
if (startPosition) {
|
||||||
|
const devicesToCheck = [];
|
||||||
|
for (let i = 0; i < deviceIds.length; i++) {
|
||||||
|
const deviceId = deviceIds[i];
|
||||||
|
const height = deviceHeightMap.get(deviceId) || 1;
|
||||||
|
devicesToCheck.push({
|
||||||
|
deviceId,
|
||||||
|
position: startPosition + i,
|
||||||
|
height
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingDevices = await Device.findAll({
|
||||||
|
where: {
|
||||||
|
rackId: targetRackId,
|
||||||
|
position: { [Op.ne]: null }
|
||||||
|
},
|
||||||
|
attributes: ['deviceId', 'position', 'height']
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const newDevice of devicesToCheck) {
|
||||||
|
const startU = newDevice.position;
|
||||||
|
const endU = newDevice.position + newDevice.height - 1;
|
||||||
|
|
||||||
|
for (const existing of existingDevices) {
|
||||||
|
if (devicesToCheck.some(d => d.deviceId === existing.deviceId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existStart = existing.position;
|
||||||
|
const existEnd = existing.position + (existing.height || 1) - 1;
|
||||||
|
|
||||||
|
if (!(endU < existStart || startU > existEnd)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: `U位冲突:机柜中已有设备 ${existing.deviceId} 占用 U${existStart}${existEnd !== existStart ? '-' + existEnd : ''},与移动设备 ${newDevice.deviceId}(U${startU}-U${endU}) 冲突`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const other of devicesToCheck) {
|
||||||
|
if (other === newDevice) continue;
|
||||||
|
|
||||||
|
const otherStart = other.position;
|
||||||
|
const otherEnd = other.position + other.height - 1;
|
||||||
|
|
||||||
|
if (!(endU < otherStart || startU > otherEnd)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: `U位冲突:移动设备 ${newDevice.deviceId}(U${startU}-U${endU}) 与设备 ${other.deviceId}(U${otherStart}-U${otherEnd}) U位重叠`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let movedCount = 0;
|
let movedCount = 0;
|
||||||
for (let i = 0; i < deviceIds.length; i++) {
|
for (let i = 0; i < deviceIds.length; i++) {
|
||||||
const deviceId = deviceIds[i];
|
const deviceId = deviceIds[i];
|
||||||
const position = startPosition ? startPosition + i : undefined;
|
const position = startPosition ? startPosition + i : undefined;
|
||||||
|
|
||||||
const updateData = { rackId: targetRackId };
|
const updateData = { rackId: targetRackId };
|
||||||
if (position) {
|
if (position) {
|
||||||
updateData.position = position;
|
updateData.position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [updated] = await Device.update(updateData, {
|
const [updated] = await Device.update(updateData, {
|
||||||
where: { deviceId }
|
where: { deviceId }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
movedCount++;
|
movedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: `批量移动成功,已将 ${movedCount} 个设备移动到机柜 ${targetRackId}`,
|
message: `批量移动成功,已将 ${movedCount} 个设备移动到机柜 ${targetRackId}`,
|
||||||
movedCount
|
movedCount
|
||||||
|
|||||||
@@ -139,13 +139,19 @@ const ImportModal = ({
|
|||||||
const optionalFields = deviceFields.filter((f) => f.visible && !f.required);
|
const optionalFields = deviceFields.filter((f) => f.visible && !f.required);
|
||||||
|
|
||||||
const previewColumns = previewData?.fieldList
|
const previewColumns = previewData?.fieldList
|
||||||
? previewData.fieldList.map((field) => ({
|
? [
|
||||||
title: field.displayName + (field.required ? ' *' : ''),
|
...previewData.fieldList
|
||||||
dataIndex: field.fieldName,
|
.filter((field) => field.fieldName !== 'rackId')
|
||||||
key: field.fieldName,
|
.map((field) => ({
|
||||||
width: 120,
|
title: field.displayName + (field.required ? ' *' : ''),
|
||||||
ellipsis: true,
|
dataIndex: field.fieldName,
|
||||||
}))
|
key: field.fieldName,
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
})),
|
||||||
|
{ title: '所在机房', dataIndex: 'roomName', key: 'roomName', width: 100 },
|
||||||
|
{ title: '所在机柜', dataIndex: 'rackName', key: 'rackName', width: 100 },
|
||||||
|
]
|
||||||
: [
|
: [
|
||||||
{ title: '行号', dataIndex: '_rowNum', key: '_rowNum', width: 60 },
|
{ title: '行号', dataIndex: '_rowNum', key: '_rowNum', width: 60 },
|
||||||
{ title: '设备名称', dataIndex: 'name', key: 'name', width: 120 },
|
{ title: '设备名称', dataIndex: 'name', key: 'name', width: 120 },
|
||||||
|
|||||||
@@ -192,6 +192,60 @@ function getGitInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findBackendServiceName() {
|
||||||
|
try {
|
||||||
|
const jlistResult = execSync('pm2 jlist', {
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
const processes = JSON.parse(jlistResult);
|
||||||
|
|
||||||
|
for (const proc of processes) {
|
||||||
|
const scriptPath = proc.pm2_env?.pm_out_log_path || proc.pm2_env?.pm_err_log_path || '';
|
||||||
|
const script = proc.pm2_env?.script || '';
|
||||||
|
|
||||||
|
if (scriptPath.includes('server.js') || script.includes('server.js')) {
|
||||||
|
return proc.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const proc of processes) {
|
||||||
|
if (proc.name === 'server' || proc.name === 'idc-backend' || proc.name === 'backend') {
|
||||||
|
return proc.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFrontendServiceName() {
|
||||||
|
try {
|
||||||
|
const jlistResult = execSync('pm2 jlist', {
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
const processes = JSON.parse(jlistResult);
|
||||||
|
|
||||||
|
for (const proc of processes) {
|
||||||
|
const script = proc.pm2_env?.script || '';
|
||||||
|
const scriptPath = proc.pm2_env?.pm_out_log_path || '';
|
||||||
|
|
||||||
|
if (script.includes('vite') || scriptPath.includes('vite') ||
|
||||||
|
script.includes('react') || script.includes('nginx') ||
|
||||||
|
proc.name === 'frontend' || proc.name === 'idc-frontend') {
|
||||||
|
return proc.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function backupDatabase(options) {
|
function backupDatabase(options) {
|
||||||
if (options.skipBackup || options.dryRun) {
|
if (options.skipBackup || options.dryRun) {
|
||||||
if (options.skipBackup) log.info('已跳过数据库备份');
|
if (options.skipBackup) log.info('已跳过数据库备份');
|
||||||
@@ -546,8 +600,18 @@ function restartServices(options) {
|
|||||||
return startDirectly();
|
return startDirectly();
|
||||||
}
|
}
|
||||||
|
|
||||||
log.subStep('重启后端服务...');
|
const backendServiceName = findBackendServiceName();
|
||||||
const backendRestart = runCommand('pm2 restart idc-backend 2>nul || pm2 start backend/server.js --name idc-backend');
|
if (!backendServiceName) {
|
||||||
|
log.warning('未找到后端服务,创建新服务...');
|
||||||
|
const startResult = runCommand('pm2 start backend/server.js --name server');
|
||||||
|
if (startResult.success) {
|
||||||
|
log.success('后端服务已创建');
|
||||||
|
}
|
||||||
|
return { success: startResult.success };
|
||||||
|
}
|
||||||
|
|
||||||
|
log.subStep(`重启后端服务 (${backendServiceName})...`);
|
||||||
|
const backendRestart = runCommand(`pm2 restart ${backendServiceName}`);
|
||||||
|
|
||||||
if (backendRestart.success) {
|
if (backendRestart.success) {
|
||||||
log.success('后端服务已重启');
|
log.success('后端服务已重启');
|
||||||
@@ -556,10 +620,10 @@ function restartServices(options) {
|
|||||||
return { success: false };
|
return { success: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const frontendCheck = runCommand('pm2 describe idc-frontend', { silent: true });
|
const frontendServiceName = findFrontendServiceName();
|
||||||
if (frontendCheck.success) {
|
if (frontendServiceName) {
|
||||||
log.subStep('重启前端服务...');
|
log.subStep(`重启前端服务 (${frontendServiceName})...`);
|
||||||
const frontendRestart = runCommand('pm2 restart idc-frontend');
|
const frontendRestart = runCommand(`pm2 restart ${frontendServiceName}`);
|
||||||
if (frontendRestart.success) {
|
if (frontendRestart.success) {
|
||||||
log.success('前端服务已重启');
|
log.success('前端服务已重启');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user