基于MongoDB、Mongoose和Express.js的后台通知系统实现问询
大规模用户后台通知系统实现方案咨询
我正在开发一个拥有10万+用户的后台管理系统,计划基于MongoDB、Mongoose和Express.js实现通知功能。需求是支持从后台向指定用户或所有用户发送统一内容的通知,同时希望避免为每个用户创建单独的通知对象,寻求高效的实现方案。
核心需求如下:
- 支持向指定用户或所有用户发送统一内容的通知;
- 尽量减少为每个用户创建单独的通知对象;
- 通知系统需适配10万+用户的大规模场景。
我已有一个MongoDB通知Schema,但需要针对通知逻辑处理、大用户量下的管理方式获取指导,包括Schema结构优化、Express.js路由设计及通知逻辑实现的建议。
当前的Schema代码如下:
// My current schema looks like this: const mongoose = require('mongoose'); const notificationSchema = new mongoose.Schema({ text: { type: String, required: true }, users: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // Other relevant fields }); const Notification = mongoose.model('Notification', notificationSchema);
一、Schema结构优化
你的当前Schema在发送全局通知时会遇到瓶颈——把10万+用户ID存入数组不仅占用大量存储,写入性能也会急剧下降。建议调整为区分全局/定向通知的结构,从根源上避免冗余数据:
const mongoose = require('mongoose'); const notificationSchema = new mongoose.Schema({ text: { type: String, required: true }, // 明确通知类型,避免模糊查询 type: { type: String, enum: ['global', 'targeted'], required: true }, // 仅定向通知时填充,存储目标用户ID集合 targetUsers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // 核心元数据,配合索引提升查询效率 createdAt: { type: Date, default: Date.now, index: true }, expiresAt: { type: Date, index: true }, isActive: { type: Boolean, default: true } }); // 创建复合索引,覆盖高频查询场景 notificationSchema.index({ type: 1, isActive: 1, createdAt: -1 }); notificationSchema.index({ type: 1, targetUsers: 1, isActive: 1 }); // TTL索引自动清理过期通知 notificationSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 }); const Notification = mongoose.model('Notification', notificationSchema);
优化优势:
- 全局通知无需存储任何用户ID,单条记录覆盖所有用户,存储成本趋近于0;
- 定向通知仅存储目标用户ID,小范围用户场景下写入/查询效率更高;
- 复合索引直接命中用户端查询条件,避免全表扫描。
二、Express.js路由设计
拆分后台管理路由和用户端查询路由,严格区分权限边界:
后台管理路由(需管理员权限校验)
const express = require('express'); const router = express.Router(); const Notification = require('../models/Notification'); // 发送全局通知 router.post('/notifications/global', async (req, res) => { try { const { text, expiresAt } = req.body; const notification = new Notification({ text, type: 'global', expiresAt: expiresAt ? new Date(expiresAt) : undefined }); await notification.save(); res.status(201).json({ message: '全局通知发送成功', notification }); } catch (err) { res.status(500).json({ error: err.message }); } }); // 发送定向通知 router.post('/notifications/targeted', async (req, res) => { try { const { text, targetUsers, expiresAt } = req.body; // 可添加用户ID合法性校验(如查询User集合确认存在) const notification = new Notification({ text, type: 'targeted', targetUsers, expiresAt: expiresAt ? new Date(expiresAt) : undefined }); await notification.save(); res.status(201).json({ message: '定向通知发送成功', notification }); } catch (err) { res.status(500).json({ error: err.message }); } }); // 更新通知状态(启用/禁用) router.patch('/notifications/:id', async (req, res) => { try { const { isActive } = req.body; const notification = await Notification.findByIdAndUpdate( req.params.id, { isActive }, { new: true } ); if (!notification) return res.status(404).json({ error: '通知不存在' }); res.json({ message: '通知状态更新成功', notification }); } catch (err) { res.status(500).json({ error: err.message }); } }); module.exports = router;
用户端查询路由
const express = require('express'); const router = express.Router(); const Notification = require('../models/Notification'); // 获取当前用户有效通知 router.get('/notifications', async (req, res) => { try { const userId = req.user._id; // 假设已通过认证中间件获取用户ID const notifications = await Notification.find({ isActive: true, $or: [ { type: 'global' }, { type: 'targeted', targetUsers: userId } ], // 可选:过滤过期通知,前端可通过参数控制是否显示 ...(req.query.showExpired !== 'true' && { expiresAt: { $gt: new Date() } }) }).sort({ createdAt: -1 }); res.json(notifications); } catch (err) { res.status(500).json({ error: err.message }); } }); module.exports = router;
三、核心逻辑实现要点
- 全局通知查询优化:用户查询时直接匹配
type: 'global'和isActive: true,无需关联用户ID,查询速度不受用户量影响。 - 定向通知批量处理:如果定向通知覆盖用户量超过1万,建议用消息队列(如BullMQ)异步写入,避免HTTP请求超时:
const Queue = require('bullmq'); const notificationQueue = new Queue('notificationQueue'); // 路由中提交任务 router.post('/notifications/targeted', async (req, res) => { try { await notificationQueue.add('sendTargeted', req.body); res.status(202).json({ message: '通知发送任务已提交' }); } catch (err) { res.status(500).json({ error: err.message }); } }); // 队列处理器 notificationQueue.process('sendTargeted', async (job) => { const { text, targetUsers, expiresAt } = job.data; const notification = new Notification({ text, type: 'targeted', targetUsers, expiresAt: expiresAt ? new Date(expiresAt) : undefined }); await notification.save(); }); - 已读状态独立存储:不要在主通知对象中修改已读状态,单独创建
UserNotification集合存储用户-通知的已读映射:
这种方式既不污染主通知数据,也能高效统计用户已读情况。const userNotificationSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, notificationId: { type: mongoose.Schema.Types.ObjectId, ref: 'Notification', required: true }, readAt: { type: Date } }); userNotificationSchema.index({ userId: 1, notificationId: 1 }, { unique: true });
内容的提问来源于stack exchange,提问作者Nightcrawler
相关产品推荐
相关产品推荐

