如何获取Node.js(Express)网站的活跃访客数量?
实现Express网站活跃访客统计方案
不用第三方工具的话,可以通过前后端配合的心跳机制+后端活跃用户管理来实现,完全满足你的需求,以下是具体步骤和代码:
核心逻辑
- 对"刚发起请求的用户":后端自动记录每个请求的访客标识,更新活跃时间
- 对"浏览器窗口打开/操作的用户":前端定时发心跳请求,后端维持活跃状态
- 后端定时清理超时(比如1分钟无交互)的用户,保证统计的是真实活跃访客
具体实现
1. 后端(Express)代码
用内存存储(小流量足够,大流量可替换为Redis)维护活跃用户,配合Express中间件和接口:
const express = require('express'); const cookieParser = require('cookie-parser'); const crypto = require('crypto'); const app = express(); app.use(cookieParser()); // 存储活跃用户:key=访客ID,value=最后活跃时间戳 const activeUsers = new Map(); // 超时时间:1分钟(用户1分钟无请求/心跳则视为非活跃) const INACTIVE_TIMEOUT = 60 * 1000; // 生成唯一访客ID function generateVisitorId() { return crypto.randomBytes(16).toString('hex'); } // 中间件:处理访客ID,更新活跃状态 app.use((req, res, next) => { let visitorId = req.cookies.visitorId; if (!visitorId) { visitorId = generateVisitorId(); // 设置cookie,有效期1天 res.cookie('visitorId', visitorId, { maxAge: 24 * 60 * 60 * 1000, httpOnly: true }); } // 更新该访客的最后活跃时间 activeUsers.set(visitorId, Date.now()); req.visitorId = visitorId; next(); }); // 心跳接口:前端定时调用,更新活跃状态 app.post('/api/heartbeat', (req, res) => { const visitorId = req.visitorId; if (visitorId) { activeUsers.set(visitorId, Date.now()); res.status(200).send({ success: true }); } else { res.status(400).send({ success: false, message: 'No visitor ID' }); } }); // 获取活跃访客数量的接口(给管理员用) app.get('/api/active-visitors', (req, res) => { const now = Date.now(); // 过滤出未超时的用户 const count = Array.from(activeUsers.values()).filter(timestamp => now - timestamp <= INACTIVE_TIMEOUT).length; res.send({ activeCount: count }); }); // 定时清理过期用户(每分钟执行一次) setInterval(() => { const now = Date.now(); activeUsers.forEach((timestamp, visitorId) => { if (now - timestamp > INACTIVE_TIMEOUT) { activeUsers.delete(visitorId); } }); }, 60 * 1000); app.listen(3000, () => console.log('Server running on port 3000'));
2. 前端代码
在页面中加入心跳逻辑,配合窗口焦点事件优化准确性:
let heartbeatTimer = null; const HEARTBEAT_INTERVAL = 30 * 1000; // 每30秒发一次心跳 // 发送心跳请求 function sendHeartbeat() { fetch('/api/heartbeat', { method: 'POST', credentials: 'include' // 携带cookie }).catch(err => console.log('Heartbeat failed:', err)); } // 启动心跳 function startHeartbeat() { if (!heartbeatTimer) { sendHeartbeat(); // 立即发一次 heartbeatTimer = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL); } } // 停止心跳 function stopHeartbeat() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } } // 页面加载完成后启动心跳 window.addEventListener('load', startHeartbeat); // 窗口失去焦点时暂停心跳,恢复时重启 window.addEventListener('blur', stopHeartbeat); window.addEventListener('focus', startHeartbeat); // 页面关闭时尝试发送结束请求(可选,不一定可靠) window.addEventListener('beforeunload', () => { fetch('/api/heartbeat-end', { method: 'POST', credentials: 'include', keepalive: true // 保证页面关闭时请求能发出 }); });
如果需要实现/api/heartbeat-end接口,后端可添加:
app.post('/api/heartbeat-end', (req, res) => { const visitorId = req.visitorId; if (visitorId) { activeUsers.delete(visitorId); } res.status(200).send({ success: true }); });
注意事项
- 内存存储重启服务会丢失数据,若需持久化可改用Redis替代
Map - 心跳间隔和超时时间可根据需求调整,比如心跳20秒、超时1.5分钟
- 多实例部署时,必须用Redis这类分布式存储,否则各实例的活跃数据不互通
内容的提问来源于stack exchange,提问作者Dim Vai
相关产品推荐
相关产品推荐

