基于给定Mongoose Schema查询用户二度连接的MongoDB实现
Mongoose 二度好友关系查询实现方案
现有数据模型定义
User(用户)模型
- 核心必填字段:
name:String类型,存储用户名email:String类型,存储用户邮箱password:String类型,存储用户密码
- 自动生成字段:模型配置了
timestamps: true,会自动维护createdAt(创建时间)、updatedAt(更新时间)两个时间戳字段
对应Schema代码:
const UserSchema = new mongoose.Schema({ name: { type: String, required: true }, email: { type: String, required: true, }, password: { type: String, required: true } }, { timestamps: true }); module.exports = mongoose.model("User", UserSchema);
Connection(好友连接关系)模型
- 核心字段:
UserID:mongoose.Schema.Types.ObjectId类型,必填,通过ref关联User模型的_id字段,对应当前关系记录所属的用户主体hasRequested:ObjectId类型数组,每个元素通过ref关联User模型_id,存储向当前用户发起好友申请的用户ID列表isConnection:ObjectId类型数组,每个元素通过ref关联User模型_id,存储与当前用户已建立双向好友关系的用户ID列表
对应Schema代码:
const ConnectionSchema = new mongoose.Schema({ UserID: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, hasRequested: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], isConnection: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }] }) module.exports = mongoose.model("Connection", ConnectionSchema);
查询需求说明
给定测试场景:_id为A的用户,直接好友为B、C;B的直接好友为A、X、Y;C的直接好友为A、M、N。需要查询得到:
- A的所有直接好友列表
- A的二度好友列表(即所有直接好友的好友,排除A本人、排除已存在的直接好友)
可行实现方案
方案1:MongoDB聚合管道单次查询(推荐,性能更优)
通过聚合管道一次查询完成所有逻辑,避免多次数据库IO,适合全量级数据场景。
实现逻辑:
- 匹配定位到用户A的好友关系记录
- 关联查询A所有直接好友对应的好友关系记录
- 合并所有直接好友的好友列表,自动去重,过滤掉A本人和已有的直接好友,得到二度好友列表
查询代码:
// 替换为用户A的实际ObjectId值 const userIdA = new mongoose.Types.ObjectId("A的ObjectId字符串"); const [result] = await Connection.aggregate([ // 匹配用户A的好友关系文档 { $match: { UserID: userIdA } }, // 关联查询所有直接好友的好友关系记录 { $lookup: { from: "connections", localField: "isConnection", foreignField: "UserID", as: "directFriendConnections" } }, // 合并所有直接好友的好友列表 { $project: { _id: 0, directFriends: "$isConnection", allFriendOfFriends: { $reduce: { input: "$directFriendConnections.isConnection", initialValue: [], in: { $setUnion: ["$$value", "$$this"] } } } } }, // 过滤掉A本人、已经是直接好友的用户,得到最终二度好友列表 { $project: { directFriends: 1, secondDegreeFriends: { $setDifference: ["$allFriendOfFriends", { $setUnion: [[userIdA], "$directFriends"] }] } } } ]);
返回结果结构:
directFriends:数组,存储B、C的ObjectId,即A的所有直接好友secondDegreeFriends:数组,存储X、Y、M、N的ObjectId,即A的二度好友
如果需要返回好友的详细信息(用户名、邮箱等),在聚合管道末尾追加以下两个阶段关联User集合即可:
// 关联查询直接好友详情 { $lookup: { from: "users", localField: "directFriends", foreignField: "_id", as: "directFriendsDetail" } }, // 关联查询二度好友详情 { $lookup: { from: "users", localField: "secondDegreeFriends", foreignField: "_id", as: "secondDegreeFriendsDetail" } }
方案2:分步查询(逻辑直观,适合小数据量场景)
分两次查询拿到基础数据,在Node.js层做数据过滤整理,代码易读易调试。
查询代码:
const userIdA = new mongoose.Types.ObjectId("A的ObjectId字符串"); // 第一步:查询A的好友关系,拿到直接好友列表 const userAConn = await Connection.findOne({ UserID: userIdA }); const directFriends = userAConn.isConnection; // 第二步:查询所有直接好友的好友关系记录 const friendConns = await Connection.find({ UserID: { $in: directFriends } }); // 第三步:合并、过滤得到二度好友列表 let allFriendOfFriends = []; friendConns.forEach(conn => { allFriendOfFriends.push(...conn.isConnection.map(id => id.toString())); }); // 去重、过滤A本人、过滤直接好友 const secondDegreeFriends = [...new Set(allFriendOfFriends)] .filter(idStr => { return idStr !== userIdA.toString() && !directFriends.some(fid => fid.toString() === idStr); }) .map(idStr => new mongoose.Types.ObjectId(idStr)); // 最终结果 const result = { directFriends, secondDegreeFriends };
注意事项
- 聚合查询中
$lookup的from字段需要和Mongoose模型实际映射的集合名一致,Mongoose默认会将模型名转为小写复数作为集合名,比如Connection对应connections、User对应users,如果自定义了集合名请同步修改。- ObjectId类型比较时请保证类型一致,不要直接用字符串和ObjectId类型做匹配,避免判断失效。
- 数据量较大时,建议给Connection集合的
UserID字段添加索引,可大幅提升查询效率。
内容的提问来源于stack exchange,提问作者Aksabar Accelerating
相关产品推荐
相关产品推荐

