我正在尝试构建一个非常简单的购物应用,用户可以使用虚拟货币购买物品。
我正在使用多个NodeJS进程,所以我很害怕异步部分。
这就是我的所作所为:
app.post('/buy', function(req, res){
User.findOne({_id: req.userId}, function(err, user){
if(user.balance >= ITEM_PRICE){
User.decrementBalance({_id: req.userId}, function(err){
//Do transaction, give item to user, etc
});
} else {
//Not enough money
}
});
});
这种方法的问题是用户可以在很短的时间内提交多个/购买请求。这可能会导致竞争条件,其中第二个请求在第一个请求减少之前检查用户的余额。这导致用户具有负值并且取出比他的余额允许的更多的项目。
有没有办法解决这个问题?我正在考虑做一个User.update()并验证用户是否被修改。这可行吗?
答案 0 :(得分:4)
你可以使用Model.findOneAndUpdate()
,这将把查询和组合在一个原子操作中调整余额:
User.findOneAndUpdate({
_id : req.userId,
balance : { $gte : ITEM_PRICE }
}, {
$inc : { balance : -ITEM_PRICE } // there is no $dec
}, {
new : true
}, function(err, user) {
...
});
如果查询条件失败(或者没有具有该ID的用户,或者他们没有足够的余额),user
将为null
(或{ {1}},不确定)。由于您似乎只处理登录用户,因此ID可能始终有效,因此如果undefined
未定义,则表示其余额不是&#39足够高。
由于user
,如果返回用户文档,它将反映新余额(默认情况下,它会返回旧文档)。
编辑:更多说明:在评估执行new : true
和发布更新({{1}之间的竞争条件时,您是否正确无误将会这样做。
但是,.findOne
是特殊的,因为它将使用保证为原子的特定MongoDB命令(findAndModify
),这意味着将在没有机会的情况下执行查找和更新干扰这些步骤的另一项行动。
文档的摘录:
修改单个文档时,
User.decrementBalance()
和findOneAndUpdate
方法原子都会更新文档。