Консольна команда 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

Розбираємо, що автоматизувати в бізнесі та як це працює на практиці. Без спаму.