mirror of
https://github.com/dqzboy/Docker-Proxy.git
synced 2026-01-12 16:25:42 +08:00
334 lines
9.1 KiB
JavaScript
334 lines
9.1 KiB
JavaScript
/**
|
||
* 服务器实用工具函数
|
||
*/
|
||
|
||
const { exec } = require('child_process');
|
||
const os = require('os');
|
||
const fs = require('fs').promises;
|
||
const path = require('path');
|
||
const logger = require('./logger');
|
||
|
||
/**
|
||
* 安全执行系统命令
|
||
* @param {string} command - 要执行的命令
|
||
* @param {object} options - 执行选项
|
||
* @returns {Promise<string>} 命令输出结果
|
||
*/
|
||
function execCommand(command, options = { timeout: 30000 }) {
|
||
return new Promise((resolve, reject) => {
|
||
exec(command, options, (error, stdout, stderr) => {
|
||
if (error) {
|
||
if (error.killed) {
|
||
reject(new Error('执行命令超时'));
|
||
} else {
|
||
reject(error);
|
||
}
|
||
return;
|
||
}
|
||
resolve(stdout.trim() || stderr.trim());
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取系统信息
|
||
* @returns {Promise<object>} 系统信息对象
|
||
*/
|
||
async function getSystemInfo() {
|
||
const platform = os.platform();
|
||
let memoryInfo = {};
|
||
let cpuInfo = {};
|
||
let diskInfo = {};
|
||
|
||
try {
|
||
// 内存信息 - 使用OS模块,适用于所有平台
|
||
const totalMem = os.totalmem();
|
||
const freeMem = os.freemem();
|
||
const usedMem = totalMem - freeMem;
|
||
const memPercent = Math.round((usedMem / totalMem) * 100);
|
||
|
||
memoryInfo = {
|
||
total: formatBytes(totalMem),
|
||
free: formatBytes(freeMem),
|
||
used: formatBytes(usedMem),
|
||
percent: memPercent
|
||
};
|
||
|
||
// CPU信息 - 使用不同平台的方法
|
||
await getCpuInfo(platform).then(info => {
|
||
cpuInfo = info;
|
||
}).catch(err => {
|
||
logger.warn('获取CPU信息失败:', err);
|
||
// 降级方案:使用OS模块
|
||
const cpuLoad = os.loadavg();
|
||
const cpuCores = os.cpus().length;
|
||
|
||
cpuInfo = {
|
||
model: os.cpus()[0].model,
|
||
cores: cpuCores,
|
||
load1: cpuLoad[0].toFixed(2),
|
||
load5: cpuLoad[1].toFixed(2),
|
||
load15: cpuLoad[2].toFixed(2),
|
||
percent: Math.round((cpuLoad[0] / cpuCores) * 100)
|
||
};
|
||
});
|
||
|
||
// 磁盘信息 - 根据平台调用不同方法
|
||
await getDiskInfo(platform).then(info => {
|
||
diskInfo = info;
|
||
}).catch(err => {
|
||
logger.warn('获取磁盘信息失败:', err);
|
||
diskInfo = {
|
||
filesystem: 'unknown',
|
||
size: 'unknown',
|
||
used: 'unknown',
|
||
available: 'unknown',
|
||
percent: '0%'
|
||
};
|
||
});
|
||
|
||
return {
|
||
platform,
|
||
hostname: os.hostname(),
|
||
memory: memoryInfo,
|
||
cpu: cpuInfo,
|
||
disk: diskInfo,
|
||
uptime: formatUptime(os.uptime())
|
||
};
|
||
} catch (error) {
|
||
logger.error('获取系统信息失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据平台获取CPU信息
|
||
* @param {string} platform - 操作系统平台
|
||
* @returns {Promise<object>} CPU信息
|
||
*/
|
||
async function getCpuInfo(platform) {
|
||
if (platform === 'linux') {
|
||
try {
|
||
// Linux平台使用/proc/stat和/proc/cpuinfo
|
||
const [loadData, cpuData] = await Promise.all([
|
||
execCommand("cat /proc/loadavg"),
|
||
execCommand("cat /proc/cpuinfo | grep 'model name' | head -1")
|
||
]);
|
||
|
||
const cpuLoad = loadData.split(' ').slice(0, 3).map(parseFloat);
|
||
const cpuCores = os.cpus().length;
|
||
const modelMatch = cpuData.match(/model name\s*:\s*(.*)/);
|
||
const model = modelMatch ? modelMatch[1].trim() : os.cpus()[0].model;
|
||
const percent = Math.round((cpuLoad[0] / cpuCores) * 100);
|
||
|
||
return {
|
||
model,
|
||
cores: cpuCores,
|
||
load1: cpuLoad[0].toFixed(2),
|
||
load5: cpuLoad[1].toFixed(2),
|
||
load15: cpuLoad[2].toFixed(2),
|
||
percent: percent > 100 ? 100 : percent
|
||
};
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
} else if (platform === 'darwin') {
|
||
// macOS平台
|
||
try {
|
||
const cpuLoad = os.loadavg();
|
||
const cpuCores = os.cpus().length;
|
||
const model = os.cpus()[0].model;
|
||
const systemProfilerData = await execCommand("system_profiler SPHardwareDataType | grep 'Processor Name'");
|
||
const cpuMatch = systemProfilerData.match(/Processor Name:\s*(.*)/);
|
||
const cpuModel = cpuMatch ? cpuMatch[1].trim() : model;
|
||
|
||
return {
|
||
model: cpuModel,
|
||
cores: cpuCores,
|
||
load1: cpuLoad[0].toFixed(2),
|
||
load5: cpuLoad[1].toFixed(2),
|
||
load15: cpuLoad[2].toFixed(2),
|
||
percent: Math.round((cpuLoad[0] / cpuCores) * 100)
|
||
};
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
} else if (platform === 'win32') {
|
||
// Windows平台
|
||
try {
|
||
// 使用wmic获取CPU信息
|
||
const cpuData = await execCommand('wmic cpu get Name,NumberOfCores /value');
|
||
const cpuLines = cpuData.split('\r\n');
|
||
|
||
let model = os.cpus()[0].model;
|
||
let cores = os.cpus().length;
|
||
|
||
cpuLines.forEach(line => {
|
||
if (line.startsWith('Name=')) {
|
||
model = line.substring(5).trim();
|
||
} else if (line.startsWith('NumberOfCores=')) {
|
||
cores = parseInt(line.substring(14).trim()) || cores;
|
||
}
|
||
});
|
||
|
||
// Windows没有直接的负载平均值,使用CPU使用率作为替代
|
||
const perfData = await execCommand('wmic cpu get LoadPercentage /value');
|
||
const loadMatch = perfData.match(/LoadPercentage=(\d+)/);
|
||
const loadPercent = loadMatch ? parseInt(loadMatch[1]) : 0;
|
||
|
||
return {
|
||
model,
|
||
cores,
|
||
load1: '不适用',
|
||
load5: '不适用',
|
||
load15: '不适用',
|
||
percent: loadPercent
|
||
};
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 默认返回OS模块的信息
|
||
const cpuLoad = os.loadavg();
|
||
const cpuCores = os.cpus().length;
|
||
|
||
return {
|
||
model: os.cpus()[0].model,
|
||
cores: cpuCores,
|
||
load1: cpuLoad[0].toFixed(2),
|
||
load5: cpuLoad[1].toFixed(2),
|
||
load15: cpuLoad[2].toFixed(2),
|
||
percent: Math.round((cpuLoad[0] / cpuCores) * 100)
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 根据平台获取磁盘信息
|
||
* @param {string} platform - 操作系统平台
|
||
* @returns {Promise<object>} 磁盘信息
|
||
*/
|
||
async function getDiskInfo(platform) {
|
||
if (platform === 'linux' || platform === 'darwin') {
|
||
try {
|
||
// Linux/macOS使用df命令
|
||
const diskCommand = platform === 'linux'
|
||
? 'df -h / | tail -1'
|
||
: 'df -h / | tail -1';
|
||
|
||
const diskData = await execCommand(diskCommand);
|
||
const parts = diskData.trim().split(/\s+/);
|
||
|
||
if (parts.length >= 5) {
|
||
return {
|
||
filesystem: parts[0],
|
||
size: parts[1],
|
||
used: parts[2],
|
||
available: parts[3],
|
||
percent: parts[4]
|
||
};
|
||
} else {
|
||
throw new Error('磁盘信息格式不正确');
|
||
}
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
} else if (platform === 'win32') {
|
||
// Windows平台
|
||
try {
|
||
// 使用wmic获取C盘信息
|
||
const diskData = await execCommand('wmic logicaldisk where DeviceID="C:" get Size,FreeSpace /value');
|
||
const lines = diskData.split(/\r\n|\n/);
|
||
let freeSpace, totalSize;
|
||
|
||
lines.forEach(line => {
|
||
if (line.startsWith('FreeSpace=')) {
|
||
freeSpace = parseInt(line.split('=')[1]);
|
||
} else if (line.startsWith('Size=')) {
|
||
totalSize = parseInt(line.split('=')[1]);
|
||
}
|
||
});
|
||
|
||
if (freeSpace !== undefined && totalSize !== undefined) {
|
||
const usedSpace = totalSize - freeSpace;
|
||
const usedPercent = Math.round((usedSpace / totalSize) * 100);
|
||
|
||
return {
|
||
filesystem: 'C:',
|
||
size: formatBytes(totalSize),
|
||
used: formatBytes(usedSpace),
|
||
available: formatBytes(freeSpace),
|
||
percent: `${usedPercent}%`
|
||
};
|
||
} else {
|
||
throw new Error('无法解析Windows磁盘信息');
|
||
}
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 默认尝试df命令
|
||
try {
|
||
const diskData = await execCommand('df -h / | tail -1');
|
||
const parts = diskData.trim().split(/\s+/);
|
||
|
||
if (parts.length >= 5) {
|
||
return {
|
||
filesystem: parts[0],
|
||
size: parts[1],
|
||
used: parts[2],
|
||
available: parts[3],
|
||
percent: parts[4]
|
||
};
|
||
} else {
|
||
throw new Error('磁盘信息格式不正确');
|
||
}
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 将字节格式化为可读大小
|
||
* @param {number} bytes - 字节数
|
||
* @returns {string} 格式化后的字符串
|
||
*/
|
||
function formatBytes(bytes) {
|
||
if (bytes === 0) return '0 B';
|
||
|
||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||
|
||
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
|
||
}
|
||
|
||
/**
|
||
* 格式化运行时间
|
||
* @param {number} seconds - 秒数
|
||
* @returns {string} 格式化后的运行时间
|
||
*/
|
||
function formatUptime(seconds) {
|
||
const days = Math.floor(seconds / 86400);
|
||
seconds %= 86400;
|
||
const hours = Math.floor(seconds / 3600);
|
||
seconds %= 3600;
|
||
const minutes = Math.floor(seconds / 60);
|
||
seconds = Math.floor(seconds % 60);
|
||
|
||
const parts = [];
|
||
if (days > 0) parts.push(`${days}天`);
|
||
if (hours > 0) parts.push(`${hours}小时`);
|
||
if (minutes > 0) parts.push(`${minutes}分钟`);
|
||
if (seconds > 0 && parts.length === 0) parts.push(`${seconds}秒`);
|
||
|
||
return parts.join(' ');
|
||
}
|
||
|
||
module.exports = {
|
||
execCommand,
|
||
getSystemInfo,
|
||
formatBytes,
|
||
formatUptime
|
||
};
|