如何在机器人重启或崩溃后恢复Timeout?求Node.js工具或方法
解决Glitch托管Discord机器人重启后恢复Timeout的方案
我来给你梳理一下核心思路和具体实现——因为Glitch上的进程重启后,内存里的setTimeout肯定会丢失,所以关键是把每个抽奖(Giveaway)的定时任务信息持久化存储,等机器人重启后再重新计算剩余时间、恢复定时器。
核心思路
- 把抽奖的关键信息(结束时间、消息ID、频道ID、奖品等)存在本地轻量数据库里
- 机器人启动时,扫描所有未结束的抽奖,计算当前到结束时间的剩余时长,重新设置
setTimeout - 抽奖结束后,执行你的逻辑并清理数据库里的对应记录
具体实现步骤
1. 选择轻量持久化工具
推荐用lowdb,它是基于JSON文件的轻量数据库,完全适配Glitch这种不需要复杂数据库的场景。先安装依赖:
npm install lowdb
2. 初始化数据库
创建一个db.js文件,用来管理数据库的读写操作:
const low = require('lowdb'); const FileSync = require('lowdb/adapters/FileSync'); // 用FileSync适配器确保写入同步,适配Glitch环境 const adapter = new FileSync('giveaways.json'); const db = low(adapter); // 初始化数据库默认结构 db.defaults({ giveaways: [] }).write(); module.exports = db;
3. 修改抽奖创建逻辑
当你发起抽奖时,不再直接调用setTimeout,而是先把抽奖信息存入数据库,再设置定时器:
const db = require('./db'); const ms = require('ms'); // 假设你已经安装了ms库处理时间格式 // 这是你创建抽奖的核心逻辑(替换你原来的setTimeout部分) function createGiveaway(m, title, duration, room) { // 计算抽奖结束的时间戳(当前时间+持续时长) const endTimestamp = Date.now() + ms(duration); // 用抽奖消息的ID作为唯一标识 const giveawayId = m.id; // 把抽奖信息存入数据库 db.get('giveaways') .push({ id: giveawayId, messageId: m.id, channelId: m.channel.id, roomChannelId: room.replace(/\D/g, ''), title: title, endTimestamp: endTimestamp }) .write(); // 设置定时器,到点执行结束逻辑 setTimeout(() => { endGiveaway(giveawayId); }, ms(duration)); }
4. 封装抽奖结束逻辑
把你原来的结束逻辑抽成独立函数,从数据库读取数据执行:
const db = require('./db'); async function endGiveaway(giveawayId) { // 从数据库获取对应抽奖信息 const giveaway = db.get('giveaways').find({ id: giveawayId }).value(); if (!giveaway) return; // 已经处理过的抽奖直接跳过 try { // 获取抽奖消息(这里适配Discord.js v12,根据你的版本调整API) const channel = await client.channels.fetch(giveaway.channelId); const m = await channel.messages.fetch(giveaway.messageId); // 你的原逻辑,变量从数据库的抽奖对象中获取 let users = m.reactions.get("🎉").users; let gFilter = users.filter(u => !u.bot).random(); const giveEmbed = { title: null, description: null, fields: [], timestamp: new Date(), footer: { text: `Ended At` } }; if (!gFilter) { giveEmbed.fields = [{ name: '🎊 Giveaway Ended! 🎊', value: `**Winner :** No Winner\n**Prize :** ${giveaway.title}`, inline: false }]; await m.edit('**🎉 GIVEAWAY ENDED 🎉 @everyone**', { embed: giveEmbed }); const roomChannel = await client.channels.fetch(giveaway.roomChannelId); await roomChannel.send(`**The Giveaway Ended With No Winner 😦**`); } else { giveEmbed.fields = [{ name: '🎊 Giveaway Ended! 🎊', value: `**Winner :** ${gFilter}\n**Prize :** ${giveaway.title}`, inline: false }]; await m.edit('**🎉 GIVEAWAY ENDED 🎉 @everyone**', { embed: giveEmbed }); const roomChannel = await client.channels.fetch(giveaway.roomChannelId); await roomChannel.send(`**Congratulations ${gFilter} ! You Won The __${giveaway.title}__ !!! <a:crazy:526018890208116738>**`); await gFilter.send(`**Congratulations ${gFilter} ! You Won The __${giveaway.title}__ !!! <a:crazy:526018890208116738>\nAsk for Your Prize In <#593334823230242817> <a:RainbowParrot:597540860934225960>**`); } } catch (error) { console.error('处理抽奖结束时出错:', error); } finally { // 从数据库移除该抽奖记录,避免重启后重复处理 db.get('giveaways').remove({ id: giveawayId }).write(); } }
5. 机器人启动时恢复未结束的抽奖
在你的机器人入口文件(比如index.js)里,添加启动后的恢复逻辑:
const db = require('./db'); const ms = require('ms'); // 机器人就绪后执行恢复逻辑 client.on('ready', async () => { console.log('机器人已上线,开始恢复未结束的抽奖...'); const now = Date.now(); // 筛选出还没到结束时间的抽奖 const activeGiveaways = db.get('giveaways').filter(g => g.endTimestamp > now).value(); for (const giveaway of activeGiveaways) { const remainingTime = giveaway.endTimestamp - now; if (remainingTime <= 0) { // 已经到结束时间,直接执行结束逻辑 endGiveaway(giveaway.id); continue; } // 设置新的定时器 setTimeout(() => { endGiveaway(giveaway.id); }, remainingTime); console.log(`恢复抽奖: ${giveaway.title},剩余时间: ${ms(remainingTime, { long: true })}`); } console.log(`共恢复 ${activeGiveaways.length} 个未结束的抽奖`); });
注意事项
- Glitch的文件系统是持久化的,但如果项目长时间休眠,可能会有轻微延迟,但
lowdb生成的giveaways.json文件会被保留。 - 确保你的Discord.js版本和代码中的API调用匹配(比如
fetch方法在v12和v13+的写法有区别)。 - 如果担心消息ID重复,可以用
uuid库生成更唯一的抽奖ID。
内容的提问来源于stack exchange,提问作者user10294268




