Перейти к содержанию

Архитектура и HTTP‑сервер

Этот раздел описывает, как устроен веб‑слой Telegram‑бота: какие функции отвечают за запуск HTTP‑сервера, приём webhook‑запросов и передачу их в бизнес‑логику. Ниже приведены примеры кода из реальных модулей, чтобы было легче сопоставлять описание с проектом.

Точка входа и создание приложения

Главная точка входа находится в файле telegram-bot/main.py. Здесь определяется синхронная функция create_app(), которая создаёт aiohttp‑приложение, добавляет маршруты для проверки статуса и подключает интеграцию с aiogram:

def create_app() -> web.Application:
    # Увеличиваем лимит тела запроса для загрузки DOCX (по умолчанию aiohttp 1 МБ)
    app = web.Application(client_max_size=50 * 1024 ** 2)  # 50 МБ

    async def health_check(request):
        return web.json_response({"status": "ok", "service": "telegram-bot"})

    app.router.add_get("/health", health_check)
    app.router.add_get("/", health_check)
    app.router.add_post("/cargo-assigned", handle_cargo_assignment)
    app.router.add_post("/cargo-docx", handle_cargo_docx)

    @web.middleware
    async def logging_middleware(request, handler):
        ...
        return await handler(request)

    app.middlewares.append(logging_middleware)

    SimpleRequestHandler(
        dispatcher=dp,
        bot=bot,
        secret_token=None,
    ).register(app, path=config.WEBHOOK_SECRET_PATH)
    setup_application(app, dp, bot=bot)

    return app

Параметры функции create_app

Name Type Description Default
Функция не принимает аргументов и возвращает web.Application.

Возвращаемое значение

Name Type Description
app web.Application Готовое aiohttp‑приложение Telegram‑бота.

В этом фрагменте видно, что:

  • web.Application(...) создаётся с увеличенным client_max_size, чтобы принимать DOCX через POST /cargo-docx;
  • маршруты GET / и GET /health обрабатываются общей функцией health_check;
  • есть дополнительные интеграционные эндпоинты:
  • POST /cargo-assigned — уведомления о взятой заявке (вызовы со стороны MCP‑сервера);
  • POST /cargo-docx — приём и пересылка DOCX пользователю (вызовы со стороны MCP‑сервера);
  • middleware logging_middleware логирует входящие запросы (в том числе выводит тело webhook‑запроса для пути config.WEBHOOK_SECRET_PATH).

Контракты (структура запросов/ответов) для POST /cargo-assigned и POST /cargo-docx описаны на странице HTTP API (служебные эндпоинты).

Через SimpleRequestHandler по пути config.WEBHOOK_SECRET_PATH подключается обработчик webhook‑запросов от Telegram к диспетчеру dp.

Инициализация и запуск сервера оформлены в функции main():

async def main() -> None:
    await on_startup()
    app = create_app()

    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, config.APP_HOST, config.APP_PORT)
    await site.start()

    await asyncio.Future()

Параметры функции main

Name Type Description Default
Точка входа; не принимает аргументов и ничего не возвращает.

Возвращаемое значение

Name Type Description
Функция работает до остановки процесса и явного значения не возвращает.

Здесь вызывается on_startup(), затем создаётся приложение, запускается HTTP‑сервер на config.APP_HOST:config.APP_PORT, и процесс переходит в ожидание, пока приложение не будет остановлено.

Жизненный цикл: запуск и остановка

Функция on_startup() выполняет начальную инициализацию: подключает роутеры обработчиков, заранее создаёт экземпляр OpenAIService через get_openai_service(), инициализирует Telethon и настраивает webhook в Telegram с помощью WebhookService:

async def on_startup() -> None:
    dp.include_router(messages.router)

    get_openai_service()

    await init_telethon()

    webhook_service = WebhookService(bot)
    await webhook_service.setup()

Остановка бота обрабатывается отдельной функцией on_shutdown(), которая закрывает HTTP‑сессию бота и останавливает Telethon:

async def on_shutdown() -> None:
    await bot.session.close()
    await shutdown_telethon()

Параметры функций запуска/остановки

