Discord机器人MongoDB经济系统存款数值拼接而非算术相加问题
Discord 机器人经济系统存款逻辑异常排查
问题现象
具备JavaScript开发经验,开发基于MongoDB存储的Discord机器人经济系统时,存款指令出现异常:预期逻辑为从用户钱包扣除指定存款金额,对应金额累加至用户银行账户;实际运行时钱包扣减逻辑完全正常,但银行账户未执行算术加法,而是将存款数字直接拼接在原有余额末尾,例如原有银行余额为7、存入5后,余额变为75而非预期的12。程序无任何报错,尝试使用+=运算符实现累加后问题依旧。
现有实现代码
存款指令逻辑
const balanceProfile = await client.createBalance(message.member); const amount = args[0]; if(isNaN(args[0])) return message.reply(`Specify an amount to deposit.`); if(amount > balanceProfile.wallet) return message.reply(`Too much money to deposit.`); try { await Balance.findOneAndUpdate({ _id: balanceProfile.id, wallet: balanceProfile.wallet - amount, bank: balanceProfile.bank + amount }); } catch (error) { console.log(error); } message.reply(`Successfully deposited ${amount} moneyz to da bank!`)
MongoDB Balance Schema 定义
const mongoose = require('mongoose'); const balanceSchema = new mongoose.Schema({ _id: mongoose.Schema.Types.ObjectId, memberId: String, wallet: { type: Number, default: 0 }, bank: { type: Number, default: 0 }, daily: { type: Number } }); module.exports = mongoose.model('Balance', balanceSchema, 'balances');
根因分析
问题由两个独立bug共同导致:
- 参数未做显式类型转换,加法触发字符串拼接
从Discord消息解析得到的args数组元素默认是字符串类型,isNaN(args[0])仅能校验值是否可转换为数字,不会修改原始值的类型。
JavaScript运算符隐式转换规则是核心诱因:- 减法运算符
-会强制将两侧操作数转为数字后再做算术运算,因此钱包扣减逻辑表现正常 - 加法运算符
+只要任意一侧操作数为字符串,就会优先执行字符串拼接逻辑,因此balanceProfile.bank + amount实际执行的是拼接操作:例如银行余额为数字7、amount为字符串"5"时,运算结果为字符串"75",写入MongoDB时被Mongoose按Schema规则转为数字75,和故障表现完全一致。这也是替换为+=运算符无效的原因——只要amount仍是字符串类型,加法永远走拼接逻辑。
- 减法运算符
findOneAndUpdate参数结构错误findOneAndUpdate的标准语法为findOneAndUpdate(查询条件, 更新规则, 配置项),当前代码把wallet、bank的更新值全部写在了查询条件位置,相当于要求匹配「钱包余额等于扣减后值、银行余额等于拼接后值」的文档才会执行更新,并发场景下大概率匹配不到目标文档,会出现偶发更新失效的问题。
修复方案
- 对输入金额做显式数字类型转换,从根源避免字符串拼接问题
- 修正
findOneAndUpdate参数结构,使用MongoDB原子更新符$inc做余额增减,规避并发场景下的数据不一致问题
修复后的核心代码如下:
const balanceProfile = await client.createBalance(message.member); // 显式将输入参数转为数字,整数场景可替换为parseInt(args[0], 10),需支持小数用parseFloat const amount = Number(args[0]); // 补充金额合法性校验 if(isNaN(amount) || amount <= 0) return message.reply(`Specify a valid positive amount to deposit.`); if(amount > balanceProfile.wallet) return message.reply(`Too much money to deposit.`); try { // 第一个参数传查询条件,第二个参数用$inc做原子增减:钱包减amount,银行加amount await Balance.findOneAndUpdate( { _id: balanceProfile.id }, { $inc: { wallet: -amount, bank: amount } } ); } catch (error) { console.log(error); return message.reply(`Deposit failed, please try again later.`); } message.reply(`Successfully deposited ${amount} moneyz to da bank!`)
内容的提问来源于stack exchange,提问作者prune
相关产品推荐
相关产品推荐

