Mongoose互引用Schema简化方案及关联时间记录实现问询
Mongoose 双向关联自动维护与社区加入/离开时间记录实现
原始 Schema 定义(修正 ref 笔误后)
const CommunitySchema = new Schema({ slug: { type: String, unique: true }, name: String, description: String, users: [{ type: Schema.Types.ObjectId, ref: "User" }] }, { timestamps: true }); const Community = mongoose.model('Community', CommunitySchema); const UserSchema = new Schema({ name: String, communities: [{ type: Schema.Types.ObjectId, ref: "Community" }], exCommunities: [{ type: Schema.Types.ObjectId, ref: "Community" }], }, { timestamps: true });
问题1:自动维护双向关联,简化操作
可以通过 Mongoose 的文档中间件自动同步 User 和 Community 的关联关系,无需手动执行两次更新。核心是监听 User 文档的修改事件,自动同步对应的 Community 数据。
实现方案
1. 监听 save 事件(适用于新建/更新 User 文档)
UserSchema.pre('save', async function(next) { const user = this; // 对比修改前后的社区ID列表 const originalCommunityIds = (user._doc.communities || []).map(id => id.toString()); const newCommunityIds = (user.communities || []).map(id => id.toString()); // 找出新增和移除的社区ID const addedIds = newCommunityIds.filter(id => !originalCommunityIds.includes(id)); const removedIds = originalCommunityIds.filter(id => !newCommunityIds.includes(id)); // 同步新增社区:将用户ID加入社区的users数组 if (addedIds.length) { await Community.updateMany( { _id: { $in: addedIds } }, { $addToSet: { users: user._id } } // 用$addToSet避免重复添加 ); } // 同步移除社区:将用户ID从社区的users数组中移除 if (removedIds.length) { await Community.updateMany( { _id: { $in: removedIds } }, { $pull: { users: user._id } } ); } next(); });
2. 监听 findOneAndUpdate 事件(适用于查询更新操作)
findOneAndUpdate 不会触发文档的 save 钩子,需要单独处理:
UserSchema.pre('findOneAndUpdate', async function(next) { const update = this.getUpdate(); const userId = this.getQuery()._id; // 处理社区添加($addToSet/$push) if (update.$addToSet?.communities || update.$push?.communities) { const communityId = update.$addToSet?.communities || update.$push?.communities; await Community.updateOne( { _id: communityId }, { $addToSet: { users: userId } } ); } // 处理社区移除($pull) if (update.$pull?.communities) { const communityId = update.$pull.communities; await Community.updateOne( { _id: communityId }, { $pull: { users: userId } } ); } next(); });
问题2:记录加入/离开社区的时间
可以将 communities 和 exCommunities 改为对象数组,每个元素包含社区引用和时间字段,同时调整中间件逻辑同步数据。
修改后的 Schema
const UserSchema = new Schema({ name: String, communities: [ { community: { type: Schema.Types.ObjectId, ref: "Community" }, joinedAt: { type: Date, default: Date.now } // 自动记录加入时间 } ], exCommunities: [ { community: { type: Schema.Types.ObjectId, ref: "Community" }, joinedAt: Date, // 继承原加入时间 leftAt: { type: Date, default: Date.now } // 自动记录离开时间 } ], }, { timestamps: true });
调整后的中间件(同步关联+时间记录)
UserSchema.pre('save', async function(next) { const user = this; // 提取修改前后的社区ID列表 const originalCommunityIds = (user._doc.communities || []).map(item => item.community.toString()); const newCommunityIds = (user.communities || []).map(item => item.community.toString()); const addedIds = newCommunityIds.filter(id => !originalCommunityIds.includes(id)); const removedIds = originalCommunityIds.filter(id => !newCommunityIds.includes(id)); // 同步新增社区 if (addedIds.length) { await Community.updateMany( { _id: { $in: addedIds } }, { $addToSet: { users: user._id } } ); } // 处理社区移除:转移到exCommunities并同步社区数据 if (removedIds.length) { // 从原communities中获取被移除项的加入时间 const removedItems = user._doc.communities.filter(item => removedIds.includes(item.community.toString()) ); // 转移到exCommunities removedItems.forEach(item => { user.exCommunities.push({ community: item.community, joinedAt: item.joinedAt, leftAt: new Date() }); }); // 从社区中移除用户 await Community.updateMany( { _id: { $in: removedIds } }, { $pull: { users: user._id } } ); } next(); });
内容的提问来源于stack exchange,提问作者Petr
相关产品推荐
相关产品推荐

