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

MongoDB数据变更时实现多用户HTML/Pug页面实时更新方案咨询

实现MongoDB数据变更时的实时页面更新方案(无需Socket.io)

方案一:MongoDB Change Streams + Server-Sent Events(SSE)

这是最贴合需求的轻量方案,利用MongoDB自带的变更监听能力,结合SSE实现服务器主动推送更新。

  • 核心逻辑:

    1. 服务器监听用户B集合的变更事件(新增/更新内容)
    2. 当检测到变更时,查询所有关注该用户的用户列表
    3. 通过SSE连接向这些用户的客户端推送更新通知
    4. 客户端接收通知后,实时更新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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 17:20:58