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

如何实现MongoDB实时数据获取与前端仪表盘自动更新?

解决"Cannot set headers after they are sent to the client"错误并实现仪表盘实时更新

错误原因

你遇到的错误源于HTTP协议的单次请求-响应模型:当调用res.json(latestDoc)后,服务器已完成本次请求的响应,HTTP连接的响应阶段结束。后续在change stream循环里再次调用res.json(change),相当于试图给同一个请求重复发送响应,违反了HTTP的规则,因此触发该错误。

要实现仪表盘自动更新,必须使用服务器主动推送技术,推荐用Server-Sent Events(SSE)——它适合这种单向、低延迟的状态推送场景,比WebSocket更轻量化。

解决方案:用SSE实现实时推送

后端代码修改

将/get-readings接口改造为SSE接口,完整代码如下:

const express = require('express');
const mongoose = require('mongoose');
const app = express();

// 中间件
app.use(express.json());
app.use(cors());

const port = 3000; // 补充端口号
const uri = '你的MongoDB连接字符串'; // 补充连接字符串

const sensorSchema = { 
    temperature: Number,
    humidity: Number,
    ec: Number,
    date: Date,
};

const Sensor = mongoose.model("Sensor", sensorSchema); 

// 数据库连接成功后再启动服务器
const connectDB = async () => {
    try {
        await mongoose.connect(uri);
        console.log('MongoDB connected');
        app.listen(port, () => {
            console.log(`Server started at http://localhost:${port}`);
        });
    } catch (error) {
        console.error('MongoDB连接失败:', error);
    }
};
connectDB();

// 调整变更流过滤规则:只监听数据插入或更新操作
const pipeline = [
    { $match: { operationType: { $in: ['insert', 'update'] } } }
];

app.get('/get-readings', async (req, res) => {
    // 设置SSE响应头,保持长连接
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    res.flushHeaders();

    try {
        // 1. 先发送最新传感器数据作为初始值
        const latestDoc = await Sensor.findOne({}, {}, { sort: { date: -1 } });
        if (latestDoc) {
            res.write(`data: ${JSON.stringify(latestDoc)}\n\n`);
        }

        // 2. 监听MongoDB变更流
        const changeStream = Sensor.watch(pipeline);

        // 收到变更时推送最新数据到前端
        changeStream.on('change', async () => {
            const newLatestDoc = await Sensor.findOne({}, {}, { sort: { date: -1 } });
            if (newLatestDoc) {
                res.write(`data: ${JSON.stringify(newLatestDoc)}\n\n`);
            }
        });

        // 3. 客户端断开连接时关闭变更流,释放资源
        req.on('close', () => {
            console.log('客户端断开连接');
            changeStream.close();
            res.end();
        });

    } catch (error) {
        console.error('获取传感器数据失败:', error);
        res.write(`event: error\ndata: ${JSON.stringify({ error: '内部服务器错误' })}\n\n`);
        res.end();
    }
});

前端接收SSE代码示例

在浏览器端用JavaScript接收推送,实时更新仪表盘:

// 建立SSE长连接
const eventSource = new EventSource('http://localhost:3000/get-readings');

// 接收初始数据或更新数据
eventSource.onmessage = function(event) {
    const sensorData = JSON.parse(event.data);
    // 更新页面DOM元素
    document.getElementById('temperature').textContent = sensorData.temperature;
    document.getElementById('humidity').textContent = sensorData.humidity;
    document.getElementById('ec').textContent = sensorData.ec;
};

// 处理连接错误
eventSource.onerror = function(error) {
    console.error('SSE连接异常:', error);
    eventSource.close();
};

关键注意事项

  • 原代码中的pipeline里runtime字段不存在于你的文档结构中,已调整为过滤insert和update操作,确保只监听有效数据变更。
  • SSE通过长连接推送数据,必须处理客户端断开事件,及时关闭MongoDB变更流,避免服务器资源泄漏。
  • SSE接口不能使用res.json(),需用res.write()发送符合SSE格式的内容(格式为data: [JSON字符串]\n\n)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 18:25:22