Консольная команда Yii2 для обслуживания бота: setWebhook, удаление вебхука, getMe и ротация логов

При разработке и эксплуатации Telegram-ботов на Yii2 консольный интерфейс (CLI) часто обходят стороной, перекладывая задачи администрирования на веб-панели или ручные GET-запросы в браузере. Однако автоматизация деплоя, диагностика в продакшене и поддержание чистоты базы данных требуют надежного консольного инструментария.

В этой статье мы напишем полноценный консольный контроллер для Yii2, который решает три критические задачи: управление вебхуками (включая генерацию и сохранение secret_token), быстрый health-check соединения с Telegram API и безопасную ротацию накопленных логов обновлений (updates) без блокировки таблиц базы данных.

Архитектура консольного контроллера в Yii2

Для взаимодействия с Telegram Bot API мы будем использовать классический cURL. Использование сторонних SDK часто избыточно и усложняет отладку. Наш контроллер будет наследоваться от yii\console\Controller, получать токен из безопасного окружения (компонента или переменных среды) и выполнять запросы с валидацией HTTP-кодов ответа и структуры JSON.

Первый шаг — создание базовой структуры консольного контроллера и метода отправки запросов к API. Мы не используем ненадежный file_get_contents, так как он не умеет гибко обрабатывать таймауты и возвращает false при любых HTTP-ошибках (например, 4xx/5xx), лишая нас диагностической информации от Telegram.

<?php

namespace app\commands;

use Yii;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\Console;

class TelegramController extends Controller
{
private ?string $token = null;

public function init()
{
parent::init();
// Загружаем токен из переменных окружения или конфигурации Yii
$this->token = getenv("TELEGRAM_BOT_TOKEN") ?: (Yii::$app->params["telegramBotToken"] ?? null);
if (!$this->token) {
$this->stderr("Ошибка: Токен Telegram бота не сконфигурирован.\n", Console::FG_RED);
Yii::$app->end(ExitCode::CONFIG);
}
}

/**
* Универсальный метод отправки запросов к Telegram Bot API через cURL
*/
protected function sendRequest(string $method, array $params = []): array
{
$url = "https://api.telegram.org/bot{$this->token}/{$method}";
$ch = curl_init();

curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);

if ($response === false) {
throw new \RuntimeException("cURL Error: {$error}");
}

$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException("Ошибка декодирования JSON: " . json_last_error_msg());
}

if (($data["ok"] ?? false) !== true) {
$description = $data["description"] ?? "Unknown error";
$errorCode = $data["error_code"] ?? $httpCode;
throw new \RuntimeException("Telegram API Error [{$errorCode}]: {$description}");
}

return $data["result"] ?? [];
}
}

Реализация getMe, setWebhook и deleteWebhook

Теперь добавим экшены для управления жизненным циклом вебхука и диагностики. Метод getMe служит отличным health-check-инструментом для проверки сетевой связности сервера с серверами Telegram и валидности токена.

При регистрации вебхука через setWebhook критически важно использовать параметр secret_token. Этот заголовок (X-Telegram-Bot-Api-Secret-Token) будет приходить в каждом POST-запросе от Telegram. С его помощью ваш вебхук-контроллер сможет отсекать фейковые запросы от злоумышленников, знающих URL вашего обработчика. Мы будем генерировать этот токен на лету и сохранять его локально для валидации.

    /**
* Проверка работоспособности токена и связи с API (getMe)
*/
public function actionGetMe()
{
$this->stdout("Вызов метода getMe...\n", Console::FG_YELLOW);
try {
$result = $this->sendRequest('getMe');
$this->stdout("Соединение установлено!\n", Console::FG_GREEN);
$this->stdout("Бот: @{$result['username']} (ID: {$result['id']})\n", Console::FG_CYAN);
return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка диагностики: " . $e->getMessage() . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

/**
* Установка Webhook с автоматической генерацией Secret Token
* @param string $url Полный HTTPS URL обработчика на вашем сервере
*/
public function actionSetWebhook(string $url)
{
if (!filter_var($url, FILTER_VALIDATE_URL) || !str_starts_with($url, 'https://')) {
$this->stderr("Ошибка: URL должен быть валидным и начинаться с https://\n", Console::FG_RED);
return ExitCode::DATAERR;
}

// Безопасная генерация секретного ключа
$secretToken = Yii::$app->security->generateRandomString(32);
$this->stdout("Установка вебхука на URL: {$url}...\n", Console::FG_YELLOW);

try {
$this->sendRequest('setWebhook', [
'url' => $url,
'secret_token' => $secretToken,
'allowed_updates' => ['message', 'callback_query', 'my_chat_member'],
]);

// Сохраняем секрет в файл (или кэш/БД) для валидатора вебхук-контроллера
$path = Yii::getAlias('@runtime/tg_webhook_secret.bin');
file_put_contents($path, $secretToken);

$this->stdout("Вебхук успешно настроен!\n", Console::FG_GREEN);
$this->stdout("Секретный токен записан в runtime/tg_webhook_secret.bin\n", Console::FG_CYAN);
return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка установки вебхука: " . $e->getMessage() . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

/**
* Удаление вебхука бота
* @param bool $dropPending Сбросить ли необработанные апдейты на стороне Telegram
*/
public function actionDeleteWebhook(bool $dropPending = false)
{
$this->stdout("Удаление вебхука...\n", Console::FG_YELLOW);
try {
$this->sendRequest('deleteWebhook', [
'drop_pending_updates' => $dropPending
]);
$this->stdout("Вебхук успешно удален.\n", Console::FG_GREEN);
return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка при удалении вебхука: " . $e->getMessage() . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

Ротация логов входящих обновлений

При логировании входящих обновлений (Updates) в базу данных для отладки таблица логов быстро разрастается. Высоконагруженный бот может генерировать миллионы записей в неделю, что замедляет работу СУБД и расходует дисковое пространство.

Для предотвращения этой проблемы мы реализуем экшен ротации. Очистка старых логов большими кусками (DELETE FROM ... WHERE created_at ) может заблокировать таблицу на длительное время. Решение — удаление записей небольшими пакетами (батчами) в цикле с небольшими паузами, чтобы не нагружать транзакционный лог СУБД и не мешать параллельным вставкам от вебхука.

    /**
* Ротация базы данных: удаление логов апдейтов старше N дней
* @param int $days Срок хранения логов в днях
*/
public function actionRotateLogs(int $days = 14)
{
$this->stdout("Запуск ротации логов обновлений (удаление старше {$days} дней)...\n", Console::FG_YELLOW);

$db = Yii::$app->db;
$tableName = '{{%telegram_update_log}}';

if ($db->getTableSchema($tableName) === null) {
$this->stderr("Ошибка: Таблица {$tableName} не существует в БД.\n", Console::FG_RED);
return ExitCode::DATAERR;
}

$cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
$totalDeleted = 0;
$batchSize = 500; // Оптимальный размер пачки для удаления без блокировок

try {
do {
// Удаляем порциями по первичному ключу или индексу даты
$deleted = $db->createCommand(
"DELETE FROM {$tableName} WHERE created_at

Новые статьи — в Telegram

Разбираем, что автоматизировать в бизнесе и как это работает на практике. Без спама.