如何让aiogram有限状态机为单条消息而非整个会话保存状态?
aiogram FSM如何为单条消息而非整个会话保存状态?
我在用aiogram的FSM模块开发Telegram机器人,用来管理用户输入,但发现FSM是按整个会话保存状态的,不是单条消息,这给InlineKeyboardMarkup的交互带来了问题。
比如我有两个回调处理器:
@dp.callback_query_handler(state=State.start, text=State.tariffs._state) async def tariffs(callback_query: types.CallbackQuery, state: FSMContext): button1 = InlineKeyboardButton(State.course_1._description, callback_data=State.course_1._state) button2 = InlineKeyboardButton(State.course_2._description, callback_data=State.course_2._state) button3 = InlineKeyboardButton(State.course_3._description, callback_data=State.course_3._state) keyboard = InlineKeyboardMarkup() keyboard.row(button1, button2, button3) await bot.edit_message_text(text=State.tariffs._text, message_id=callback_query.message.message_id, chat_id=callback_query.message.chat.id, reply_markup=keyboard) await State.tariffs.set() @dp.callback_query_handler(state=State.start, text=State.mytariffs._state) async def mytariffs(callback_query: types.CallbackQuery, state: FSMContext): button1 = InlineKeyboardButton(State.mycourse_1._description, callback_data=State.mycourse_1._state) keyboard = InlineKeyboardMarkup() keyboard.row(button1) await bot.edit_message_text(text=State.mytariffs._text, message_id=callback_query.message.message_id, chat_id=callback_query.message.chat.id, reply_markup=keyboard) await State.mytariffs.set()
当我收到两条消息,在其中一条里进入tariffs状态后,就没法在另一条消息里访问tariffs或mytariffs板块了。想问能不能修改aiogram FSM让它为每条消息单独保存状态?我试过改dispatcher.py,用CallbackQuery里的消息ID代替会话ID来保存,但现在识别不了已设置的状态。
解决方案
不建议直接修改aiogram的核心源码(比如dispatcher.py),这样会破坏原有逻辑,且后续版本更新会覆盖你的修改。推荐两种更稳妥的方式:
1. 自定义基于消息ID的FSM存储
通过重写FSM存储类的键生成逻辑,让状态以chat_id+user_id+message_id为唯一标识,实现单条消息独立保存状态。
from aiogram.contrib.fsm_storage.memory import MemoryStorage from aiogram.dispatcher import Dispatcher class MessageBasedStorage(MemoryStorage): async def get_key(self, chat_id: int, user_id: int, state=None, default=None): # 获取当前更新中的消息ID dp = Dispatcher.get_current() update = dp.current_update.get() message_id = None if update.callback_query: message_id = update.callback_query.message.message_id elif update.message: message_id = update.message.message_id if message_id is not None: # 构建包含消息ID的唯一键 return (chat_id, user_id, message_id) # fallback到默认的会话键 return await super().get_key(chat_id, user_id, state, default)
初始化Dispatcher时使用这个自定义存储:
storage = MessageBasedStorage() dp = Dispatcher(bot, storage=storage)
这样每条消息的状态会单独存储,不同消息间的状态不会互相干扰。
2. 在回调数据中携带消息上下文
完全摆脱FSM的会话状态依赖,把消息ID和状态信息编码到callback_data中,处理器通过解析回调数据来判断是否执行逻辑。
修改按钮的回调数据生成逻辑:
# 示例:给回调数据加上当前消息ID后缀 button_tariffs = InlineKeyboardButton(State.tariffs._description, callback_data=f"{State.tariffs._state}:{callback_query.message.message_id}")
然后编写处理器时,先解析回调数据中的消息ID,匹配当前交互的消息后再执行逻辑:
@dp.callback_query_handler(lambda c: c.data.startswith(f"{State.tariffs._state}:")) async def tariffs(callback_query: types.CallbackQuery): # 拆分回调数据,获取状态标识和消息ID state_key, message_id = callback_query.data.split(':') # 验证消息ID是否匹配当前交互的消息 if int(message_id) != callback_query.message.message_id: return # 正常执行按钮逻辑 button1 = InlineKeyboardButton(State.course_1._description, callback_data=f"{State.course_1._state}:{callback_query.message.message_id}") button2 = InlineKeyboardButton(State.course_2._description, callback_data=f"{State.course_2._state}:{callback_query.message.message_id}") button3 = InlineKeyboardButton(State.course_3._description, callback_data=f"{State.course_3._state}:{callback_query.message.message_id}") keyboard = InlineKeyboardMarkup() keyboard.row(button1, button2, button3) await bot.edit_message_text(text=State.tariffs._text, message_id=callback_query.message.message_id, chat_id=callback_query.message.chat.id, reply_markup=keyboard)
这种方式不需要依赖FSM的状态管理,完全通过回调数据控制交互流程,适合多消息并行操作的场景。
内容的提问来源于stack exchange,提问作者mold
相关产品推荐
相关产品推荐

