如何实现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
相关产品推荐
相关产品推荐

