
本文详解如何使用 node-telegram-bot-api 实现混合交互流程:既响应预设键盘按钮(如“添加贺卡”),又可靠捕获并校验用户自由输入(如姓名、电话、地址),并通过状态管理确保多步对话逻辑清晰、不丢失上下文。
本文详解如何使用 node-telegram-bot-api 实现混合交互流程:既响应预设键盘按钮(如“添加贺卡”),又可靠捕获并校验用户自由输入(如姓名、电话、地址),并通过状态管理确保多步对话逻辑清晰、不丢失上下文。
在构建 Telegram 花店机器人这类多步骤表单场景时,仅依赖 bot.onText() 的静态正则匹配(如 /^Add a card$/)无法区分「用户当前应答的是哪一步」——例如,当 bot 正在等待收件人电话时,若用户误发“Add a card”,系统不应触发卡片流程,而应提示“请先输入电话号码”。
✅ 正确方案:结合用户会话状态 + 动态输入校验
1. 维护用户对话状态(推荐使用轻量数据库或内存 Map)
// 使用 Map 模拟简单状态存储(生产环境建议用 Redis 或 SQLite)
const userStates = new Map(); // key: chatId, value: { step: 'waiting_for_phone', data: {} }
// 示例:用户点击“Add a card”
bot.onText(/Add a card/, (msg) => {
const chatId = msg.chat.id;
userStates.set(chatId, { step: 'waiting_for_delivery_date' });
bot.sendMessage(chatId, messages.askDeliveryDate, {
reply_markup: { keyboard: keyboard.dateMenu, resize_keyboard: true }
});
});
2. 统一捕获文本输入,并按状态路由处理
// 兜底监听所有文本消息(排除命令和键盘按钮触发的显式事件)
bot.on('message', async (msg) => {
if (!msg.text || msg.text.startsWith('/')) return;
const chatId = msg.chat.id;
const state = userStates.get(chatId);
if (!state) return; // 无活跃流程,忽略或引导重新开始
switch (state.step) {
case 'waiting_for_phone':
if (/^\+(?:[0-9] ?){6,14}[0-9]$/.test(msg.text.trim())) {
state.data.phone = msg.text.trim();
state.step = 'waiting_for_address';
bot.sendMessage(chatId, messages.askAddress);
} else {
bot.sendMessage(chatId, '⚠️ 请输入有效的国际格式手机号(例如:+86 138 1234 5678)');
}
break;
case 'waiting_for_address':
if (msg.text.trim().length >= 10) {
state.data.address = msg.text.trim();
// 进入下一步(如确认订单)
await sendOrderSummary(chatId, state.data);
userStates.delete(chatId); // 清理已完成会话
} else {
bot.sendMessage(chatId, '? 请提供完整配送地址(至少10个字符)');
}
break;
default:
bot.sendMessage(chatId, '请按提示操作,或发送 /start 重新开始');
}
});
3. 增强体验:结合 Telegram 原生功能减少手动输入
对于敏感/结构化字段(如手机号),优先使用 KeyboardButton 的 request_contact:
const contactKeyboard = {
keyboard: [
[{ text: '分享手机号', request_contact: true }]
],
resize_keyboard: true,
one_time_keyboard: true
};
bot.onText(/Enter phone manually/, (msg) => {
bot.sendMessage(msg.chat.id, '或点击下方按钮直接发送您的手机号:', {
reply_markup: contactKeyboard
});
});
// 处理联系人消息
bot.on('contact', (msg) => {
const chatId = msg.chat.id;
const state = userStates.get(chatId);
if (state?.step === 'waiting_for_phone') {
state.data.phone = msg.contact.phone_number;
state.step = 'waiting_for_address';
bot.sendMessage(chatId, messages.askAddress);
}
});
⚠️ 关键注意事项
-
避免多个
onText()冲突:不要为同一字段(如电话)同时注册全局正则和状态路由,否则可能重复触发。 -
超时清理:为
userStates添加 TTL(如 15 分钟无操作自动清除),防止内存泄漏。 -
键盘按钮 ≠ 文本消息:
reply_markup中的按钮发送的是纯文本,但需确保按钮文案与onText()正则严格一致(注意空格、大小写)。 -
错误恢复:提供
/cancel命令重置当前流程,提升用户体验。
通过状态驱动的设计,你的花店 Bot 将能稳健支撑「键盘选择 → 自由输入 → 再次选择」的复杂服务链路,兼顾准确性、可维护性与用户友好性。










