server-utils.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /**
  2. * 服务器实用工具函数
  3. */
  4. const { exec } = require('child_process');
  5. const os = require('os');
  6. const fs = require('fs').promises;
  7. const path = require('path');
  8. const logger = require('./logger');
  9. /**
  10. * 安全执行系统命令
  11. * @param {string} command - 要执行的命令
  12. * @param {object} options - 执行选项
  13. * @returns {Promise<string>} 命令输出结果
  14. */
  15. function execCommand(command, options = { timeout: 30000 }) {
  16. return new Promise((resolve, reject) => {
  17. exec(command, options, (error, stdout, stderr) => {
  18. if (error) {
  19. if (error.killed) {
  20. reject(new Error('执行命令超时'));
  21. } else {
  22. reject(error);
  23. }
  24. return;
  25. }
  26. resolve(stdout.trim() || stderr.trim());
  27. });
  28. });
  29. }
  30. /**
  31. * 获取系统信息
  32. * @returns {Promise<object>} 系统信息对象
  33. */
  34. async function getSystemInfo() {
  35. const platform = os.platform();
  36. let memoryInfo = {};
  37. let cpuInfo = {};
  38. let diskInfo = {};
  39. try {
  40. // 内存信息 - 使用OS模块,适用于所有平台
  41. const totalMem = os.totalmem();
  42. const freeMem = os.freemem();
  43. const usedMem = totalMem - freeMem;
  44. const memPercent = Math.round((usedMem / totalMem) * 100);
  45. memoryInfo = {
  46. total: formatBytes(totalMem),
  47. free: formatBytes(freeMem),
  48. used: formatBytes(usedMem),
  49. percent: memPercent
  50. };
  51. // CPU信息 - 使用不同平台的方法
  52. await getCpuInfo(platform).then(info => {
  53. cpuInfo = info;
  54. }).catch(err => {
  55. logger.warn('获取CPU信息失败:', err);
  56. // 降级方案:使用OS模块
  57. const cpuLoad = os.loadavg();
  58. const cpuCores = os.cpus().length;
  59. cpuInfo = {
  60. model: os.cpus()[0].model,
  61. cores: cpuCores,
  62. load1: cpuLoad[0].toFixed(2),
  63. load5: cpuLoad[1].toFixed(2),
  64. load15: cpuLoad[2].toFixed(2),
  65. percent: Math.round((cpuLoad[0] / cpuCores) * 100)
  66. };
  67. });
  68. // 磁盘信息 - 根据平台调用不同方法
  69. await getDiskInfo(platform).then(info => {
  70. diskInfo = info;
  71. }).catch(err => {
  72. logger.warn('获取磁盘信息失败:', err);
  73. diskInfo = {
  74. filesystem: 'unknown',
  75. size: 'unknown',
  76. used: 'unknown',
  77. available: 'unknown',
  78. percent: '0%'
  79. };
  80. });
  81. return {
  82. platform,
  83. hostname: os.hostname(),
  84. memory: memoryInfo,
  85. cpu: cpuInfo,
  86. disk: diskInfo,
  87. uptime: formatUptime(os.uptime())
  88. };
  89. } catch (error) {
  90. logger.error('获取系统信息失败:', error);
  91. throw error;
  92. }
  93. }
  94. /**
  95. * 根据平台获取CPU信息
  96. * @param {string} platform - 操作系统平台
  97. * @returns {Promise<object>} CPU信息
  98. */
  99. async function getCpuInfo(platform) {
  100. if (platform === 'linux') {
  101. try {
  102. // Linux平台使用/proc/stat和/proc/cpuinfo
  103. const [loadData, cpuData] = await Promise.all([
  104. execCommand("cat /proc/loadavg"),
  105. execCommand("cat /proc/cpuinfo | grep 'model name' | head -1")
  106. ]);
  107. const cpuLoad = loadData.split(' ').slice(0, 3).map(parseFloat);
  108. const cpuCores = os.cpus().length;
  109. const modelMatch = cpuData.match(/model name\s*:\s*(.*)/);
  110. const model = modelMatch ? modelMatch[1].trim() : os.cpus()[0].model;
  111. const percent = Math.round((cpuLoad[0] / cpuCores) * 100);
  112. return {
  113. model,
  114. cores: cpuCores,
  115. load1: cpuLoad[0].toFixed(2),
  116. load5: cpuLoad[1].toFixed(2),
  117. load15: cpuLoad[2].toFixed(2),
  118. percent: percent > 100 ? 100 : percent
  119. };
  120. } catch (error) {
  121. throw error;
  122. }
  123. } else if (platform === 'darwin') {
  124. // macOS平台
  125. try {
  126. const cpuLoad = os.loadavg();
  127. const cpuCores = os.cpus().length;
  128. const model = os.cpus()[0].model;
  129. const systemProfilerData = await execCommand("system_profiler SPHardwareDataType | grep 'Processor Name'");
  130. const cpuMatch = systemProfilerData.match(/Processor Name:\s*(.*)/);
  131. const cpuModel = cpuMatch ? cpuMatch[1].trim() : model;
  132. return {
  133. model: cpuModel,
  134. cores: cpuCores,
  135. load1: cpuLoad[0].toFixed(2),
  136. load5: cpuLoad[1].toFixed(2),
  137. load15: cpuLoad[2].toFixed(2),
  138. percent: Math.round((cpuLoad[0] / cpuCores) * 100)
  139. };
  140. } catch (error) {
  141. throw error;
  142. }
  143. } else if (platform === 'win32') {
  144. // Windows平台
  145. try {
  146. // 使用wmic获取CPU信息
  147. const cpuData = await execCommand('wmic cpu get Name,NumberOfCores /value');
  148. const cpuLines = cpuData.split('\r\n');
  149. let model = os.cpus()[0].model;
  150. let cores = os.cpus().length;
  151. cpuLines.forEach(line => {
  152. if (line.startsWith('Name=')) {
  153. model = line.substring(5).trim();
  154. } else if (line.startsWith('NumberOfCores=')) {
  155. cores = parseInt(line.substring(14).trim()) || cores;
  156. }
  157. });
  158. // Windows没有直接的负载平均值,使用CPU使用率作为替代
  159. const perfData = await execCommand('wmic cpu get LoadPercentage /value');
  160. const loadMatch = perfData.match(/LoadPercentage=(\d+)/);
  161. const loadPercent = loadMatch ? parseInt(loadMatch[1]) : 0;
  162. return {
  163. model,
  164. cores,
  165. load1: '不适用',
  166. load5: '不适用',
  167. load15: '不适用',
  168. percent: loadPercent
  169. };
  170. } catch (error) {
  171. throw error;
  172. }
  173. }
  174. // 默认返回OS模块的信息
  175. const cpuLoad = os.loadavg();
  176. const cpuCores = os.cpus().length;
  177. return {
  178. model: os.cpus()[0].model,
  179. cores: cpuCores,
  180. load1: cpuLoad[0].toFixed(2),
  181. load5: cpuLoad[1].toFixed(2),
  182. load15: cpuLoad[2].toFixed(2),
  183. percent: Math.round((cpuLoad[0] / cpuCores) * 100)
  184. };
  185. }
  186. /**
  187. * 根据平台获取磁盘信息
  188. * @param {string} platform - 操作系统平台
  189. * @returns {Promise<object>} 磁盘信息
  190. */
  191. async function getDiskInfo(platform) {
  192. if (platform === 'linux' || platform === 'darwin') {
  193. try {
  194. // Linux/macOS使用df命令
  195. const diskCommand = platform === 'linux'
  196. ? 'df -h / | tail -1'
  197. : 'df -h / | tail -1';
  198. const diskData = await execCommand(diskCommand);
  199. const parts = diskData.trim().split(/\s+/);
  200. if (parts.length >= 5) {
  201. return {
  202. filesystem: parts[0],
  203. size: parts[1],
  204. used: parts[2],
  205. available: parts[3],
  206. percent: parts[4]
  207. };
  208. } else {
  209. throw new Error('磁盘信息格式不正确');
  210. }
  211. } catch (error) {
  212. throw error;
  213. }
  214. } else if (platform === 'win32') {
  215. // Windows平台
  216. try {
  217. // 使用wmic获取C盘信息
  218. const diskData = await execCommand('wmic logicaldisk where DeviceID="C:" get Size,FreeSpace /value');
  219. const lines = diskData.split(/\r\n|\n/);
  220. let freeSpace, totalSize;
  221. lines.forEach(line => {
  222. if (line.startsWith('FreeSpace=')) {
  223. freeSpace = parseInt(line.split('=')[1]);
  224. } else if (line.startsWith('Size=')) {
  225. totalSize = parseInt(line.split('=')[1]);
  226. }
  227. });
  228. if (freeSpace !== undefined && totalSize !== undefined) {
  229. const usedSpace = totalSize - freeSpace;
  230. const usedPercent = Math.round((usedSpace / totalSize) * 100);
  231. return {
  232. filesystem: 'C:',
  233. size: formatBytes(totalSize),
  234. used: formatBytes(usedSpace),
  235. available: formatBytes(freeSpace),
  236. percent: `${usedPercent}%`
  237. };
  238. } else {
  239. throw new Error('无法解析Windows磁盘信息');
  240. }
  241. } catch (error) {
  242. throw error;
  243. }
  244. }
  245. // 默认尝试df命令
  246. try {
  247. const diskData = await execCommand('df -h / | tail -1');
  248. const parts = diskData.trim().split(/\s+/);
  249. if (parts.length >= 5) {
  250. return {
  251. filesystem: parts[0],
  252. size: parts[1],
  253. used: parts[2],
  254. available: parts[3],
  255. percent: parts[4]
  256. };
  257. } else {
  258. throw new Error('磁盘信息格式不正确');
  259. }
  260. } catch (error) {
  261. throw error;
  262. }
  263. }
  264. /**
  265. * 将字节格式化为可读大小
  266. * @param {number} bytes - 字节数
  267. * @returns {string} 格式化后的字符串
  268. */
  269. function formatBytes(bytes) {
  270. if (bytes === 0) return '0 B';
  271. const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
  272. const i = Math.floor(Math.log(bytes) / Math.log(1024));
  273. return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
  274. }
  275. /**
  276. * 格式化运行时间
  277. * @param {number} seconds - 秒数
  278. * @returns {string} 格式化后的运行时间
  279. */
  280. function formatUptime(seconds) {
  281. const days = Math.floor(seconds / 86400);
  282. seconds %= 86400;
  283. const hours = Math.floor(seconds / 3600);
  284. seconds %= 3600;
  285. const minutes = Math.floor(seconds / 60);
  286. seconds = Math.floor(seconds % 60);
  287. const parts = [];
  288. if (days > 0) parts.push(`${days}天`);
  289. if (hours > 0) parts.push(`${hours}小时`);
  290. if (minutes > 0) parts.push(`${minutes}分钟`);
  291. if (seconds > 0 && parts.length === 0) parts.push(`${seconds}秒`);
  292. return parts.join(' ');
  293. }
  294. module.exports = {
  295. execCommand,
  296. getSystemInfo,
  297. formatBytes,
  298. formatUptime
  299. };