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

JavaScript开发Discord机器人时采集冷却功能的数据覆盖问题及解决方法

问题原因分析

你遇到的这个数据回退问题,本质是异步操作下的竞态条件(Race Condition),再加上同步文件写入的逻辑缺陷,具体拆解下:

  1. 旧数据快照的误用:当你触发砍树操作时,代码读取了当时的user数据;3秒后触发采矿操作时,又读取了此时的user数据(这时候砍树还没完成,user还是初始状态)。两个异步任务各自拿着操作触发时的旧数据快照,后续的修改都是基于这个旧快照,而非实时最新的用户数据。
  2. 写入顺序的覆盖问题:砍树的冷却时间是5秒,你3秒后触发采矿,所以砍树会先完成并写入wood+5的用户数据;但采矿完成时,它用的还是触发采矿时拿到的初始user(wood仍是初始值),写入时就把刚更新的wood又覆盖回了初始状态,只保留了stone+3的修改。
  3. 额外的语法错误:你的saveUser对象里还有个明显的笔误,比如砍树代码里写了user.mine_time : user.mine_time,这是无效的对象键名,应该是wood_time: user.wood_time(采矿逻辑里对应mine_time: user.mine_time),这个问题虽不直接导致数据回退,但会导致时间属性无法正确保存。

修复方案

核心解决思路是每次修改用户数据前,都读取最新的文件内容,而非依赖操作触发时的旧数据快照,同时改用异步文件操作避免阻塞事件循环,下面是具体的修改步骤:

1. 替换同步文件操作为异步Promise版本

Node.js的fs.promises提供了异步的文件读写方法,既能避免同步操作阻塞事件循环,也能更好地和你的Promise链结合。

2. 在写入前重新读取最新用户数据

不要在操作开始时就保存user快照,而是在准备写入数据的环节,重新读取文件中的最新用户数据,基于这个最新数据做修改。

3. 优化冷却机制(可选但推荐)

当前的mine_cooltime和wood_cooltime是全局变量,若有多个用户同时操作会互相干扰,建议改成用户级的冷却(比如存在用户数据里,或用Map存储每个用户的冷却状态)。


修复后的代码示例

保留原sleep函数

function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }

采矿操作修复版

if (mine_cooltime === 0) {
  mine_cooltime = 1;
  const msgRef = await message.reply({embeds: [embed_mine_stone]});
  
  sleep(user.mine_time)
    .then(() => msgRef.edit({embeds: [embed_mine_stone_end]}))
    // 重新读取最新的用户数据,确保修改基于实时状态
    .then(async () => {
      const latestUser = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
      // 只修改需要更新的字段,其余保留最新值
      return {
        ...latestUser,
        stone: latestUser.stone + 3,
        findStone: 1,
        mine_time: latestUser.mine_time
      };
    })
    .then(async (updatedUser) => {
      await fs.promises.writeFile(filePath, JSON.stringify(updatedUser));
    })
    .then(() => mine_cooltime = 0)
    .catch(err => {
      console.error('采矿操作出错:', err);
      mine_cooltime = 0; // 出错时重置冷却,避免用户永久无法操作
    });
}

砍树操作修复版

if (wood_cooltime === 0) {
  wood_cooltime = 1;
  const msgRef = await message.reply({embeds: [embed_wood_cut]});
  
  sleep(user.wood_time)
    .then(() => msgRef.edit({embeds: [embed_wood_cut_end]}))
    // 重新读取最新用户数据
    .then(async () => {
      const latestUser = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
      return {
        ...latestUser,
        wood: latestUser.wood + 5,
        findWood: 1,
        wood_time: latestUser.wood_time
      };
    })
    .then(async (updatedUser) => {
      await fs.promises.writeFile(filePath, JSON.stringify(updatedUser));
    })
    .then(() => wood_cooltime = 0)
    .catch(err => {
      console.error('砍树操作出错:', err);
      wood_cooltime = 0;
    });
}

额外优化:多用户友好的冷却机制

如果你的机器人支持多用户,全局冷却变量会导致用户间互相干扰,建议改用Map存储每个用户的冷却状态:

// 全局定义冷却Map,键为用户ID+操作类型
const cooldowns = new Map();

// 采矿操作的冷却判断示例
const userId = message.author.id;
const mineCooldownKey = `mine_${userId}`;
if (!cooldowns.has(mineCooldownKey)) {
  cooldowns.set(mineCooldownKey, true);
  // ... 采矿操作逻辑 ...
  // 最后在重置冷却时删除对应键
  cooldowns.delete(mineCooldownKey);
} else {
  await message.reply('你正在采矿冷却中,请稍后再试!');
}

这样修改后,不管两个操作的执行顺序如何,每次写入数据都会基于文件中的最新内容,就不会出现一个操作覆盖另一个操作修改的情况了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 06:48:19