我正在更新一个基于node.js
、mongodb
和 ``mongoose`` 构建的财务应用程序,目前我面临着处理从系统中提取资金的逻辑的挑战。问题是,如果用户同时访问资金提取端点,则他们可以请求大于其余额的金额。例如,可用余额为 10 美元,用户可以同时处理多次 10 美元提款。
我知道问题出在哪里,只是我一直无法解决它。我知道这种情况正在发生,因为这些操作是同时发生的,所以user.balance
总是会发生,10
因为借记发生在读取之后。我对后端开发比较陌生,所以如果我没有使用正确的术语,请原谅我。这就是我的代码的样子。
try{
// Perform tasks such as validating the account number, etc.
const user = await User.findById(req.user._id);
if(user.balance < req.body.amount)
return res.status(400).send({message:"Insufficient Balance"});
// update the user balance in the DB to user.balance - req.body.amount
return res.status(200).send({message:"Fund has been sent to the account provided"})
}
我尝试了一种更新集合中的用户记录并使用该响应来检查用户余额的方法,但我不太确定这是否能完全解决我的问题,或者是否仍然存在漏洞,或者是否有更好的方法来处理这。
这是我尝试的第二种方法
try{
// Perform tasks such as validating the account number, etc.
const idx = uuid()
const user = await User.findByIdAndUpdate(req.user._id, {withdrawal_intent:idx} {
new: true,
});
if(user.balance < req.body.amount)
return res.status(400).send({message:"Insufficient Balance"});
// update the user balance in the DB to user.balance - req.body.amount
return res.status(200).send({message:"Fund has been sent to the account provided"})
}
我愿意重构代码以适应处理此问题的最佳方法
PS:这是一个旧的代码库,所以我可能使用猫鼬的旧语法。
"mongoose": "^5.9.9"
简短回答:
这个问题中的竞争条件可以使用 findOneAndUpdate 方法来处理。最重要的一点是传递给该方法的条件。这应该始终检查正在执行的更新不会导致负值。下面给出了示例代码,阅读时请利用其中的注释。
详细解答:
请按顺序阅读这篇文章。
条件更新: 条件更新是一种常见的乐观方式,用于在面对并发访问时保持数据的一致性,即面对并发时的一致性。
它的工作原理是,任何进行更新的客户端都会在更新之前测试该值,以查看自上次读取以来该值是否已更改。如果测试失败,则发生竞争条件 - 写入冲突,并且应中止当前更新,并通知用户该情况。对于这个原始问题,已经实施了竞态条件测试,因为更新不会导致负余额。
下面的代码将演示它。它主要基于 findOneAndUpdate 方法,因为它将以原子方式执行三个操作 - 查找、更新和读取。欲了解更多详情,请参阅代码中给出的注释。代码中的两个语句需要一些附加信息,如下所示。
语句 - 语句#1:这是在代码中导致竞争条件的语句。目前尚未评论。为了在不创建竞争条件的情况下运行代码,请注释此语句。
语句 - 语句 #2 :这是已实现条件更新的语句,条件 - { a: 'A', b: { $gte: orgdoc.b } } 正在执行这项工作。
首先,我建议在端点上设置速率限制,例如每秒 1 个请求。
其次,如果您尚未定义余额检查列以防止出现负余额,则应该这样做。
(虽然我不是 MongoDB 专家),您可以通过使用可序列化事务来实现这一点。这将涉及选择和更新用户的余额,将新记录插入提款表,然后提交交易。如果检查失败,交易将被回滚。
对于金融应用程序,存储过程可能是一个好方法。
这是一个例子:
Mongoose v5 模式支持
min
和max
模式验证器,因此您可以应用min
of0
to ,balance
这样就不可能像这样低于它:您可以使用负数来
$inc
增加字段,但我会首先检查它是否是正整数。然后,当您进行更新时,请确保使用或使用如下选项:balance
req.body.amount
findByIdAndUpdate
findOneAndUpdate
{ runValidators: true }