在社交类应用开发中,用户添加好友是核心功能之一,使用Mongoose实现该功能时,合理的Schema设计和操作流程能提升系统性能与可维护性。本文将从Schema结构设计入手,讲解好友关系存储、添加好友的状态流转、重复好友校验、双向关系维护等核心环节,同时提供完整的代码示例和常见场景的处理方案,帮助开发者避开设计误区,快速实现稳定可靠的好友添加功能,适配不同规模的社交应用需求。

好友关系Schema设计
好友关系需要存储双方用户ID、关系状态、创建时间等信息,推荐使用单独的集合存储好友关系,而不是在用户Schema中嵌套好友列表,这样更便于查询和扩展。以下是好友关系Schema的示例:
const mongoose = require('mongoose');
const { Schema } = mongoose;
// 好友关系Schema
const friendSchema = new Schema({
// 发起好友请求的用户ID
requester: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
// 接收好友请求的用户ID
recipient: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
// 好友关系状态:pending-待确认,accepted-已通过,rejected-已拒绝
status: {
type: String,
enum: ['pending', 'accepted', 'rejected'],
default: 'pending'
},
// 关系创建时间
createdAt: {
type: Date,
default: Date.now
},
// 关系更新时间
updatedAt: {
type: Date,
default: Date.now
}
});
// 添加复合索引,避免重复的好友请求,同时提升查询效率
friendSchema.index({ requester: 1, recipient: 1 }, { unique: true });
const Friend = mongoose.model('Friend', friendSchema);
module.exports = Friend;
用户Schema设计
用户Schema只需要存储用户的基本信息即可,不需要嵌套好友相关的数组,保持Schema的简洁性:
const userSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
// 其他用户基本信息字段
avatar: String,
nickname: String
});
const User = mongoose.model('User', userSchema);
module.exports = User;
添加好友核心功能实现
发起好友请求
发起好友请求时需要校验请求用户和接收用户是否存在,同时检查是否已经存在对应的好友关系,避免重复发起请求:
const addFriendRequest = async (req, res) => {
const { requesterId, recipientId } = req.body;
// 校验两个用户是否存在
const requester = await User.findById(requesterId);
const recipient = await User.findById(recipientId);
if (!requester || !recipient) {
return res.status(404).json({ message: '用户不存在' });
}
// 不能添加自己为好友
if (requesterId === recipientId) {
return res.status(400).json({ message: '不能添加自己为好友' });
}
try {
// 创建好友请求记录
const friendRequest = await Friend.create({
requester: requesterId,
recipient: recipientId,
status: 'pending'
});
res.status(201).json({ message: '好友请求已发送', data: friendRequest });
} catch (err) {
// 唯一索引冲突说明已经存在好友关系
if (err.code === 11000) {
return res.status(400).json({ message: '已经存在好友请求或已经是好友' });
}
res.status(500).json({ message: '操作失败', error: err.message });
}
};
处理好友请求
接收方可以对好友请求进行处理,同意或者拒绝,处理完成后更新好友关系的状态:
const handleFriendRequest = async (req, res) => {
const { requestId, action } = req.body; // action可选值:accept、reject
const { userId } = req.user; // 当前登录用户ID,即接收方ID
// 查找对应的好友请求,并且确保当前用户是接收方
const friendRequest = await Friend.findOne({
_id: requestId,
recipient: userId,
status: 'pending'
});
if (!friendRequest) {
return res.status(404).json({ message: '好友请求不存在或已处理' });
}
if (action === 'accept') {
// 同意好友请求,更新状态为已通过
friendRequest.status = 'accepted';
friendRequest.updatedAt = Date.now();
await friendRequest.save();
res.json({ message: '已同意好友请求', data: friendRequest });
} else if (action === 'reject') {
// 拒绝好友请求,更新状态为已拒绝
friendRequest.status = 'rejected';
friendRequest.updatedAt = Date.now();
await friendRequest.save();
res.json({ message: '已拒绝好友请求', data: friendRequest });
} else {
res.status(400).json({ message: '无效的操作类型' });
}
};
好友关系查询
查询用户的好友列表时,需要筛选出状态为已通过的好友关系,同时可以关联查询用户的基本信息:
const getFriendList = async (req, res) => {
const { userId } = req.params;
// 查询用户作为请求方或者接收方的已通过的好友关系
const friendRelations = await Friend.find({
$or: [
{ requester: userId, status: 'accepted' },
{ recipient: userId, status: 'accepted' }
]
}).populate('requester', 'username nickname avatar')
.populate('recipient', 'username nickname avatar');
// 提取好友的用户信息
const friendList = friendRelations.map(relation => {
// 如果当前用户是请求方,好友就是接收方,反之则是请求方
const friend = relation.requester._id.toString() === userId
? relation.recipient
: relation.requester;
return {
friendId: friend._id,
username: friend.username,
nickname: friend.nickname,
avatar: friend.avatar,
relationCreatedAt: relation.createdAt
};
});
res.json({ data: friendList });
};
最佳实践总结
- 使用独立的好友关系集合存储好友数据,避免用户Schema过度膨胀,便于后续扩展好友备注、分组等功能。
- 为好友关系集合添加
requester和recipient的复合唯一索引,避免重复的好友请求和关系。 - 好友关系状态流转清晰,区分待确认、已通过、已拒绝三种状态,便于处理不同的业务场景。
- 查询好友列表时通过
$or条件匹配双向关系,确保不会遗漏好友数据。 - 所有操作都做好参数校验和异常处理,避免无效数据写入数据库。
注意:如果需要支持双向好友关系的额外校验,比如防止用户A给用户B发请求后,用户B又给用户A发重复请求,可以在发起请求时同时检查反向关系是否存在,若存在则直接提示已经是好友或者有待处理的请求。