
本文详解为何直接用 === 比较两个 mongoose 文档对象无法判断是否为同一用户,并提供安全、高效的身份校验方案,同时说明 http 方法选型建议。
本文详解为何直接用 === 比较两个 mongoose 文档对象无法判断是否为同一用户,并提供安全、高效的身份校验方案,同时说明 http 方法选型建议。
在实现用户关注(follow)与取关(unfollow)功能时,一个关键的安全约束是:禁止用户关注自己。然而,许多开发者会误以为通过 userToModify === currentUser 即可完成身份比对——这是典型的常见误区。
❌ 错误做法:直接比较对象引用
Mongoose 的 User.findById() 返回的是独立的文档实例(即不同内存地址的对象),即使它们对应数据库中同一条记录(如 id 相同),其 JavaScript 引用也永远不相等:
if (userToModify === currentUser) { // ❌ 总是 false!
return res.status(400).json({ message: "You cannot follow/unfollow yourself!" });
}
该条件永远不会触发,导致逻辑失效,用户可成功“关注自己”。
✅ 正确做法:比较唯一标识符(_id)
应始终使用数据库主键(即 _id 字段值)进行语义级比对。由于 req.params.id 和 req.user._id 均为字符串或 ObjectId 类型,二者可直接安全比较:
const { id } = req.params;
// ✅ 在查询前就校验,避免无谓 DB 请求
if (id.toString() === req.user._id.toString()) {
return res.status(400).json({ message: "You cannot follow/unfollow yourself!" });
}
// 后续再执行查询
const userToModify = await User.findById(id);
const currentUser = await User.findById(req.user._id);
if (!userToModify || !currentUser) {
return res.status(404).json({ message: "User not found!" });
}
? 优化提示:将 ID 校验提前至数据库查询之前,既提升性能,又增强健壮性(避免空查询 + 提前失败)。
? 关注/取关核心逻辑(精简版)
const isFollowing = currentUser.following.includes(id);
if (isFollowing) {
// 取关:双向移除关联
await User.findByIdAndUpdate(id, { $pull: { followers: req.user._id } });
await User.findByIdAndUpdate(req.user._id, { $pull: { following: id } });
res.status(200).json({ message: "User unfollowed successfully" });
} else {
// 关注:双向添加关联
await User.findByIdAndUpdate(id, { $push: { followers: req.user._id } });
await User.findByIdAndUpdate(req.user._id, { $push: { following: id } });
res.status(200).json({ message: "User followed successfully" });
}
⚠️ 注意事项与最佳实践
- 类型一致性:确保 id 和 req.user._id 类型一致。推荐统一转为字符串比对(.toString()),避免 ObjectId 实例比较陷阱。
- 原子性考虑:当前实现为两个独立更新操作,极端情况下可能出现数据不一致(如第一个成功、第二个失败)。生产环境建议使用事务(session)包裹。
-
HTTP 方法选型:
使用 POST 是可行的(尤其当接口语义为“触发状态切换”时),但更符合 REST 规范的做法是:- ✅ 使用 PATCH /users/:id/follow 表示修改关注状态(幂等、语义清晰);
- ❌ 避免 PUT(通常用于全量替换资源);
- ? 不适用 GET 或 DELETE(副作用操作不应通过 GET 触发)。
✅ 总结
防止用户关注自己的本质,不是比较对象引用,而是校验业务主键。务必使用 id.toString() === req.user._id.toString() 进行前置判断,并将该检查置于任何数据库操作之前。同时,结合合理的 HTTP 方法设计与错误处理,才能构建出健壮、可维护的关注系统。











