diff --git a/server/README.md b/server/README.md index 9c9895c..523e2ef 100644 --- a/server/README.md +++ b/server/README.md @@ -145,8 +145,9 @@ npm run api # 启动 server/index.js,默认端口 3001 - **路径**:`/ws`(与 HTTP 同端口)。 - **连接时**:服务端发送一条 `{ type: 'situation', data, stats }`。 -- **定时广播**:`setInterval(broadcastSituation, 3000)` 每 3 秒向所有已连接客户端推送最新 `getSituation()` + `getStats()`。 -- **爬虫通知**:POST `/api/crawler/notify` 会立即执行一次 `broadcastSituation()`,不必等 3 秒。 +- **定时广播**:按 `BROADCAST_INTERVAL_MS`(默认 30 秒)轮询;**仅当数据有变化**(以 `situation.updated_at` + `situation_update` 条数为版本)时才执行 `getSituation()` + `getStats()` 并推送,避免无变更时重复查库和推送、降低负载。 +- **即时广播**:以下情况会立即推送一次(不等待定时间隔):爬虫 POST `/api/crawler/notify`、修订页保存(PUT/PATCH/POST/DELETE `/api/edit/*`)。 +- **环境变量**:`BROADCAST_INTERVAL_MS=0` 可关闭定时轮询,仅依赖即时广播;设为 `3000` 可恢复为每 3 秒检查一次(仍仅在数据变化时推送)。 --- diff --git a/server/index.js b/server/index.js index 168b2a1..e594f75 100644 --- a/server/index.js +++ b/server/index.js @@ -55,19 +55,42 @@ wss.on('connection', (ws) => { ws.send(JSON.stringify({ type: 'situation', data: getSituation(), stats: getStats() })) }) -function broadcastSituation() { +// 仅用 situation.updated_at + situation_update 条数做“版本”,避免无变更时重复查库和推送 +function getBroadcastVersion() { + try { + const meta = db.prepare('SELECT updated_at FROM situation WHERE id = 1').get() + const row = db.prepare('SELECT COUNT(*) as c FROM situation_update').get() + return `${meta?.updated_at || ''}_${row?.c ?? 0}` + } catch (_) { + return '' + } +} + +let lastBroadcastVersion = null + +function broadcastSituation(force = false) { + if (!force && wss.clients.size === 0) return + const version = getBroadcastVersion() + if (!force && version === lastBroadcastVersion) return try { const data = JSON.stringify({ type: 'situation', data: getSituation(), stats: getStats() }) wss.clients.forEach((c) => { if (c.readyState === 1) c.send(data) }) + lastBroadcastVersion = version } catch (_) {} } -app.set('broadcastSituation', broadcastSituation) + +app.set('broadcastSituation', () => broadcastSituation(true)) + if (typeof routes.setBroadcastSituation === 'function') { - routes.setBroadcastSituation(broadcastSituation) + routes.setBroadcastSituation(() => broadcastSituation(true)) +} + +const BROADCAST_INTERVAL_MS = Math.max(0, parseInt(process.env.BROADCAST_INTERVAL_MS, 10) || 30000) +if (BROADCAST_INTERVAL_MS > 0) { + setInterval(() => broadcastSituation(false), BROADCAST_INTERVAL_MS) } -setInterval(broadcastSituation, 3000) // 供爬虫调用:先从磁盘重载 DB(纳入爬虫写入),再更新 updated_at 并立即广播 function notifyCrawlerUpdate() {