如何用MongoDB(含Mongoose)和Express实现类Instagram好友请求系统?
如何用MongoDB(Mongoose)和Express实现类似Instagram的私有用户关注请求系统
我正在开发一款社交应用,需要实现类似Instagram的好友请求系统,需求如下:
- 用户A可关注用户B,B无需回关A
- 若用户B为私有用户(
isPrivate: true),B会收到A的关注请求,可选择同意或拒绝;同意后A将成为B的粉丝
目前我已有基础的User、UserFollower和UserFollowing Schema,支持公开用户的关注/取消关注,但缺少私有用户的请求授权逻辑。现有代码如下:
const userSchema = new Schema({ email: String, // isPrivate: Boolean, }); const userFollowerSchema = new Schema({ userId: Schema.Types.ObjectId, followerIds: [Schema.Types.ObjectId], }); const userFollowingSchema = new Schema({ userId: Schema.Types.ObjectId, followingIds: [Schema.Types.ObjectId], });
我愿意完全重构Schema以支持拉黑等扩展功能,请问该如何实现?
一、Schema重构
为了支持私有用户请求、状态管理和扩展拉黑功能,调整现有Schema并新增FollowRequest集合处理请求状态:
1. User Schema(新增私有状态与拉黑列表)
const userSchema = new Schema({ email: String, isPrivate: { type: Boolean, default: false }, // 标记是否为私有用户 blockedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }] // 拉黑用户ID列表 }); const User = mongoose.model('User', userSchema);
2. FollowRequest Schema(管理关注请求状态)
const followRequestSchema = new Schema({ requester: { type: Schema.Types.ObjectId, ref: 'User', required: true }, // 请求发起者ID recipient: { type: Schema.Types.ObjectId, ref: 'User', required: true }, // 请求接收者ID status: { type: String, enum: ['pending', 'accepted', 'rejected'], default: 'pending' }, // 请求状态 createdAt: { type: Date, default: Date.now } }); // 唯一索引,避免重复发送同一请求 followRequestSchema.index({ requester: 1, recipient: 1 }, { unique: true }); const FollowRequest = mongoose.model('FollowRequest', followRequestSchema);
3. 粉丝/关注列表Schema(保留独立集合以支持大规模数据)
// 用户的粉丝集合 const userFollowerSchema = new Schema({ userId: { type: Schema.Types.ObjectId, ref: 'User', unique: true }, followerIds: [{ type: Schema.Types.ObjectId, ref: 'User' }] }); // 用户的关注列表集合 const userFollowingSchema = new Schema({ userId: { type: Schema.Types.ObjectId, ref: 'User', unique: true }, followingIds: [{ type: Schema.Types.ObjectId, ref: 'User' }] }); const UserFollower = mongoose.model('UserFollower', userFollowerSchema); const UserFollowing = mongoose.model('UserFollowing', userFollowingSchema);
二、核心业务逻辑实现
1. 用户发起关注请求/直接关注
async function followUser(requesterId, recipientId) { const recipient = await User.findById(recipientId); // 检查是否被对方拉黑 if (recipient.blockedUsers.includes(requesterId)) { throw new Error('您已被该用户拉黑,无法关注'); } // 检查是否已关注过该用户 const existingFollowing = await UserFollowing.findOne({ userId: requesterId, followingIds: recipientId }); if (existingFollowing) { throw new Error('您已关注该用户'); } if (!recipient.isPrivate) { // 公开用户:直接建立关注关系 await Promise.all([ UserFollowing.findOneAndUpdate( { userId: requesterId }, { $push: { followingIds: recipientId } }, { upsert: true, new: true } ), UserFollower.findOneAndUpdate( { userId: recipientId }, { $push: { followerIds: requesterId } }, { upsert: true, new: true } ) ]); return { message: '关注成功' }; } else { // 私有用户:创建待处理请求 try { await FollowRequest.create({ requester: requesterId, recipient: recipientId }); return { message: '关注请求已发送,请等待对方确认' }; } catch (err) { if (err.code === 11000) { // 唯一索引冲突,说明已有请求 return { message: '您已发送过关注请求,请等待对方确认' }; } throw err; } } }
2. 私有用户处理关注请求(同意/拒绝)
async function handleFollowRequest(requestId, action, userId) { const request = await FollowRequest.findById(requestId); // 验证当前用户是否为请求接收者 if (request.recipient.toString() !== userId.toString()) { throw new Error('无权处理该请求'); } if (action === 'accept') { // 同意请求:建立双向关注关系 await Promise.all([ UserFollowing.findOneAndUpdate( { userId: request.requester }, { $push: { followingIds: request.recipient } }, { upsert: true, new: true } ), UserFollower.findOneAndUpdate( { userId: request.recipient }, { $push: { followerIds: request.requester } }, { upsert: true, new: true } ), FollowRequest.findByIdAndUpdate(requestId, { status: 'accepted' }) ]); return { message: '已同意关注请求' }; } else if (action === 'reject') { // 拒绝请求:更新状态 await FollowRequest.findByIdAndUpdate(requestId, { status: 'rejected' }); return { message: '已拒绝关注请求' }; } else { throw new Error('无效操作,仅支持accept或reject'); } }
3. 获取当前用户的待处理请求
async function getPendingFollowRequests(userId) { return FollowRequest.find({ recipient: userId, status: 'pending' }) .populate('requester', 'email') // 仅返回请求发起者的邮箱信息 .sort({ createdAt: -1 }); }
三、Express API接口示例
1. 发起关注请求/关注用户
router.post('/follow', async (req, res) => { try { const { recipientId } = req.body; const requesterId = req.user._id; // 假设已通过认证中间件获取当前用户ID const result = await followUser(requesterId, recipientId); res.status(200).json(result); } catch (err) { res.status(400).json({ error: err.message }); } });
2. 处理关注请求
router.post('/follow-request/:requestId', async (req, res) => { try { const { requestId } = req.params; const { action } = req.body; const userId = req.user._id; const result = await handleFollowRequest(requestId, action, userId); res.status(200).json(result); } catch (err) { res.status(400).json({ error: err.message }); } });
3. 获取待处理请求列表
router.get('/follow-requests/pending', async (req, res) => { try { const userId = req.user._id; const requests = await getPendingFollowRequests(userId); res.status(200).json(requests); } catch (err) { res.status(500).json({ error: '获取请求失败' }); } });
四、扩展拉黑功能
实现拉黑操作,并同步清理相关请求:
async function blockUser(userId, targetId) { await User.findByIdAndUpdate( userId, { $addToSet: { blockedUsers: targetId } }, // 避免重复拉黑 { new: true } ); // 清理双方的关注请求(如果存在) await FollowRequest.deleteMany({ $or: [ { requester: userId, recipient: targetId }, { requester: targetId, recipient: userId } ] }); return { message: '拉黑成功' }; }
内容的提问来源于stack exchange,提问作者xandert.93
相关产品推荐
相关产品推荐

