guoziyun před 3 měsíci
rodič
revize
c6bfe137e4

+ 169 - 0
service/monitor/check-disk.js

@@ -0,0 +1,169 @@
+/// 磁盘空间监控
+const { exec } = require('child_process');
+const { promisify } = require('util');
+const execAsync = promisify(exec);
+
+const { sendEmail } = require('./email');
+const { sendSms } = require('./sms');
+const SmsTemplate = require('./sms-templates');
+
+// 阈值配置
+const USAGE_THRESHOLD = 90; // 使用率阈值 90%
+const FREE_SPACE_THRESHOLD = 10; // 剩余空间阈值 10G
+
+async function getDiskInfo() {
+  try {
+    const { stdout } = await execAsync('df -h /');
+    const lines = stdout.trim().split('\n');
+    
+    if (lines.length < 2) {
+      throw new Error('无法解析df命令输出');
+    }
+
+    // 解析df输出
+    const dataLine = lines[1].trim().split(/\s+/);
+    const [filesystem, size, used, avail, usePercent, mountpoint] = dataLine;
+    
+    return {
+      filesystem,
+      size,
+      used,
+      avail,
+      usePercent: parseInt(usePercent),
+      mountpoint,
+      raw: stdout
+    };
+  } catch (error) {
+    throw new Error(`获取磁盘信息失败: ${error.message}`);
+  }
+}
+
+function parseSize(sizeStr) {
+  // 将 "106G", "1.5T" 等格式转换为GB数值
+  const match = sizeStr.match(/^([\d.]+)([KMGT]?)$/i);
+  if (!match) return 0;
+  
+  const value = parseFloat(match[1]);
+  const unit = match[2].toUpperCase();
+  
+  const multipliers = { '': 1/1024, 'K': 1/1024/1024, 'M': 1/1024, 'G': 1, 'T': 1024 };
+  return value * (multipliers[unit] || 1);
+}
+
+async function checkDiskSpace() {
+  let diskInfo;
+  
+  try {
+    diskInfo = await getDiskInfo();
+  } catch (e) {
+    console.error('磁盘信息获取失败:', e.message);
+    return {
+      result: false,
+      errcode: 200,
+      title: '磁盘监控:无法获取磁盘信息',
+      sms: SmsTemplate.SMS_DISK_ERROR,
+      data: [e.message],
+      diskInfo: null
+    };
+  }
+
+  const usagePercent = diskInfo.usePercent;
+  const availGB = parseSize(diskInfo.avail);
+  
+  console.log(`磁盘使用率: ${usagePercent}%, 剩余空间: ${availGB.toFixed(2)}GB`);
+
+  // 检查是否超过阈值
+  if (usagePercent >= USAGE_THRESHOLD || availGB < FREE_SPACE_THRESHOLD) {
+    const reasons = [];
+    if (usagePercent >= USAGE_THRESHOLD) {
+      reasons.push(`使用率达到 ${usagePercent}% (阈值: ${USAGE_THRESHOLD}%)`);
+    }
+    if (availGB < FREE_SPACE_THRESHOLD) {
+      reasons.push(`剩余空间仅 ${availGB.toFixed(2)}GB (阈值: ${FREE_SPACE_THRESHOLD}GB)`);
+    }
+
+    return {
+      result: false,
+      errcode: 201,
+      title: '磁盘空间告警',
+      sms: SmsTemplate.SMS_DISK_WARNING,
+      data: [
+        `文件系统: ${diskInfo.filesystem}`,
+        `挂载点: ${diskInfo.mountpoint}`,
+        `总容量: ${diskInfo.size}`,
+        `已使用: ${diskInfo.used}`,
+        `可用空间: ${diskInfo.avail}`,
+        `使用率: ${usagePercent}%`,
+        '',
+        '告警原因:',
+        ...reasons,
+        '',
+        '详细信息:',
+        diskInfo.raw
+      ],
+      diskInfo
+    };
+  }
+
+  return {
+    result: true,
+    errcode: 0,
+    title: '磁盘空间正常',
+    sms: SmsTemplate.SMS_RESUME,
+    data: [
+      `文件系统: ${diskInfo.filesystem}`,
+      `使用率: ${usagePercent}%`,
+      `剩余空间: ${diskInfo.avail}`
+    ],
+    diskInfo
+  };
+}
+
+async function run(lastErrCode = 0) {
+  console.log("检查磁盘空间...", new Date());
+  
+  try {
+    let result = null;
+    
+    // 连续检测3次才发送通知,减少误报
+    for (let i = 0; i < 3; i++) {
+      result = await checkDiskSpace();
+      console.log(result);
+      
+      if (!result.result) {
+        console.warn("磁盘空间检查出现异常,一分钟后重试");
+        await delay(60 * 1000);
+      } else {
+        console.log("磁盘空间正常");
+        break;
+      }
+    }
+
+    if (!result.result) {
+      console.error("连续3次检查磁盘空间异常,发送通知");
+      sendEmail(result.title, result.data);
+      sendSms(result.sms);
+      console.log("5分钟后再试");
+      setTimeout(run, 5 * 60 * 1000, result.errcode);
+      return;
+    } else {
+      // 如果上次是出错的情况,那么属于服务恢复,发送恢复通知
+      if (lastErrCode === 200 || lastErrCode === 201) {
+        sendEmail(result.title, result.data);
+        sendSms(result.sms);
+      }
+    }
+  } catch (err) {
+    console.error(err.stack);
+  }
+}
+
+function delay(ms) {
+  return new Promise(done => setTimeout(done, ms));
+}
+
+module.exports = { run, checkDiskSpace };
+
+if (require.main == module) {
+  run();
+}

+ 14 - 0
service/monitor/index-disk.js

@@ -0,0 +1,14 @@
+/// 磁盘监控入口
+
+const cron = require('node-cron');
+
+const checkDisk = require('./check-disk');
+
+// 每天早上10点检查磁盘空间
+cron.schedule('0 10 * * *', () => {
+  let date = new Date().toLocaleString();
+  console.log(`[check-disk]@'0 10 * * *' running @ ${date}`);
+  checkDisk.run().then(console.log).catch(console.error);
+});
+
+console.log('[check-disk] run@\'0 10 * * *\' installed!');

+ 9 - 7
service/monitor/sms-templates.js

@@ -1,8 +1,10 @@
 module.exports = {
-  SMS_API_ERROR: '2341138',   // API接口访问异常
-  SMS_NO_NEW_CONTENT: '2341062',  // 没有新的发布内容
-  SMS_SYNC_ERROR: '2341060', // 线上数据不同步
-  SMS_HEARTBEAT: '2341055',  // monitor心跳提醒
-  SMS_RESUME: '2394348',  // 服务恢复正常
-  SMS_PCOLORING_ERROR: '2402346', // art.pcoloring.com 网站访问异常
-}
+  SMS_API_ERROR: "2341138", // API接口访问异常
+  SMS_NO_NEW_CONTENT: "2341062", // 没有新的发布内容
+  SMS_SYNC_ERROR: "2341060", // 线上数据不同步
+  SMS_HEARTBEAT: "2341055", // monitor心跳提醒
+  SMS_RESUME: "2394348", // 服务恢复正常
+  SMS_PCOLORING_ERROR: "2402346", // art.pcoloring.com 网站访问异常
+  SMS_DISK_WARNING: "2605332", // 磁盘空间告警(暂用API错误模板)
+  SMS_DISK_ERROR: "2605332", // 磁盘监控错误(暂用API错误模板)
+};