From e8e23c5af8171da02302e4ca9b3cba3bde60fe30 Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Sun, 5 Apr 2026 21:13:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Health=E5=81=A5=E5=BA=B7?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/cache.js | 11 +++++++++++ server/db.js | 13 ++++++++++++- server/index.js | 46 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/server/cache.js b/server/cache.js index 422f124..af14cc7 100644 --- a/server/cache.js +++ b/server/cache.js @@ -63,6 +63,17 @@ const cache = { } catch (e) { // ignore } + }, + + async checkHealth() { + if (!redis) return { status: 'down', error: 'Valkey client not initialized' }; + try { + const result = await redis.ping(); + if (result === 'PONG') return { status: 'up' }; + return { status: 'down', error: 'Invalid ping response' }; + } catch (e) { + return { status: 'down', error: e.message }; + } } }; diff --git a/server/db.js b/server/db.js index 1c49bd4..4f9d48c 100644 --- a/server/db.js +++ b/server/db.js @@ -18,9 +18,20 @@ function initPool() { }); } +async function checkHealth() { + try { + if (!pool) return { status: 'down', error: 'Database pool not initialized' }; + await pool.query('SELECT 1'); + return { status: 'up' }; + } catch (err) { + return { status: 'down', error: err.message }; + } +} + initPool(); module.exports = { query: (...args) => pool.query(...args), - initPool + initPool, + checkHealth }; diff --git a/server/index.js b/server/index.js index 73468af..3bf62a0 100644 --- a/server/index.js +++ b/server/index.js @@ -51,6 +51,50 @@ async function checkDb() { } checkDb(); + +// --- Health API --- +app.get('/health', async (req, res) => { + try { + const dbStatus = await db.checkHealth(); + const cacheStatus = await cache.checkHealth(); + const isAllOk = dbStatus.status === 'up' && cacheStatus.status === 'up'; + + const healthInfo = { + status: isAllOk ? 'ok' : 'error', + timestamp: new Date().toISOString(), + service: { + status: 'running', + uptime: Math.floor(process.uptime()), + memory_usage: { + rss: Math.floor(process.memoryUsage().rss / 1024 / 1024) + ' MB', + heapTotal: Math.floor(process.memoryUsage().heapTotal / 1024 / 1024) + ' MB' + }, + node_version: process.version + }, + checks: { + database: { + name: 'MySQL', + status: dbStatus.status, + message: dbStatus.error || 'Connected' + }, + valkey: { + name: 'Valkey (Redis)', + status: cacheStatus.status, + message: cacheStatus.error || 'Connected' + } + } + }; + + if (isAllOk) { + res.json(healthInfo); + } else { + res.status(500).json(healthInfo); + } + } catch (err) { + res.status(500).json({ status: 'error', message: err.message }); + } +}); + // --- Auth API --- app.post('/api/auth/login', async (req, res) => { const { username, password } = req.body; @@ -313,7 +357,7 @@ app.post('/api/setup/admin', async (req, res) => { // Middleware to protect routes & enforce setup app.use(async (req, res, next) => { // Allow system files and setup APIs - if (req.path.startsWith('/api/setup') || req.path === '/init.html' || req.path.startsWith('/css/') || req.path.startsWith('/js/') || req.path.startsWith('/fonts/')) { + if (req.path === '/health' || req.path.startsWith('/api/setup') || req.path === '/init.html' || req.path.startsWith('/css/') || req.path.startsWith('/js/') || req.path.startsWith('/fonts/')) { return next(); }