const fs = require('fs'); const path = require('path'); const db = require('../db'); const logger = require('./logger'); const parsePositiveNumber = (value, fallback) => { const num = Number(value); return Number.isFinite(num) && num > 0 ? num : fallback; }; // 磁盘告警阈值(GB),默认 10 GB const THRESHOLD_GB = parsePositiveNumber(process.env.DISK_ALERT_THRESHOLD_GB, 10); const THRESHOLD_BYTES = THRESHOLD_GB * 1024 * 1024 * 1024; // 告警冷却时间(小时),默认 24 小时 const ALERT_COOLDOWN_HOURS = parsePositiveNumber(process.env.DISK_ALERT_COOLDOWN_HOURS, 24); const getDiskSpace = async (directoryPath) => { try { // 仅在支持 statfs 的 Node 版本启用 if (fs.promises.statfs) { const stats = await fs.promises.statfs(directoryPath); const FreeSpace = stats.bfree * stats.bsize; const Size = stats.blocks * stats.bsize; return { FreeSpace, Size }; } logger.warn('当前 Node.js 版本不支持 fs.promises.statfs,跳过磁盘检查'); return { FreeSpace: Number.MAX_SAFE_INTEGER, Size: Number.MAX_SAFE_INTEGER }; } catch (error) { logger.error(`获取磁盘空间失败 [${directoryPath}]: ${error.message}`); throw error; } }; const checkDiskSpace = async () => { try { // 1. 获取上传目录绝对路径 const uploadDir = process.env.UPLOAD_DIR || 'uploads'; const absolutePath = path.resolve(uploadDir); // 目录不存在时,回退到所在盘符根目录进行检测 let targetPath = absolutePath; try { await fs.promises.access(targetPath); } catch { targetPath = path.parse(absolutePath).root; } // 2. 获取磁盘空间 const { FreeSpace } = await getDiskSpace(targetPath); // 3. 判断是否低于阈值 if (FreeSpace < THRESHOLD_BYTES) { const root = path.parse(absolutePath).root; const freeGB = (FreeSpace / (1024 * 1024 * 1024)).toFixed(2); const title = `【系统警告】磁盘空间不足 (${root})`; // 4. 冷却期内同标题只发一次 const cooldownTime = new Date(Date.now() - ALERT_COOLDOWN_HOURS * 60 * 60 * 1000); const recentAlert = await db.query( `SELECT id FROM notifications WHERE title = $1 AND created_at > $2 LIMIT 1`, [title, cooldownTime] ); if (recentAlert.rows.length === 0) { logger.warn(`磁盘空间不足警告: ${root} 剩余 ${freeGB} GB`); const content = `系统检测到存储磁盘 (${root}) 剩余空间仅剩 ${freeGB} GB,低于警告阈值 (${THRESHOLD_GB} GB)。\n\n请系统管理员尽快处理:\n1. 清理磁盘空间。\n2. 或配置新的存储路径(如挂载大容量磁盘),并更新配置文件中的 UPLOAD_DIR 参数。`; // 尝试获取 ID 最小的 super_admin 作为发布者 let publisherId = 1; const adminRes = await db.query("SELECT id FROM users WHERE role = 'super_admin' ORDER BY id ASC LIMIT 1"); if (adminRes.rows.length > 0) { publisherId = adminRes.rows[0].id; } await db.query( 'INSERT INTO notifications (title, content, publisher_id, publisher_name, is_top, created_at) VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)', [title, content, publisherId, '系统监控', true] ); logger.info('已发布磁盘空间不足警告通知'); } } } catch (error) { logger.error(`磁盘空间检查失败: ${error.message}`); } }; module.exports = { checkDiskSpace };