Новые роуты API для статистики и мониторинга
- /api/version - /api/health - /api/stats
This commit is contained in:
@@ -9,6 +9,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Core\Bot;
|
||||||
|
use App\Core\Kernel;
|
||||||
|
use App\Core\StatisticsService;
|
||||||
use App\Errors\PlaylistNotFoundException;
|
use App\Errors\PlaylistNotFoundException;
|
||||||
use chillerlan\QRCode\QRCode;
|
use chillerlan\QRCode\QRCode;
|
||||||
use chillerlan\QRCode\QROptions;
|
use chillerlan\QRCode\QROptions;
|
||||||
@@ -81,4 +84,106 @@ class ApiController extends BasicController
|
|||||||
return $response->withStatus(200)
|
return $response->withStatus(200)
|
||||||
->withHeader('Content-Type', $mime);
|
->withHeader('Content-Type', $mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает информацию о плейлисте
|
||||||
|
*
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
* @param ResponseInterface $response
|
||||||
|
* @return ResponseInterface
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function version(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
return $this->responseJson($response, 200, [
|
||||||
|
'web' => Kernel::VERSION,
|
||||||
|
'php' => PHP_VERSION,
|
||||||
|
'keydb' => redis()->info('server')['redis_version'],
|
||||||
|
'checker' => 'todo',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает информацию о плейлисте
|
||||||
|
*
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
* @param ResponseInterface $response
|
||||||
|
* @return ResponseInterface
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function health(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
function getSize(string $directory): int
|
||||||
|
{
|
||||||
|
$size = 0;
|
||||||
|
foreach (glob($directory . '/*') as $path){
|
||||||
|
is_file($path) && $size += filesize($path);
|
||||||
|
is_dir($path) && $size += getSize($path);
|
||||||
|
}
|
||||||
|
return $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tgBotInfo = config('bot.token') ? new Bot()->api->getMe() : null;
|
||||||
|
$redisInfoServer = redis()->info('server'); // General information about the Redis server
|
||||||
|
$redisInfoClients = redis()->info('clients'); // Client connections section
|
||||||
|
$redisInfoMemory = redis()->info('memory'); // Memory consumption related information
|
||||||
|
$redisInfoPers = redis()->info('persistence'); // RDB and AOF related information
|
||||||
|
$redisInfoStats = redis()->info('stats'); // General statistics
|
||||||
|
$redisInfoCpu = redis()->info('cpu'); // CPU consumption statistics
|
||||||
|
$redisInfoCmd = redis()->info('commandstats'); // Redis command statistics
|
||||||
|
$redisInfoKeysp = redis()->info('keyspace'); // Database related statistics
|
||||||
|
$redisInfoErr = redis()->info('errorstats'); // Redis error statistics
|
||||||
|
|
||||||
|
$health = [
|
||||||
|
'fileCache' => [
|
||||||
|
'tv-logos' => [
|
||||||
|
'sizeB' => $size = getSize(cache_path('tv-logos')),
|
||||||
|
'sizeMiB' => round($size / 1024 / 1024, 3),
|
||||||
|
'count' => count(glob(cache_path('tv-logos') . '/*')),
|
||||||
|
],
|
||||||
|
'qr-codes' => [
|
||||||
|
'sizeB' => $size = getSize(cache_path('qr-codes')),
|
||||||
|
'sizeMiB' => round($size / 1024 / 1024, 3),
|
||||||
|
'count' => count(glob(cache_path('qr-codes') . '/*')),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'telegram' => [
|
||||||
|
'id' => $tgBotInfo->getId(),
|
||||||
|
'first_name' => $tgBotInfo->getFirstName(),
|
||||||
|
'username' => $tgBotInfo->getUsername(),
|
||||||
|
],
|
||||||
|
'redis' => [
|
||||||
|
'isConnected' => redis()->isConnected(),
|
||||||
|
'info' => [
|
||||||
|
'server' => [
|
||||||
|
'uptime_in_seconds' => $redisInfoServer['uptime_in_seconds'],
|
||||||
|
'uptime_in_days' => $redisInfoServer['uptime_in_days'],
|
||||||
|
],
|
||||||
|
'clients' => ['connected_clients' => $redisInfoClients['connected_clients']],
|
||||||
|
'memory' => $redisInfoMemory,
|
||||||
|
'persistence' => $redisInfoPers,
|
||||||
|
'stats' => $redisInfoStats,
|
||||||
|
'cpu' => $redisInfoCpu,
|
||||||
|
'commandstats' => $redisInfoCmd,
|
||||||
|
'keyspace' => $redisInfoKeysp,
|
||||||
|
'errorstats' => $redisInfoErr,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->responseJson($response, 200, $health);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает информацию о плейлисте
|
||||||
|
*
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
* @param ResponseInterface $response
|
||||||
|
* @return ResponseInterface
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function stats(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
return $this->responseJson($response, 200, new StatisticsService()->get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ class BasicController
|
|||||||
* @param array $data
|
* @param array $data
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
protected function responseJson(ResponseInterface $response, int $status, array $data): ResponseInterface
|
protected function responseJson(ResponseInterface $response, int $status, mixed $data): ResponseInterface
|
||||||
{
|
{
|
||||||
|
is_scalar($data) && $data = [$data];
|
||||||
$data = array_merge(['timestamp' => time()], $data);
|
$data = array_merge(['timestamp' => time()], $data);
|
||||||
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
|
|||||||
120
app/Core/Bot.php
120
app/Core/Bot.php
@@ -33,37 +33,35 @@ class Bot
|
|||||||
/**
|
/**
|
||||||
* @var BotApi Объект Telegram Bot API
|
* @var BotApi Объект Telegram Bot API
|
||||||
*/
|
*/
|
||||||
protected BotApi $bot;
|
public readonly BotApi $api;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Update Объект обновления бота
|
* @var Update Объект обновления бота
|
||||||
*/
|
*/
|
||||||
protected Update $update;
|
protected Update $update;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ServerRequestInterface Пришедший от Telegram запрос
|
||||||
|
*/
|
||||||
|
protected ServerRequestInterface $request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Конструктор
|
* Конструктор
|
||||||
*
|
*
|
||||||
* @param ServerRequestInterface $request
|
* @param ServerRequestInterface|null $request
|
||||||
* @throws InvalidTelegramSecretException
|
* @throws InvalidTelegramSecretException
|
||||||
* @throws JsonException
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
*/
|
||||||
public function __construct(ServerRequestInterface $request)
|
public function __construct(?ServerRequestInterface $request = null)
|
||||||
{
|
{
|
||||||
$this->checkSecret($request);
|
$this->api = new BotApi(config('bot.token'));
|
||||||
|
if ($request) {
|
||||||
$body = json_decode((string)$request->getBody(), true);
|
$this->checkSecret($request);
|
||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
$this->request = $request;
|
||||||
throw new JsonException(json_last_error_msg());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->bot = new BotApi(config('bot.token'));
|
|
||||||
$this->update = Update::fromResponse($body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Запсукает обработку команды
|
* Запускает обработку команды
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
@@ -72,7 +70,9 @@ class Bot
|
|||||||
*/
|
*/
|
||||||
public function process(): bool
|
public function process(): bool
|
||||||
{
|
{
|
||||||
|
$this->parseRequestBody();
|
||||||
$commandText = $this->getBotCommandText();
|
$commandText = $this->getBotCommandText();
|
||||||
|
|
||||||
return match (true) {
|
return match (true) {
|
||||||
str_starts_with($commandText, '/start') => $this->processHelpCommand(),
|
str_starts_with($commandText, '/start') => $this->processHelpCommand(),
|
||||||
str_starts_with($commandText, '/list') => $this->processListCommand(),
|
str_starts_with($commandText, '/list') => $this->processListCommand(),
|
||||||
@@ -84,6 +84,22 @@ class Bot
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подготавливает объект события бота
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws JsonException
|
||||||
|
*/
|
||||||
|
protected function parseRequestBody(): void
|
||||||
|
{
|
||||||
|
$body = json_decode((string)$this->request->getBody(), true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new JsonException(json_last_error_msg());
|
||||||
|
}
|
||||||
|
$this->update = Update::fromResponse($body);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обрабатывает команду /list
|
* Обрабатывает команду /list
|
||||||
*
|
*
|
||||||
@@ -94,7 +110,7 @@ class Bot
|
|||||||
*/
|
*/
|
||||||
protected function processListCommand(): bool
|
protected function processListCommand(): bool
|
||||||
{
|
{
|
||||||
$this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
$this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
||||||
|
|
||||||
$playlists = ini()->getPlaylists();
|
$playlists = ini()->getPlaylists();
|
||||||
if (empty($playlists)) {
|
if (empty($playlists)) {
|
||||||
@@ -139,7 +155,7 @@ class Bot
|
|||||||
*/
|
*/
|
||||||
protected function processInfoCommand(): bool
|
protected function processInfoCommand(): bool
|
||||||
{
|
{
|
||||||
$this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
$this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
||||||
|
|
||||||
$message = $this->update->getMessage();
|
$message = $this->update->getMessage();
|
||||||
$text = $message->getText();
|
$text = $message->getText();
|
||||||
@@ -235,7 +251,7 @@ class Bot
|
|||||||
*/
|
*/
|
||||||
protected function processHelpCommand(): bool
|
protected function processHelpCommand(): bool
|
||||||
{
|
{
|
||||||
$this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
$this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
||||||
|
|
||||||
$replyText[] = 'Бот предоставляет короткую сводку о плейлистах, которые видны на сайте ' .
|
$replyText[] = 'Бот предоставляет короткую сводку о плейлистах, которые видны на сайте ' .
|
||||||
$this->escape(base_url()) . '\.';
|
$this->escape(base_url()) . '\.';
|
||||||
@@ -265,7 +281,7 @@ class Bot
|
|||||||
*/
|
*/
|
||||||
protected function processLinksCommand(): bool
|
protected function processLinksCommand(): bool
|
||||||
{
|
{
|
||||||
$this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
$this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
||||||
|
|
||||||
$replyText[] = '*Ресурсы и страницы*';
|
$replyText[] = '*Ресурсы и страницы*';
|
||||||
$replyText[] = '';
|
$replyText[] = '';
|
||||||
@@ -288,69 +304,7 @@ class Bot
|
|||||||
*/
|
*/
|
||||||
protected function processStatsCommand(): bool
|
protected function processStatsCommand(): bool
|
||||||
{
|
{
|
||||||
$this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
$this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing');
|
||||||
|
|
||||||
$allChannels = [];
|
|
||||||
foreach (ini()->getPlaylists() as $pls) {
|
|
||||||
$allChannels = array_merge($allChannels, $pls['channels'] ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
$onlinePls = array_filter(
|
|
||||||
ini()->getPlaylists(),
|
|
||||||
static fn (array $pls) => $pls['isOnline'] === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
$offlinePls = array_filter(
|
|
||||||
ini()->getPlaylists(),
|
|
||||||
static fn (array $pls) => $pls['isOnline'] === false,
|
|
||||||
);
|
|
||||||
|
|
||||||
$unknownPls = array_filter(
|
|
||||||
ini()->getPlaylists(),
|
|
||||||
static fn (array $pls) => $pls['isOnline'] === null,
|
|
||||||
);
|
|
||||||
|
|
||||||
$adultPls = array_filter(
|
|
||||||
$onlinePls,
|
|
||||||
static fn (array $pls) => in_array('adult', $pls['tags']),
|
|
||||||
);
|
|
||||||
|
|
||||||
$catchupPls = array_filter(
|
|
||||||
$onlinePls,
|
|
||||||
static fn (array $pls) => $pls['hasCatchup'] === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
$tvgPls = array_filter(
|
|
||||||
$onlinePls,
|
|
||||||
static fn (array $pls) => $pls['hasTvg'] === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
$grouppedPls = array_filter(
|
|
||||||
$onlinePls,
|
|
||||||
static fn (array $pls) => count($pls['groups'] ?? []) > 0
|
|
||||||
);
|
|
||||||
|
|
||||||
$onlineCh = $offlineCh = $adultCh = [];
|
|
||||||
foreach ($onlinePls as $pls) {
|
|
||||||
$tmpOnline = array_filter(
|
|
||||||
$pls['channels'] ?? [],
|
|
||||||
static fn (array $ch) => $ch['isOnline'] === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
$tmpOffline = array_filter(
|
|
||||||
$pls['channels'] ?? [],
|
|
||||||
static fn (array $ch) => $ch['isOnline'] === false,
|
|
||||||
);
|
|
||||||
|
|
||||||
$tmpAdult = array_filter(
|
|
||||||
$pls['channels'] ?? [],
|
|
||||||
static fn (array $ch) => in_array('adult', $ch['tags']),
|
|
||||||
);
|
|
||||||
|
|
||||||
$onlineCh = array_merge($onlineCh, $tmpOnline);
|
|
||||||
$offlineCh = array_merge($offlineCh, $tmpOffline);
|
|
||||||
$adultCh = array_merge($adultCh, $tmpAdult);
|
|
||||||
}
|
|
||||||
$stats = new StatisticsService()->get();
|
$stats = new StatisticsService()->get();
|
||||||
|
|
||||||
$replyText[] = '📊 *Статистика*';
|
$replyText[] = '📊 *Статистика*';
|
||||||
@@ -437,7 +391,7 @@ class Bot
|
|||||||
InlineKeyboardMarkup|ReplyKeyboardMarkup|ReplyKeyboardRemove|ForceReply|null $keyboard = null,
|
InlineKeyboardMarkup|ReplyKeyboardMarkup|ReplyKeyboardRemove|ForceReply|null $keyboard = null,
|
||||||
): bool {
|
): bool {
|
||||||
try {
|
try {
|
||||||
$this->bot->sendMessage(
|
$this->api->sendMessage(
|
||||||
chatId: $this->update->getMessage()->getChat()->getId(),
|
chatId: $this->update->getMessage()->getChat()->getId(),
|
||||||
text: $text,
|
text: $text,
|
||||||
parseMode: 'MarkdownV2',
|
parseMode: 'MarkdownV2',
|
||||||
|
|||||||
@@ -67,6 +67,24 @@ return [
|
|||||||
'handler' => [ApiController::class, 'makeQrCode'],
|
'handler' => [ApiController::class, 'makeQrCode'],
|
||||||
'name' => 'api::makeQrCode',
|
'name' => 'api::makeQrCode',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'method' => 'GET',
|
||||||
|
'path' => '/api/version',
|
||||||
|
'handler' => [ApiController::class, 'version'],
|
||||||
|
'name' => 'api::version',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'method' => 'GET',
|
||||||
|
'path' => '/api/health',
|
||||||
|
'handler' => [ApiController::class, 'health'],
|
||||||
|
'name' => 'api::health',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'method' => 'GET',
|
||||||
|
'path' => '/api/stats',
|
||||||
|
'handler' => [ApiController::class, 'stats'],
|
||||||
|
'name' => 'api::stats',
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user