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

如何高效统计Node.js博客中未登录用户的文章浏览量

未登录用户文章浏览量高效统计方案(Node.js/Express/MongoDB)

核心思路

针对你的约束(不用token、客户端不存数据、避免IP直存数据库),结合你提到的「离开页面统计」技巧,推荐以下方案:

  • 用户识别:用「IP + User-Agent」的哈希值作为用户唯一标识,比单纯IP更精准(同一IP下多设备/浏览器可区分),同时避免直接存储敏感信息
  • 高效去重:用Redis做内存缓存(比MongoDB读写快得多),缓存用户对单篇文章的浏览记录,设置过期时间(如1小时),只有当缓存不存在时才更新MongoDB的浏览量
  • 统计时机:采用页面卸载时发送请求,避免用户刷新页面重复统计,同时用navigator.sendBeacon替代普通AJAX,确保请求不会被浏览器中断

具体实现代码

前端代码(页面统计逻辑)

window.addEventListener('beforeunload', () => {
  const articleId = '替换为当前文章的实际ID';
  // 使用sendBeacon保证请求在页面卸载时能被正常发送
  navigator.sendBeacon(`/api/articles/${articleId}/view`, JSON.stringify({}));
});

后端代码(Express + Redis + MongoDB)

1. 依赖安装

npm install express mongoose redis crypto

2. Redis连接配置

const redis = require('redis');
// 初始化Redis客户端(根据你的Redis实例配置调整)
const redisClient = redis.createClient({
  host: 'localhost',
  port: 6379,
  // 如果有密码需添加 password: 'your-redis-password'
});

redisClient.on('error', (err) => console.error('Redis连接错误:', err));
await redisClient.connect();

3. Express路由处理

const express = require('express');
const router = express.Router();
const crypto = require('crypto');
const Article = require('../models/Article'); // 你的MongoDB文章模型
const redisClient = require('./redis-client'); // 引入上述Redis连接

// 生成用户唯一哈希标识
function getUserHash(req) {
  // 获取真实用户IP(若部署在反向代理后,需设置app.set('trust proxy', true))
  const userIp = req.ip || req.connection.remoteAddress;
  const userAgent = req.get('User-Agent') || 'unknown';
  // 生成SHA256哈希,避免直接存储IP和User-Agent
  return crypto.createHash('sha256').update(`${userIp}-${userAgent}`).digest('hex');
}

// 浏览量统计接口
router.post('/api/articles/:id/view', async (req, res) => {
  const articleId = req.params.id;
  const userHash = getUserHash(req);
  const cacheKey = `article:${articleId}:viewer:${userHash}`;

  try {
    // 检查缓存:若用户已在有效期内统计过,直接返回
    const hasViewed = await redisClient.exists(cacheKey);
    if (hasViewed) {
      return res.status(200).json({ success: true, message: '已统计过浏览量' });
    }

    // 更新MongoDB中的文章浏览量
    await Article.findByIdAndUpdate(articleId, { $inc: { views: 1 } }, { new: true });

    // 设置缓存,有效期1小时(3600秒),可根据业务调整
    await redisClient.setEx(cacheKey, 3600, '1');

    res.status(200).json({ success: true, message: '浏览量统计成功' });
  } catch (error) {
    console.error('浏览量统计失败:', error);
    // 即使数据库/缓存出错,也返回成功,避免影响用户体验
    res.status(200).json({ success: true });
  }
});

module.exports = router;

4. MongoDB文章模型示例

const mongoose = require('mongoose');

const articleSchema = new mongoose.Schema({
  title: { type: String, required: true },
  content: { type: String, required: true },
  views: { type: Number, default: 0 }, // 浏览数字段
  // 其他业务字段...
});

module.exports = mongoose.model('Article', articleSchema);

关键优化点

  • 反向代理下的IP获取:如果你的应用部署在Nginx等反向代理后,需要在Express中添加app.set('trust proxy', true),才能通过req.ip拿到用户真实IP
  • 缓存过期时间:可根据业务需求调整,比如希望用户24小时内同一文章只统计一次,就把过期时间设为86400秒
  • 隐私保护:用哈希存储用户标识,避免直接存储IP和User-Agent,符合数据隐私规范
  • 异常容错:统计逻辑出错时不返回错误,避免影响用户的页面体验

内容的提问来源于stack exchange,提问作者Saswat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 00:15:39