Node.js+Telegraf+MySQL开发Telegram Bot多请求异常求助
问题根因
你现在遇到的并发串数据、进程崩溃报错、运行久了数据丢失,全是代码写法问题,不需要额外复杂框架就能解决,核心问题有4个:
- 把单用户请求产生的
dataStore存在全局作用域,所有并发用户的请求共用同一个数组,A用户刚查完的数据,会被B用户的请求直接覆盖,必然出现不同用户数据混淆的问题 - 变量作用域混乱,
dataStore没有在使用它的代码块内声明,异步请求时序一乱,就会触发ReferenceError: dataStore is not defined;你在数据库回调里直接throw err会直接崩掉整个Node进程,机器人自然就冻结无响应 - 用
mysql.createConnection创建单连接处理请求,本身不支持并发,连接意外断开后也不会自动重连,跑久了连接失效就会出现数据丢失、请求无响应的问题;另外你贴的连接代码本身还有语法错误,配置对象没有闭合,漏了}),连接从一开始就不稳定 - 数据库查询结果的判断逻辑写错了,
conn.query返回的查询结果是数组,就算没匹配到数据也是空数组,直接写if(result==0)判断逻辑不可靠,很容易走到错误分支
修复方案
按下面步骤改就能解决所有问题:
- 把单数据库连接换成连接池,自动管理连接生命周期、支持并发请求,建议直接用
mysql2包替代老的mysql包,支持Promise写法,异步逻辑更不容易出错
const mysql = require('mysql2/promise'); // 创建连接池替代单连接 const pool = mysql.createPool({ host: "你的数据库地址", user: "数据库用户名", password: "数据库密码", database: "DB", waitForConnections: true, connectionLimit: 10, // 按你的并发量调整,一般小机器人10个连接足够 queueLimit: 0 });
- 注册Telegraf自带的session中间件,把每个用户的临时状态存在自己的会话上下文里,彻底隔离不同用户的数据,绝对不要再用全局变量存单用户的请求数据
const { Telegraf, Scenes, session } = require('telegraf'); const bot = new Telegraf('你的机器人Token'); // 一定要在注册场景之前挂载session中间件 bot.use(session()); // 注册你自己的wizard场景 const stage = new Scenes.Stage([/* 把你写的scanstarterpack等场景放到这里 */]); bot.use(stage.middleware());
- 改写查询逻辑,把查询到的用户数据存在当前请求的
ctx.session里,不要碰全局变量
// 删掉所有全局声明的dataStore变量,完全不要用 const arr = [wizardData.number, wizardData.firstnumber] const sql = 'SELECT DB.tblbotusers.Name,DB.tblbotusers.PhoneNumber AS phoneNumber,DB.tblbotusers.IDDealer ,DB.tblbotusers.UserID FROM DB.tblbotusers WHERE DB.tblbotusers.PhoneNumber IN (?);' // 用连接池执行查询 const [result] = await pool.query(sql, [arr]); // 正确判断空结果 if(result.length === 0) { console.log("Number Not Found In Database") ctx.replyWithHTML("❌Your <b>number</b> is not found in the database! Please register", Markup.inlineKeyboard([ [Markup.button.callback('Return to main menu','mainmenu')] ]) ) return; } console.log(result) // 数据存在当前用户的会话里,和其他用户完全隔离 ctx.session.dataStore = []; result.forEach(item => { ctx.session.dataStore.push({ Name:item.Name, phoneNumber:item.phoneNumber, IDDealer:item.IDDealer, UserID:item.UserID }) });
- 改写后续插入逻辑,从当前用户会话里取数据,不要访问全局变量,同时改掉全局抛错的写法
console.log(newagentFN+" "+newagentMSISDN+" "+newagentMSISDNtxt) // 从当前用户会话取数据,不会串到其他用户,也不会出现未定义 ctx.session.dataStore.forEach(item =>{ const newagent = [wizardData.agentaddname[0],wizardData.agentaddname[1],wizardData.agentidpassnum,status,wizardData.agentnumber,item.IDDealer]; const sql='INSERT INTO DB.tblbotagentstoadd(FirstName,LastName,IDNumber,SpecialInstructions,MSISDN,IDDealer) VALUES (?);'; pool.query(sql,[newagent],function(err,result){ if (err){ // 不要throw err,不然会直接崩掉整个进程,打日志返回错误即可 console.error('插入数据失败:', err); ctx.reply('操作失败,请稍后重试'); return; } }); }); // 用完临时数据记得清理,避免内存占用 ctx.session.dataStore = [];
注意:默认的内存session只适合本地调试用,生产环境建议把session存储到MySQL或者Redis里,不然机器人重启之后所有用户的会话状态都会清空,并发高了内存占用也会持续上涨。
内容的提问来源于stack exchange,提问作者sb_thedev
相关产品推荐
相关产品推荐