Name Type Description Default
on_startup Асинхронная функция без аргументов; выполняет первоначальную инициализацию.
on_shutdown Асинхронная функция без аргументов; аккуратно завершает работу HTTP‑клиента бота.

Возвращаемое значение

Name Type Description
Обе функции завершаются без явного возвращаемого значения.

После выполнения on_shutdown() можно безопасно останавливать aiohttp‑сервер.

Создание Bot и Dispatcher

Создание экземпляров Bot и Dispatcher вынесено в модуль telegram-bot/bot/loader.py. Это позволяет использовать одни и те же объекты во всех частях приложения:

from aiogram import Bot, Dispatcher

from config.settings import config

bot = Bot(token=config.BOT_TOKEN, parse_mode="HTML")
dp = Dispatcher()

Модуль loader.py импортируется в main.py и других файлах, которые взаимодействуют с Telegram API и инфраструктурой aiogram.

Обработчик бизнес‑сообщений

За обработку бизнес‑сообщений отвечает модуль telegram-bot/handlers/messages.py. Он фильтрует входящие события бизнес‑чата (игнорирует исходящие сообщения, в том числе отправленные через Telethon) и разделяет сценарии «текст» и «документ»:

router = Router()


@router.business_message()
async def handle_business_message(message: Message):
    if not _is_incoming_business_message(message):
        return
    if message.document:
        await _handle_business_document(message)
        return
    await _handle_business_text(message)

Текстовый сценарий делегируется в utils/ai_processor.process_ai_request: обработчик передаёт примитивные значения (tg_id, request_text, message_id, business_connection_id) и не завязывает процессор на тип Message.

WebhookService и настройка webhook в Telegram

Сервис WebhookService описан в файле telegram-bot/services/webhook.py. Его задача — убедиться, что Telegram настроен на отправку webhook‑запросов в правильное место:

BUSINESS_ALLOWED_UPDATES = [
    "message",
    "business_message",
    "edited_business_message",
    "deleted_business_messages",
    "business_connection",
]

class WebhookService:
    def __init__(self, bot: Bot) -> None:
        self._bot = bot
        self._webhook_url = config.WEBHOOK_URL

    async def setup(self) -> None:
        info = await self._bot.get_webhook_info()
        if info.url == self._webhook_url:
            return

        await self._bot.set_webhook(
            url=self._webhook_url,
            drop_pending_updates=False,
            allowed_updates=BUSINESS_ALLOWED_UPDATES,
        )

В конструкторе класс получает экземпляр Bot и вычисляет webhook_url из config.WEBHOOK_URL. Метод setup() сначала запрашивает текущие настройки webhook через get_webhook_info(). Если URL совпадает с ожидаемым, метод просто завершает работу. Если URL отличается или не задан, вызывается set_webhook() с нужным адресом и списком разрешённых типов обновлений. Дополнительно в классе может быть метод get_info(), возвращающий краткое состояние webhook для диагностик.

Параметры класса WebhookService

Name Type Description Default
bot Bot Экземпляр Telegram‑бота, через который выполняются запросы get_webhook_info и set_webhook.
setup метод Асинхронный метод без аргументов; проверяет текущий webhook и при необходимости перенастраивает его.

Поток обработки запроса целиком

Если посмотреть на поток обработки запроса целиком, он выглядит так. Telegram отправляет бизнес‑сообщение на настроенный webhook URL. aiohttp‑приложение в main.py принимает HTTP‑запрос и передаёт его в aiogram‑диспетчер через SimpleRequestHandler. Модуль handlers/messages.py получает объект Message и вызывает process_ai_request из utils/ai_processor.py.

Внутри process_ai_request определяется user_id через MCP‑инструмент verify_user, при необходимости сохраняется телефон, обрабатываются служебные запросы (сброс контекста, смена роли), а затем текст передаётся в OpenAIService.process_message. Ответ модели возвращается в виде строки, отправляется пользователю и записывается в таблицу dialogue_history. Детали того, как устроено определение пользователя, работа с БД и интеграция с OpenAI, описаны в отдельных разделах про конфигурацию и MCP.