server v3.3: 并行硬件采集修复

This commit is contained in:
bingshuo 2026-05-25 23:16:34 +08:00
parent 0600063e21
commit 53dd67a2ea

View File

@ -11,7 +11,7 @@ const fs = require('fs');
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(__dirname));
const DATA_DIR = '/opt/zhuyuan/data';
try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch(e) {}
@ -27,19 +27,12 @@ const SERVERS = [
{ code: "AW-GZ-001", name: "企业主服务器 · 广州", ip: "43.139.251.175", key: "zy_gtw_cab20e8c1b957aaad7507bf542231521bba30665461aa540", port: 3910, spec: { cpu: 4, mem_gb: 16, disk_gb: 80 } },
{ code: "AW-SH-002", name: "企业灾备 · 上海", ip: "124.222.54.198", key: "zy_gtw_242f1dd3b14c1260fb01196083abde3dcb85e53532d27666", port: 3910, spec: { cpu: 4, mem_gb: 4, disk_gb: 40 } },
{ code: "AW-GZ-003", name: "企业灾备 · 广州", ip: "119.29.181.132", key: "zy_gtw_8c9a8b439af823690ae6ad57f52734771ff39493ab986032", port: 3910, spec: { cpu: 2, mem_gb: 8, disk_gb: 50 } },
// 页页 · 硅谷(待接入)
// { code: "YY-SV-001", name: "页页 · 硅谷", ip: "TBD", key: "TBD", port: 3910, spec: { cpu: 2, mem_gb: 4, disk_gb: 50 } },
// 之之 · 待定
// { code: "ZZ-XX-001", name: "之之 · 待定", ip: "TBD", key: "TBD", port: 3910, spec: { cpu: 2, mem_gb: 4, disk_gb: 50 } },
];
const tempKeys = {};
const authQueue = [];
const hwCache = {};
// ── 硬件缓存 (60秒TTL) ──
const hwCache = {}; // { code: { data, ts } }
// ── Gatekeeper exec 通用函数 ──
async function gatekeeperExec(srv, cmd, timeoutMs) {
timeoutMs = timeoutMs || 10000;
const d = JSON.stringify({ cmd });
@ -59,27 +52,21 @@ async function gatekeeperExec(srv, cmd, timeoutMs) {
});
}
// ── 硬件采集(通过 Gatekeeper exec ──
async function fetchHardware(srv) {
const now = Date.now();
const cached = hwCache[srv.code];
if (cached && (now - cached.ts) < 60000) return cached.data; // 60s cache
if (cached && (now - cached.ts) < 60000) return cached.data;
try {
const cmd = "echo 'CPU_CORES:'$(nproc);echo 'UPTIME_S:'$(awk '{print int($1)}' /proc/uptime 2>/dev/null||echo 0);free -m|awk '/^Mem:/{printf \"MEM_TOTAL:%d\\nMEM_USED:%d\\nMEM_AVAIL:%d\\n\",$2,$3,$7}';cat /proc/loadavg 2>/dev/null|awk '{printf \"LOAD1:%.2f\\nLOAD5:%.2f\\nLOAD15:%.2f\\n\",$1,$2,$3}';df -h / 2>/dev/null|awk 'NR==2{printf \"DISK_TOTAL:%s\\nDISK_USED:%s\\nDISK_AVAIL:%s\\nDISK_PCT:%s\\n\",$2,$3,$4,$5}'";
const r = await gatekeeperExec(srv, cmd, 8000);
if (!r.ok || !r.body || !r.body.stdout) {
const fb = { cpu_cores: srv.spec.cpu || 2, uptime_h: 0, mem_total_mb: (srv.spec.mem_gb || 4) * 1024, mem_used_mb: 0, mem_avail_mb: (srv.spec.mem_gb || 4) * 1024, load_1min: 0, load_5min: 0, load_15min: 0, disk_total: (srv.spec.disk_gb || 50) + 'G', disk_used: '0G', disk_avail: (srv.spec.disk_gb || 50) + 'G', disk_pct: '0%', source: 'spec' };
hwCache[srv.code] = { data: fb, ts: now };
return fb;
}
const stdout = r.body.stdout || '';
const hw = { source: 'live' };
const m = (re) => { const match = stdout.match(re); return match ? match[1] : null; };
hw.cpu_cores = parseInt(m(/CPU_CORES:(\d+)/)) || srv.spec.cpu || 2;
hw.uptime_h = Math.floor((parseInt(m(/UPTIME_S:(\d+)/)) || 0) / 3600);
hw.mem_total_mb = parseInt(m(/MEM_TOTAL:(\d+)/)) || (srv.spec.mem_gb || 4) * 1024;
@ -92,11 +79,9 @@ async function fetchHardware(srv) {
hw.disk_used = m(/DISK_USED:(\S+)/) || '0G';
hw.disk_avail = m(/DISK_AVAIL:(\S+)/) || hw.disk_total;
hw.disk_pct = m(/DISK_PCT:(\S+)/) || '0%';
hw.mem_pct = hw.mem_total_mb > 0 ? Math.round(hw.mem_used_mb / hw.mem_total_mb * 100) : 0;
hw.cpu_pct = hw.cpu_cores > 0 ? Math.min(Math.round(hw.load_1min / hw.cpu_cores * 100), 100) : 0;
hw.disk_used_pct = parseInt(hw.disk_pct) || 0;
hwCache[srv.code] = { data: hw, ts: now };
return hw;
} catch (e) {
@ -106,14 +91,13 @@ async function fetchHardware(srv) {
}
}
// ── 操作日志滚动记录最多100条 ──
const opLogs = [];
function addOpLog(code, name, action, detail, level) {
opLogs.unshift({ time: new Date().toISOString(), code, name, action, detail, level: level || 'info' });
if (opLogs.length > 100) opLogs.length = 100;
}
// ========== 基础 API ==========
// ========== API ==========
app.get('/api/servers', async (req, res) => {
const withHw = req.query.hw !== '0';
@ -134,12 +118,14 @@ app.get('/api/servers', async (req, res) => {
});
}));
for (const r of pingResults) {
const hwTasks = pingResults.filter(r => r.alive && withHw).map(async (r) => {
const srv = SERVERS.find(s => s.code === r.code);
r.last_seen = new Date().toISOString();
if (r.alive && withHw) {
try { r.hardware = await fetchHardware(srv); } catch(e) { r.hardware = null; }
}
try { r.hardware = await fetchHardware(srv); } catch(e) { r.hardware = null; }
});
await Promise.all(hwTasks);
for (const r of pingResults) {
if (!r.last_seen) r.last_seen = new Date().toISOString();
}
res.json({ ok: true, servers: pingResults, timestamp: new Date().toISOString(), total: SERVERS.length });
@ -176,19 +162,12 @@ app.get('/api/summary', async (req, res) => {
req.write(data); req.end();
});
}));
const aliveCount = pingResults.filter(r => r.alive).length;
const totalCpu = SERVERS.reduce((sum, s) => sum + (s.spec.cpu || 2), 0);
const totalMem = SERVERS.reduce((sum, s) => sum + (s.spec.mem_gb || 4), 0);
const totalDisk = SERVERS.reduce((sum, s) => sum + (s.spec.disk_gb || 50), 0);
const alertCount = opLogs.filter(l => l.level === 'error').length;
res.json({
ok: true, total_servers: SERVERS.length, alive: aliveCount,
offline: SERVERS.length - aliveCount, alerts: alertCount,
total_cpu_cores: totalCpu, total_mem_gb: totalMem, total_disk_gb: totalDisk,
timestamp: new Date().toISOString()
});
res.json({ ok: true, total_servers: SERVERS.length, alive: aliveCount, offline: SERVERS.length - aliveCount, alerts: alertCount, total_cpu_cores: totalCpu, total_mem_gb: totalMem, total_disk_gb: totalDisk, timestamp: new Date().toISOString() });
});
app.post('/api/knock', (req, res) => {
@ -226,14 +205,11 @@ app.post('/api/exec', async (req, res) => {
if (!temp_key || !target || !cmd) return res.status(400).json({ error: '缺少参数' });
const ke = tempKeys[temp_key]; if (!ke || Date.now() > ke.expires_at) return res.status(401).json({ error: '密钥无效' });
const s = SERVERS.find(s => s.code === target); if (!s) return res.status(404).json({ error: '未找到' });
try {
const r = await gatekeeperExec(s, cmd, 30000);
addOpLog(s.code, s.name, 'EXEC', cmd.slice(0, 60), 'info');
res.json({ ok: true, result: r });
} catch (e) { res.status(500).json({ error: e.message }); }
try { const r = await gatekeeperExec(s, cmd, 30000); addOpLog(s.code, s.name, 'EXEC', cmd.slice(0, 60), 'info'); res.json({ ok: true, result: r }); }
catch (e) { res.status(500).json({ error: e.message }); }
});
// ========== 节点池 API ==========
// 节点池 API
const POOL_REGISTRY_FILE = DATA_DIR + '/pool-registry.json';
const JOIN_TOKENS_FILE = DATA_DIR + '/join-tokens.json';
function loadPoolRegistry() { try { if (fs.existsSync(POOL_REGISTRY_FILE)) return JSON.parse(fs.readFileSync(POOL_REGISTRY_FILE, 'utf-8')); } catch(e) {} return { active: {}, pending: {}, history: [] }; }
@ -284,11 +260,10 @@ app.post('/api/pool/report', (req, res) => {
res.json({ ok: true, node_code });
});
// ========== 启动 ==========
const PORT = process.env.CONSOLE_PORT || 3920;
app.listen(PORT, '127.0.0.1', () => {
console.log('光湖主控台 v3.3 · ' + SERVERS.length + '台 | SG-001:' + PORT);
console.log(' 硬件采集: 60s缓存 · 操作日志: 100条上限');
console.log(' 硬件采集: 并行+60s缓存 · 操作日志: 100条上限');
});
setInterval(() => {