You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 08:50:22