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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 01:20:31