
本文旨在指导开发者如何在使用 Mongoose 构建社交应用时,正确地更新用户的好友列表。文章将探讨如何安全有效地处理好友请求的接受流程,并讨论维护用户好友列表的不同策略,包括直接在 User Schema 中维护以及通过查询 FriendRequest Schema 间接获取。同时,本文将强调数据一致性的重要性,并建议使用事务来确保数据库操作的原子性。
在构建社交应用时,管理用户之间的好友关系是一个常见的需求。本文将深入探讨如何使用 Mongoose 在用户接受好友请求后,更新双方用户的好友列表。我们将讨论安全性、数据一致性以及不同的实现策略。
确保请求发送者的身份验证
首先,务必确保好友请求的发送者身份已通过验证。不要依赖客户端发送的 sender ID,因为这可能被篡改。使用服务器端已经验证过的用户 ID,通常可以通过中间件获取。
router.post("/requests", (req, res) => { const { receiver } = req.body; FriendRequest.create({ sender: req.user._id, // 从经过身份验证的用户获取 sender ID receiver, }) .then(() => { res.status(204).send(); }) .catch((error) => { res.status(500).send("Error sending friend request"); }); });
维护好友列表的策略:直接 vs. 间接
有两种主要策略来维护好友列表:
- 直接方法: 在 User Schema 中维护一个 friends 数组,存储好友的 ObjectId。
- 间接方法: 依赖 FriendRequest Schema,通过查询 status 为 “accepted” 的请求来获取好友列表。
间接方法:查询 FriendRequest Schema
如果采用间接方法,你无需在 User Schema 中维护 friends 数组。你可以通过查询 FriendRequest 集合来动态获取好友列表。
FriendRequest.find({ $or: [ { sender: req.user._id, status: "accepted" }, { receiver: req.user._id, status: "accepted" }, ], }) .then((listOfFriends) => { console.log(listOfFriends); }) .catch((e) => { // 处理错误 });
这种方法的优点是避免了在多个地方更新数据,降低了数据不一致的风险。
直接方法:更新 User Schema 中的 friends 数组
如果选择在 User Schema 中维护 friends 数组,需要在接受好友请求时更新双方用户的 friends 数组。
router.post("/requests/:id/accept", async (req, res) => { try { const request = await FriendRequest.findById(req.params.id); if (!request) { return res.status(404).send("Friend request not found"); } request.status = "accepted"; await request.save(); const currentUser = await User.findById(request.receiver); const friendUser = await User.findById(request.sender); if (!currentUser || !friendUser) { return res.status(404).send("User not found"); } currentUser.friends.push(request.sender); friendUser.friends.push(request.receiver); await currentUser.save(); await friendUser.save(); res.redirect("/requests"); } catch (error) { console.error(error); res.status(500).send("Error accepting friend request"); } });
重要注意事项:使用事务
当同时更新多个集合(例如 FriendRequest 和 User)时,必须使用事务来保证数据的一致性。如果服务器在更新过程中崩溃,事务可以回滚所有更改,防止数据损坏。
以下是如何使用 Mongoose 事务的示例:
const mongoose = require('mongoose'); router.post("/requests/:id/accept", async (req, res) => { const session = await mongoose.startSession(); session.startTransaction(); try { const request = await FriendRequest.findById(req.params.id).session(session); if (!request) { await session.abortTransaction(); session.endSession(); return res.status(404).send("Friend request not found"); } request.status = "accepted"; await request.save({ session }); const currentUser = await User.findById(request.receiver).session(session); const friendUser = await User.findById(request.sender).session(session); if (!currentUser || !friendUser) { await session.abortTransaction(); session.endSession(); return res.status(404).send("User not found"); } currentUser.friends.push(request.sender); friendUser.friends.push(request.receiver); await currentUser.save({ session }); await friendUser.save({ session }); await session.commitTransaction(); session.endSession(); res.redirect("/requests"); } catch (error) { await session.abortTransaction(); session.endSession(); console.error(error); res.status(500).send("Error accepting friend request"); } });
代码解释:
- mongoose.startSession(): 启动一个新的 Mongoose 会话,用于事务管理。
- session.startTransaction(): 开始事务。
- .session(session): 将会话传递给所有 Mongoose 操作 (例如 findById, save)。
- session.commitTransaction(): 提交事务,将所有更改保存到数据库。
- session.abortTransaction(): 如果出现错误,中止事务,回滚所有更改。
- session.endSession(): 结束会话。
总结
管理用户好友关系需要仔细考虑安全性、数据一致性和性能。 使用经过身份验证的用户 ID,选择适合你的应用需求的维护策略(直接或间接),并始终使用事务来保证数据一致性,这些都是至关重要的。 通过遵循这些最佳实践,你可以构建一个健壮且可靠的社交应用。


