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

如何通过Mongoose的aggregate与group实现按发件人取最新消息?

使用MongoDB聚合管道实现需求

当然可以用aggregate()结合分组操作来实现你的需求,而且这种方式效率更高——所有逻辑都在数据库层面完成,不需要把所有消息数据拉到应用层再做两次遍历处理,尤其当消息量较大时优势会很明显。

完整聚合实现代码

const inbox = await Messages.aggregate([
  // 1. 筛选出发给指定用户的所有消息
  { $match: { recipient: mongoose.Types.ObjectId(req.params.id) } },
  // 2. 按更新时间倒序排序,确保每个发件人的最新消息排在最前面
  { $sort: { updatedAt: -1 } },
  // 3. 关联User集合,填充sender的详细信息(替代populate)
  {
    $lookup: {
      from: 'users', // 注意这里是User模型对应的集合名称,Mongoose默认会转成复数小写
      localField: 'sender',
      foreignField: '_id',
      as: 'sender'
    }
  },
  // 4. 展开sender数组($lookup返回的是数组,即使只有一个匹配项)
  { $unwind: '$sender' },
  // 5. 按发件人用户名分组,取每组的第一条数据(也就是最新消息)
  {
    $group: {
      _id: '$sender.username',
      latestMessage: { $first: '$message' },
      senderDetails: { $first: '$sender' },
      latestTimestamp: { $first: '$updatedAt' }
    }
  },
  // 6. 调整输出格式,让结果结构更符合需求
  {
    $project: {
      _id: 0,
      username: '$_id',
      message: '$latestMessage',
      sender: '$senderDetails',
      updatedAt: '$latestTimestamp'
    }
  }
]);

console.log(inbox);

每个阶段的作用说明

  • $match:先过滤出目标收件人的消息,减少后续管道需要处理的数据量,是聚合优化的关键第一步。
  • $sort:按updatedAt降序排序,这样每个发件人的最新消息会出现在该发件人所有消息的最前面,后续用$first就能直接拿到最新的那条。
  • $lookup:替代Mongoose的populate方法,关联users集合获取发件人的完整信息,这是聚合管道中实现关联查询的标准方式。
  • $unwind:因为$lookup返回的是数组类型的结果(哪怕只有一个匹配项),所以需要用$unwind把数组展开成单个对象,才能后续用sender.username分组。
  • $group:以sender.username为分组键,用$first操作符取每组的第一条数据(也就是排序后最靠前的最新消息),同时保留发件人信息和时间戳。
  • $project:重新整理输出的字段结构,去掉不必要的_id,把字段名调整成更直观的形式,和你原来代码的输出结构保持一致甚至更完整。

和原方法的对比

原方法需要将所有匹配的消息从数据库拉到应用层,再通过两次遍历去重取最新消息,当消息数量较多时,不仅会增加网络传输开销,还会占用更多应用层内存。而聚合管道的方式把所有计算逻辑放在数据库端完成,只返回最终需要的结果,性能和资源利用率都会更好。

内容的提问来源于stack exchange,提问作者fugu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:58:16