2026-05-10 13:12:44 +08:00

333 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const os = require('os');
const app = express();
const PORT = 3000;
// 记录服务器启动时间
const SERVER_START_TIME = new Date();
// 解析JSON请求体
app.use(express.json());
// ========== 数据存储 ==========
let heartbeats = [];
const patrolHistory = [];
const alerts = [];
const HEARTBEAT_TIMEOUT = 5 * 60 * 1000; // 5分钟
// ========== 告警检查函数 ==========
function checkAlerts(memPercent) {
let level = null;
let message = '';
if (memPercent > 90) {
level = 'critical';
message = `内存使用率过高: ${memPercent}%`;
} else if (memPercent > 80) {
level = 'warning';
message = `内存使用率警告: ${memPercent}%`;
}
if (level) {
const alert = {
timestamp: new Date().toISOString(),
level: level,
message: message,
memoryPercent: memPercent
};
alerts.push(alert);
if (alerts.length > 20) {
alerts.shift();
}
console.log(`[ALERT] ${level.toUpperCase()}: ${message}`);
}
}
// ========== 在线状态检查函数 ==========
function checkOnlineStatus() {
const now = Date.now();
const onlineServices = [];
const offlineServices = [];
const latestHeartbeats = {};
heartbeats.forEach(hb => {
const source = hb.service || 'unknown';
if (!latestHeartbeats[source] || new Date(hb.timestamp) > new Date(latestHeartbeats[source].timestamp)) {
latestHeartbeats[source] = hb;
}
});
for (const [source, hb] of Object.entries(latestHeartbeats)) {
const hbTime = new Date(hb.timestamp).getTime();
const isOnline = (now - hbTime) < HEARTBEAT_TIMEOUT;
const serviceInfo = {
source: source,
lastHeartbeat: hb.timestamp,
secondsAgo: Math.floor((now - hbTime) / 1000),
status: isOnline ? 'online' : 'offline'
};
if (isOnline) {
onlineServices.push(serviceInfo);
} else {
offlineServices.push(serviceInfo);
}
}
return { online: onlineServices, offline: offlineServices };
}
// ========== API路由 ==========
// GET /api/health - 快速健康检查
app.get('/api/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString()
});
});
// GET /api/status - 详细系统状态
app.get('/api/status', (req, res) => {
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const memPercent = Math.round((usedMem / totalMem) * 100);
checkAlerts(memPercent);
res.json({
service: 'HoloLake Health Monitor',
version: '1.0.0',
status: 'running',
timestamp: new Date().toISOString(),
uptime: {
seconds: Math.floor((Date.now() - SERVER_START_TIME.getTime()) / 1000),
startedAt: SERVER_START_TIME.toISOString()
},
system: {
platform: os.platform(),
arch: os.arch(),
hostname: os.hostname(),
nodeVersion: process.version
},
memory: {
totalMB: Math.round(totalMem / 1024 / 1024),
usedMB: Math.round(usedMem / 1024 / 1024),
freeMB: Math.round(freeMem / 1024 / 1024),
usagePercent: memPercent + '%'
},
services: checkOnlineStatus()
});
});
// POST /api/heartbeat - 心跳上报
app.post('/api/heartbeat', (req, res) => {
const heartbeat = {
timestamp: new Date().toISOString(),
service: req.body.service || 'unknown',
status: req.body.status || 'ok'
};
heartbeats.push(heartbeat);
res.json({
received: true,
heartbeat: heartbeat
});
});
// GET /api/heartbeat/history - 查看心跳历史
app.get('/api/heartbeat/history', (req, res) => {
res.json({
total: heartbeats.length,
heartbeats: heartbeats.slice(-20)
});
});
// GET /api/heartbeat/online - 查看在线服务
app.get('/api/heartbeat/online', (req, res) => {
const status = checkOnlineStatus();
res.json({
timestamp: new Date().toISOString(),
onlineCount: status.online.length,
offlineCount: status.offline.length,
online: status.online,
offline: status.offline
});
});
// GET /api/patrol/history - 查看巡检历史
app.get('/api/patrol/history', (req, res) => {
res.json({
total: patrolHistory.length,
patrols: patrolHistory
});
});
// GET /api/alerts - 查看告警历史
app.get('/api/alerts', (req, res) => {
res.json({
total: alerts.length,
critical: alerts.filter(a => a.level === 'critical').length,
warning: alerts.filter(a => a.level === 'warning').length,
alerts: alerts
});
});
// GET / - 根路径欢迎页
app.get('/', (req, res) => {
res.json({
service: 'HoloLake Health Check & Status Report',
version: '2.0.0',
endpoints: [
'GET /api/health - 快速健康检查',
'GET /api/status - 详细系统状态',
'POST /api/heartbeat - 心跳上报',
'GET /api/heartbeat/history - 心跳历史',
'GET /api/heartbeat/online - 在线服务',
'GET /api/patrol/history - 巡检历史',
'GET /api/alerts - 告警历史'
]
});
});
// ========== 定时自动巡检 ==========
setInterval(() => {
console.log('[PATROL] 开始定时巡检...');
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const memPercent = Math.round((usedMem / totalMem) * 100);
const patrolRecord = {
timestamp: new Date().toISOString(),
memory: {
totalMB: Math.round(totalMem / 1024 / 1024),
usedMB: Math.round(usedMem / 1024 / 1024),
usagePercent: memPercent
},
uptime: Math.floor((Date.now() - SERVER_START_TIME.getTime()) / 1000),
heartbeatCount: heartbeats.length
};
patrolHistory.push(patrolRecord);
if (patrolHistory.length > 50) {
patrolHistory.shift();
}
checkAlerts(memPercent);
console.log(`[PATROL] 巡检完成 - 内存使用: ${memPercent}%`);
}, 60000);
// 启动服务器
app.listen(PORT, () => {
console.log('='.repeat(50));
console.log('🌊 HoloLake Health Check & Status Report');
console.log('📡 服务运行在: http://localhost:' + PORT);
console.log('💚 GET /api/health - 健康检查');
console.log('📊 GET /api/status - 系统状态');
console.log('💓 POST /api/heartbeat - 心跳上报');
console.log('🔄 定时巡检 - 每60秒');
console.log('🚨 异常告警 - 内存>80%');
console.log('⏰ 心跳超时 - 5分钟');
console.log('='.repeat(50));
});
// ========== 环节3巡检数据持久化 ==========
const fs = require('fs');
const path = require('path');
const LOG_FILE = path.join(__dirname, 'health-log.json');
const MAX_LOG_DAYS = 7; // 保留7天记录
// 读取历史记录
function loadLogs() {
try {
if (fs.existsSync(LOG_FILE)) {
const data = fs.readFileSync(LOG_FILE, 'utf8');
return JSON.parse(data);
}
} catch (err) {
console.error('[LOG] 读取日志文件失败:', err.message);
}
return [];
}
// 保存记录到文件
function saveLogs(logs) {
try {
fs.writeFileSync(LOG_FILE, JSON.stringify(logs, null, 2));
} catch (err) {
console.error('[LOG] 保存日志文件失败:', err.message);
}
}
// 清理旧记录
function cleanOldLogs(logs) {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - MAX_LOG_DAYS);
return logs.filter(log => new Date(log.timestamp) > cutoff);
}
// 修改定时巡检加入持久化找到setInterval替换为以下
setInterval(() => {
console.log('[PATROL] 开始定时巡检...');
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const memPercent = Math.round((usedMem / totalMem) * 100);
// 检查告警
checkAlerts(memPercent);
const patrolRecord = {
timestamp: new Date().toISOString(),
memory: {
totalMB: Math.round(totalMem / 1024 / 1024),
usedMB: Math.round(usedMem / 1024 / 1024),
usagePercent: memPercent
},
uptime: Math.floor((Date.now() - SERVER_START_TIME.getTime()) / 1000),
heartbeatCount: heartbeats.length,
services: checkOnlineStatus()
};
// 添加到内存数组
patrolHistory.push(patrolRecord);
if (patrolHistory.length > 50) {
patrolHistory.shift();
}
// 持久化到文件
let logs = loadLogs();
logs.push(patrolRecord);
logs = cleanOldLogs(logs); // 清理旧记录
saveLogs(logs);
console.log(`[PATROL] 巡检完成 - 内存使用: ${memPercent}% - 已保存到health-log.json`);
}, 60000);
// GET /health/history - 查询巡检历史(支持?days=N
app.get('/health/history', (req, res) => {
const days = parseInt(req.query.days) || 7;
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - days);
let logs = loadLogs();
const filteredLogs = logs.filter(log => new Date(log.timestamp) > cutoff);
res.json({
total: filteredLogs.length,
days: days,
logs: filteredLogs
});
});
console.log('💾 巡检数据持久化已启动保留7天');