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

如何让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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 15:03:15