Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
FSM (Finite State Machine) в aiogram — управление диалогом
FSM (Конечный автомат) — это инструмент для управления состоянием диалога с пользователем в Telegram боте. Это очень важная часть разработки интерактивных ботов.
Что такое FSM
FSM — это система, которая определяет, в каком "состоянии" находится пользователь и какие действия он может выполнять.
Пример из реальной жизни: заказ доставки
Пользователь может находиться в состояниях:
- start → ожидание действия
- choosing_item → выбирает товар
- entering_address → вводит адрес доставки
- confirming_order → подтверждает заказ
- completed → заказ завершен
Как это помогает
Без FSM:
# ❌ Невозможно отследить, что делает пользователь
@router.message()
async def any_message(message: Message):
text = message.text
if text == "Купить":
# Но как узнать, что это первый раз?
# Может быть, пользователь уже выбрал товар?
pass
С FSM:
# ✅ Четко определено состояние пользователя
class OrderStates(StatesGroup):
choosing_item = State()
entering_address = State()
confirming_order = State()
@router.message(OrderStates.choosing_item)
async def process_item_choice(message: Message, state: FSMContext):
# Здесь точно знаем, что пользователь выбирает товар
await state.set_state(OrderStates.entering_address)
Основные компоненты
1. StatesGroup — определение состояний
from aiogram.fsm.state import State, StatesGroup
class RegistrationStates(StatesGroup):
waiting_name = State()
waiting_email = State()
waiting_phone = State()
waiting_password = State()
# Состояния — это просто строки
print(RegistrationStates.waiting_name) # 'RegistrationStates:waiting_name'
2. FSMContext — работа с состоянием
# Установить состояние
await state.set_state(RegistrationStates.waiting_name)
# Получить текущее состояние
current_state = await state.get_state()
print(current_state) # 'RegistrationStates:waiting_name'
# Сохранить данные (key-value)
await state.update_data(name="John")
# Получить данные
data = await state.get_data()
print(data) # {'name': 'John'}
# Очистить состояние
await state.clear()
3. Handlers фильтруют по состоянию
@router.message(RegistrationStates.waiting_name)
async def process_name(message: Message, state: FSMContext):
# Этот обработчик запустится ТОЛЬКО если пользователь в состоянии waiting_name
await state.update_data(name=message.text)
await state.set_state(RegistrationStates.waiting_email)
await message.answer("Введи email")
Полный пример: регистрация пользователя
from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.filters import Command
router = Router()
# 1. Определяем состояния
class RegistrationStates(StatesGroup):
waiting_name = State()
waiting_email = State()
waiting_phone = State()
# 2. Команда /start — начинаем регистрацию
@router.message(Command("start"))
async def start_registration(message: Message, state: FSMContext):
await state.set_state(RegistrationStates.waiting_name)
await message.answer("Как тебя зовут?")
# 3. Получение имени
@router.message(RegistrationStates.waiting_name)
async def process_name(message: Message, state: FSMContext):
# Проверка
if len(message.text) < 2:
await message.answer("Имя слишком короткое, введи еще раз")
return
# Сохраняем
await state.update_data(name=message.text)
await state.set_state(RegistrationStates.waiting_email)
await message.answer("Введи email")
# 4. Получение email
@router.message(RegistrationStates.waiting_email)
async def process_email(message: Message, state: FSMContext):
email = message.text
# Валидация
if "@" not in email:
await message.answer("Некорректный email, попробуй еще")
return
await state.update_data(email=email)
await state.set_state(RegistrationStates.waiting_phone)
await message.answer("Введи номер телефона")
# 5. Получение телефона — завершение
@router.message(RegistrationStates.waiting_phone)
async def process_phone(message: Message, state: FSMContext):
# Получаем ВСЕ собранные данные
data = await state.get_data()
# Сохраняем в БД
user_data = {
"name": data["name"],
"email": data["email"],
"phone": message.text
}
# Очищаем состояние
await state.clear()
await message.answer(
f"Спасибо! Ты зарегистрирован:\n"
f"Имя: {user_data['name']}\n"
f"Email: {user_data['email']}\n"
f"Телефон: {user_data['phone']}"
)
Проблема отключения обработчиков
Без FSM пользователь может отправить любое сообщение и оно обработается первым подходящим хендлером.
# ❌ БЕЗ FSM — конфликты обработчиков
@router.message()
async def process_any(message: Message):
if message.text == "1":
await message.answer("Вы выбрали 1")
elif message.text == "2":
await message.answer("Вы выбрали 2")
# А что если пользователь должен выбирать только ДО регистрации?
# Нет способа это проверить!
# ✅ С FSM — точно контролируем
class MenuStates(StatesGroup):
selecting_option = State()
@router.message(MenuStates.selecting_option, F.text.in_(["1", "2"]))
async def process_choice(message: Message):
# Срабатывает ТОЛЬКО если пользователь в selecting_option
# И текст "1" или "2"
pass
Сохранение данных между состояниями
class OrderStates(StatesGroup):
choosing_item = State()
choosing_quantity = State()
confirming = State()
@router.message(OrderStates.choosing_item)
async def choose_item(message: Message, state: FSMContext):
# Сохраняем выбранный товар
await state.update_data(item=message.text)
await state.set_state(OrderStates.choosing_quantity)
await message.answer("Сколько штук?")
@router.message(OrderStates.choosing_quantity)
async def choose_quantity(message: Message, state: FSMContext):
# Получаем сохраненный товар
data = await state.get_data()
item = data["item"]
quantity = int(message.text)
# Обновляем данные
await state.update_data(quantity=quantity)
await state.set_state(OrderStates.confirming)
await message.answer(
f"Вы заказали {quantity} шт. товара '{item}'. Подтвердить?"
)
@router.message(OrderStates.confirming, F.text.in_(["Да", "Нет"]))
async def confirm_order(message: Message, state: FSMContext):
data = await state.get_data()
if message.text == "Да":
# Сохраняем заказ в БД
print(f"Заказ: {data}")
await state.clear() # Очищаем состояние
await message.answer("Спасибо за заказ!")
Использование с inline кнопками
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.fsm.state import State, StatesGroup
class PurchaseStates(StatesGroup):
selecting_item = State()
confirming = State()
@router.message(Command("shop"))
async def shop(message: Message, state: FSMContext):
await state.set_state(PurchaseStates.selecting_item)
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Товар 1 ($10)", callback_data="item_1")],
[InlineKeyboardButton(text="Товар 2 ($20)", callback_data="item_2")],
[InlineKeyboardButton(text="Отмена", callback_data="cancel")],
])
await message.answer("Выбери товар:", reply_markup=keyboard)
@router.callback_query(PurchaseStates.selecting_item)
async def process_item_selection(query, state: FSMContext):
if query.data == "cancel":
await state.clear()
await query.message.edit_text("Отмена")
return
await state.update_data(item=query.data)
await state.set_state(PurchaseStates.confirming)
await query.message.edit_text("Подтвердить заказ?")
Отмена операции (важно!)
# Пользователь может нажать /cancel в любой момент
@router.message(Command("cancel"))
async def cancel_operation(message: Message, state: FSMContext):
current_state = await state.get_state()
if current_state is None:
await message.answer("Нечего отменять")
return
await state.clear()
await message.answer("Операция отменена")
Хранение FSM
По умолчанию FSM хранится в памяти (MemoryStorage). Для production используй другие хранилища:
# ❌ В памяти (теряется при перезагрузке бота)
from aiogram.fsm.storage.memory import MemoryStorage
storage = MemoryStorage()
# ✅ Redis (рекомендуется)
from aiogram.fsm.storage.redis import RedisStorage
storage = RedisStorage.from_url("redis://localhost:6379/1")
# ✅ PostgreSQL
from aiogram.fsm.storage.sql import SQLAlchemyStorage
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_async_engine("postgresql+asyncpg://...")
storage = SQLAlchemyStorage(engine=engine)
bot = Bot(token=TOKEN, session=session)
dp = Dispatcher(storage=storage)
Когда использовать FSM
- Многошаговые диалоги (регистрация, заказ, опрос)
- Валидация пользовательского ввода
- Контроль потока диалога
- Сохранение данных между сообщениями
Когда НЕ нужен FSM
- Простые боты с одной командой
- Боты с inline кнопками (можно без состояния)
- Статические ответы
Итог
FSM в aiogram — это мощный инструмент для управления диалогом. Без него очень сложно создавать интерактивные боты, где пользователь проходит несколько шагов.
В production ботах, которые я разрабатывал, FSM использовался во всех сложных сценариях взаимодействия с пользователем. Это делает код чище и логику понятнее.