MongoDB数据变更时实现多用户HTML/Pug页面实时更新方案咨询
实现MongoDB数据变更时的实时页面更新方案(无需Socket.io)
方案一:MongoDB Change Streams + Server-Sent Events(SSE)
这是最贴合需求的轻量方案,利用MongoDB自带的变更监听能力,结合SSE实现服务器主动推送更新。
核心逻辑:
- 服务器监听用户B集合的变更事件(新增/更新内容)
- 当检测到变更时,查询所有关注该用户的用户列表
- 通过SSE连接向这些用户的客户端推送更新通知
- 客户端接收通知后,实时更新Pug/HTML页面
代码示例:
服务器端(Node.js + MongoDB):const { MongoClient } = require('mongodb'); const express = require('express'); const app = express(); // 存储SSE连接,键为用户ID,值为响应对象 const sseConnections = new Map(); // SSE连接接口 app.get('/sse', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const userId = req.query.userId; sseConnections.set(userId, res); // 客户端断开连接时清理 req.on('close', () => { sseConnections.delete(userId); }); }); // 监听MongoDB变更 async function watchUserUpdates() { const client = await MongoClient.connect('mongodb://localhost:27017'); const db = client.db('your-db-name'); const userBCollection = db.collection('userB'); const changeStream = userBCollection.watch([ { $match: { operationType: { $in: ['insert', 'update'] } } } ]); changeStream.on('change', async (change) => { const updatedUserB = change.fullDocument || change.updateDescription.updatedFields; const userId = change.documentKey._id; // 查询所有关注该用户的用户 const followers = await db.collection('userA').find({ following: userId }).toArray(); // 向每个关注者推送更新 followers.forEach(follower => { const res = sseConnections.get(follower._id.toString()); if (res) { res.write(`data: ${JSON.stringify(updatedUserB)}\n\n`); } }); }); } watchUserUpdates(); app.listen(3000);客户端:
// 假设当前登录用户ID为currentUserId const eventSource = new EventSource(`/sse?userId=${currentUserId}`); eventSource.onmessage = (event) => { const updatedContent = JSON.parse(event.data); // 更新页面内容,比如替换对应模块 const contentContainer = document.getElementById('followed-user-content'); // 这里根据Pug渲染的结构更新,示例用HTML contentContainer.innerHTML = ` <div class="latest-post"> <h3>${updatedContent.title}</h3> <p>${updatedContent.content}</p> <time>${new Date(updatedContent.updatedAt).toLocaleString()}</time> </div> `; };
方案二:长轮询(Long Polling)
如果SSE兼容性有顾虑(比如旧浏览器),长轮询是更通用的替代方案,客户端发起请求后,服务器挂起连接直到有更新或超时,再返回结果。
代码示例:
客户端:async function longPollUpdates(followedUserId) { try { const response = await fetch(`/long-poll-updates?followedUserId=${followedUserId}`); const updates = await response.json(); if (updates) { // 更新页面 updatePageContent(updates); } // 完成后立即发起下一次请求 longPollUpdates(followedUserId); } catch (err) { // 请求失败时延迟重试 setTimeout(() => longPollUpdates(followedUserId), 5000); } } // 启动长轮询 longPollUpdates('userB-id-here'); function updatePageContent(content) { const container = document.getElementById('followed-content'); container.insertAdjacentHTML('afterbegin', ` <div class="content-item">${content.content}</div> `); }服务器端:
app.get('/long-poll-updates', async (req, res) => { const followedUserId = req.query.followedUserId; const timeout = 30000; // 30秒超时 // 设置超时定时器 const timeoutId = setTimeout(() => { res.json(null); }, timeout); // 监听变更,有更新时返回 const changeStream = db.collection('userB').watch([ { $match: { operationType: { $in: ['insert', 'update'] }, documentKey: { _id: followedUserId } } } ]); changeStream.once('change', (change) => { clearTimeout(timeoutId); res.json(change.fullDocument); changeStream.close(); }); });
补充:关于Socket.io的说明
如果你之前觉得Socket.io不符合需求,可能是担心广播的广泛性——其实Socket.io支持定向推送:可以给每个用户的socket连接绑定用户ID,当用户B更新时,只向关注B的用户对应的socket发送消息,精准度完全满足需求。如果后续考虑优化,这也是一个成熟的方案。
内容的提问来源于stack exchange,提问作者Avi
相关产品推荐
相关产品推荐

