如何高效统计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
相关产品推荐
相关产品推荐

