如何实现Mongoose黑名单Schema?含Person与Message场景方案及示例
Mongoose 黑名单 Schema 设计方案:要不要新增?怎么实现?
先给你把核心判断逻辑说清楚:要不要新增Schema完全取决于你的业务场景和数据规模,分两种典型情况来拆解:
情况1:简单拉黑(无额外字段、黑名单规模小)—— 无需新增Schema
如果你的拉黑逻辑只是「用户A拉黑用户B」,不需要记录拉黑时间、原因这类额外信息,且单个用户的黑名单数量不会特别大(比如几百条以内),直接在现有的Person Schema里加一个数组字段就足够了。这种方案读写效率很高,因为都是单文档操作,没必要额外搞一个Schema。
实现示例:修改现有Person Schema
const mongoose = require('mongoose'); const personSchema = new mongoose.Schema({ // 你原有的Person字段,比如用户名、邮箱等 username: { type: String, unique: true, required: true }, email: String, // 新增黑名单字段:存储被拉黑用户的ObjectId blockedUsers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Person' }] }); const Person = mongoose.model('Person', personSchema);
拉黑/取消拉黑操作示例
// 拉黑用户:用户userId1拉黑userId2 async function blockUser(userId1, userId2) { // 用$addToSet避免重复拉黑同一个用户 await Person.findByIdAndUpdate( userId1, { $addToSet: { blockedUsers: userId2 } }, { new: true } // 返回更新后的文档 ); } // 取消拉黑 async function unblockUser(userId1, userId2) { await Person.findByIdAndUpdate( userId1, { $pull: { blockedUsers: userId2 } }, { new: true } ); }
过滤被拉黑用户的消息
比如获取当前用户的消息列表时,自动排除被拉黑用户发的内容:
async function getValidMessages(userId) { // 先拿到当前用户的黑名单列表 const currentUser = await Person.findById(userId).select('blockedUsers'); // 查询消息,排除黑名单用户的内容 const messages = await Message.find({ sender: { $nin: currentUser.blockedUsers } }).populate('sender', 'username'); return messages; }
情况2:复杂拉黑场景—— 必须新增独立Blacklist Schema
如果你的业务需要记录拉黑时间、拉黑原因,或者单个用户的黑名单数量极大(比如上千条),或者需要做复杂查询(比如「统计被拉黑最多的用户」「按拉黑时间筛选黑名单」),那单独建一个Blacklist Schema会更合适。这种方案不会让Person文档变得过于庞大,还能给关键字段加索引,大幅提升查询性能。
实现示例:新增Blacklist Schema
const blacklistSchema = new mongoose.Schema({ blocker: { type: mongoose.Schema.Types.ObjectId, ref: 'Person', required: true, index: true // 给拉黑者ID加索引,优化查询速度 }, blocked: { type: mongoose.Schema.Types.ObjectId, ref: 'Person', required: true, index: true // 给被拉黑者ID加索引 }, blockReason: String, // 可选:拉黑原因 blockAt: { type: Date, default: Date.now } // 自动记录拉黑时间 }, { timestamps: true }); // 加联合唯一索引:同一个用户不能重复拉黑另一个用户 blacklistSchema.index({ blocker: 1, blocked: 1 }, { unique: true }); const Blacklist = mongoose.model('Blacklist', blacklistSchema);
拉黑/取消拉黑操作示例
// 拉黑用户,可附带原因 async function blockUserWithReason(blockerId, blockedId, reason = '') { try { await Blacklist.create({ blocker: blockerId, blocked: blockedId, blockReason: reason }); } catch (err) { // 捕获重复拉黑的错误(因为有联合唯一索引) if (err.code === 11000) { throw new Error('你已经拉黑过这个用户了'); } throw err; } } // 取消拉黑 async function unblockUser(blockerId, blockedId) { await Blacklist.findOneAndDelete({ blocker: blockerId, blocked: blockedId }); }
复杂查询示例:统计被拉黑最多的用户
async function getMostBlockedUsers(topN = 10) { const result = await Blacklist.aggregate([ { $group: { _id: '$blocked', blockCount: { $sum: 1 } } }, { $sort: { blockCount: -1 } }, { $limit: topN }, { $lookup: { from: 'people', localField: '_id', foreignField: '_id', as: 'userInfo' } }, { $unwind: '$userInfo' }, { $project: { _id: 0, userId: '$_id', userName: '$userInfo.username', blockCount: 1 } } ]); return result; }
过滤被拉黑用户的消息(独立Schema版)
async function getValidMessages(userId) { // 获取当前用户拉黑的所有用户ID const blockedIds = await Blacklist.find({ blocker: userId }).distinct('blocked'); // 查询消息,排除这些用户的内容 const messages = await Message.find({ sender: { $nin: blockedIds } }).populate('sender', 'username'); return messages; }
最后给你个选择参考
- 简单拉黑需求(无额外信息、黑名单规模小):选嵌入数组方案,实现简单、读写快;
- 复杂拉黑需求(需记录额外信息、黑名单规模大、需复杂查询):选独立Blacklist Schema,性能更稳定,扩展性更强。
内容的提问来源于stack exchange,提问作者Saeed Heidarizarei
相关产品推荐
相关产品推荐

