You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现博客点赞多请求的依赖执行与错误回滚?

博客点赞功能的事务性实现与优化方案

问题需求

现有博客点赞功能包含三个操作阶段:

  1. 将目标文章ID添加到点赞用户的likedPosts列表
  2. 将点赞用户ID添加到目标文章的likedPeople列表
  3. 向文章作者发送点赞通知

需要满足以下约束:

  • 若阶段2执行失败,阶段1和3必须完全不执行(或回滚已执行的操作)
  • 若检测到用户已点赞过该文章,立即终止所有操作并回滚已做修改
  • 保证三个操作的原子性:要么全部成功,要么全部失败

现有代码

module.exports.like = (req, res) => {
    try{
        const userReq = {body: {set: {likedPosts: req.body.post_id}, unset: {}}, params: {userId: req.body.user_id}};
        const postReq = {body: {set: {likedPeople: req.body.user_id}, unset: {}}, params: {postId: req.body.post_id}};
        const notifReq = {body: {to: req.body.poster_id, fromPost: req.body.post_id, content: "*title* Baslikli postunuz *magnitude* begeni aldi!", magnitude: 1, title: "Icerigin begenildi", img: "https://images.emojiterra.com/google/android-10/512px/1f44d.png", kind: "postLikes"}}

        console.log(notifReq);

        userController.pushUser(userReq, {send: _ => {console.log(_)}, status: _ => {}});
        postController.pushPost(postReq, {send: _ => {console.log(_)}, status: _ => {}});
        notiController.newNotif(notifReq, {send: _ => {console.log(_)}, status: _ => {}});
        res.status(200).send("Success!");
        
    }catch(err){
        console.log("Wrong parameters")
        res.status(500).send(err);
    }
}

解决方案与实现架构

核心思路:使用数据库事务保证原子性

所有涉及数据库修改的操作(阶段1、2、3)必须包裹在一个事务中,事务会自动处理失败回滚;同时前置添加重复点赞校验,提前终止无效操作。

具体实现步骤

  1. 前置重复点赞校验
    先查询用户的likedPosts或文章的likedPeople,确认用户未点赞过该文章:

    • 推荐给user.likedPosts和post.likedPeople字段添加索引,提升查询效率
    • 若检测到已点赞,直接返回409 Conflict状态码,终止流程
  2. 改造控制器为Promise风格
    原代码使用回调式控制器,无法同步处理异步操作的成功/失败状态,必须将pushUser、pushPost、newNotif改造为返回Promise的方法(或用util.promisify包装回调)

  3. 事务包裹所有操作
    根据你使用的数据库类型实现事务:

    • MongoDB:需要启用副本集,使用session对象包裹所有数据库操作
    • SQL(MySQL/PostgreSQL):使用数据库原生事务机制
  4. 错误捕获与回滚
    事务中任何一步操作失败,都会触发整个事务回滚,自动撤销已执行的所有修改

改造后的示例代码

const { promisify } = require('util');
// 假设控制器方法已改造为Promise,或用promisify包装回调
const pushUser = promisify(userController.pushUser);
const pushPost = promisify(postController.pushPost);
const newNotif = promisify(notiController.newNotif);

module.exports.like = async (req, res) => {
    try {
        // 1. 输入参数校验
        const { user_id, post_id, poster_id } = req.body;
        if (!user_id || !post_id || !poster_id) {
            return res.status(400).send("缺少必要参数");
        }

        // 2. 前置校验:检查用户是否已点赞
        const existingLike = await User.findById(user_id).select('likedPosts');
        if (existingLike.likedPosts.includes(post_id)) {
            return res.status(409).send("你已点赞过该文章");
        }

        // 3. 启动数据库事务(以MongoDB为例)
        const session = await mongoose.startSession();
        session.startTransaction();

        try {
            // 构造请求参数
            const userReq = { body: { set: { likedPosts: post_id }, unset: {} }, params: { userId: user_id }, session };
            const postReq = { body: { set: { likedPeople: user_id }, unset: {} }, params: { postId: post_id }, session };
            const notifReq = { body: { to: poster_id, fromPost: post_id, content: "*title* 标题的文章获得了*magnitude*个赞!", magnitude: 1, title: "你的内容被点赞了", img: "https://images.emojiterra.com/google/android-10/512px/1f44d.png", kind: "postLikes" }, session };

            // 按顺序执行事务内操作
            await pushUser(userReq);
            await pushPost(postReq);
            await newNotif(notifReq);

            // 提交事务
            await session.commitTransaction();
            res.status(200).send("点赞成功!");
        } catch (transactionErr) {
            // 回滚事务
            await session.abortTransaction();
            throw transactionErr;
        } finally {
            session.endSession();
        }
    } catch (err) {
        console.error("点赞操作失败:", err);
        // 根据错误类型返回对应状态码
        if (err.message.includes("已点赞")) {
            res.status(409).send(err.message);
        } else if (err.message.includes("参数")) {
            res.status(400).send(err.message);
        } else {
            res.status(500).send("服务器内部错误");
        }
    }
};

优化建议

  • 参数校验增强:使用Joi、Yup等校验库,对输入参数的类型、格式做严格验证,避免非法数据进入业务逻辑
  • 索引优化:给user.likedPosts和post.likedPeople添加数组索引,大幅提升重复点赞校验的查询速度
  • 通知异步化(可选):若通知操作非核心业务(即使通知失败也不影响点赞逻辑),可将通知放入消息队列异步处理,提升接口响应速度;但需保证点赞成功后才发送通知
  • 错误日志细化:记录错误的具体信息(如用户ID、文章ID、错误栈),便于排查问题
  • 避免直接操作req.body:解构req.body后使用变量,代码更清晰且便于校验

内容的提问来源于stack exchange,提问作者Bot Ellis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 22:25:34