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

Node+Express+MongoDB中用户评论时earnings字段递增失效问题

解决用户评论后earnings字段无法递增的问题

看起来你遇到的核心问题是没有正确地对数据库中的用户记录进行更新操作,而且模型定义和路由代码里都有一些关键错误,导致earnings始终停在20。咱们一步步拆解问题和解决方案:

1. 先修正User模型的定义

你当前模型里的earnings: 0写法不符合mongoose的规范,需要明确指定字段类型和默认值,否则可能无法正确识别这个字段的持久化规则。把它改成:

var userSchema = new mongoose.Schema({
  username: String,
  firstName: String,
  lastName: String,
  earnings: {
    type: Number,
    default: 0 // 明确指定字段类型与初始默认值
  },
  email: String,
  password: String
})

2. 路由代码的核心错误分析

你原来的更新逻辑有两个致命问题:

  • 直接操作User.earnings:User是mongoose的模型对象,不是当前登录用户的实例,修改它根本不会影响数据库里的任何用户记录。
  • 没有持久化更新:就算你修改了req.user.earnings,req.user只是登录时缓存的用户数据,不调用save()保存到数据库的话,下次请求还是会从数据库读取旧值;而且你每次获取的req.user.earnings都是登录时的初始值(比如第一次是0,加20变成20但没保存),下次评论时还是0加20,永远只能得到20。

3. 修正后的路由代码(推荐两种方案)

方案一:使用原子更新操作(更安全,避免并发问题)

用mongoose的findByIdAndUpdate结合$inc操作符,这是数据库层面的原子操作,能保证多用户同时评论时不会出现数据覆盖:

router.post('/', middleware.isLoggedIn, (req, res) => {
  Blog.findById(req.params.id, (err, blog) => {
    if(err) {
      console.log('Error:', err);
      res.redirect('/blogs');
    } else {
      Comment.create(req.body.comment, (err, comment) => {
        if(err) {
          req.flash('error', 'Something went wrong');
          console.log('Error:', err);
        } else {
          // 关联评论的作者信息
          comment.author.id = req.user._id;
          comment.author.username = req.user.username;
          comment.save();
          blog.comments.push(comment);
          blog.save();

          // 关键:原子更新用户的earnings字段
          User.findByIdAndUpdate(
            req.user._id,
            { $inc: { earnings: 20 } }, // 数据库层面直接递增20
            { new: true }, // 返回更新后的用户文档
            (err, updatedUser) => {
              if (err) {
                console.log('Error updating earnings:', err);
                req.flash('error', 'Failed to update earnings');
              } else {
                console.log(`Updated earnings: ${updatedUser.earnings}`);
              }
              res.redirect('/blogs/' + blog._id);
            }
          );
        }
      })
    }
  });
});

方案二:先获取用户实例再更新

如果你更习惯先拿到用户对象再修改,可以用这种方式:

router.post('/', middleware.isLoggedIn, (req, res) => {
  Blog.findById(req.params.id, (err, blog) => {
    if(err) {
      console.log('Error:', err);
      res.redirect('/blogs');
    } else {
      Comment.create(req.body.comment, (err, comment) => {
        if(err) {
          req.flash('error', 'Something went wrong');
          console.log('Error:', err);
        } else {
          // 关联评论的作者信息
          comment.author.id = req.user._id;
          comment.author.username = req.user.username;
          comment.save();
          blog.comments.push(comment);
          blog.save();

          // 关键:找到当前用户实例并更新
          User.findById(req.user._id, (err, user) => {
            if (err) {
              console.log('Error finding user:', err);
              req.flash('error', 'Failed to update earnings');
            } else {
              user.earnings += 20;
              user.save((saveErr) => {
                if (saveErr) console.log('Error saving user:', saveErr);
                console.log(`Updated earnings: ${user.earnings}`);
                res.redirect('/blogs/' + blog._id);
              });
            }
          });
        }
      })
    }
  });
});

为什么推荐方案一?

$inc是MongoDB的内置操作符,它会在数据库层面直接对字段进行递增,避免了先查询再修改的竞态问题(比如两个评论请求同时到达,都读取到earnings=20,然后都加20变成40,最终只保存一次40,而不是正确的60)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:33:01