chore: initial import
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user