Squashed commit message
Some checks failed
Telegram Mini App Shop Builder / Compute version metadata (push) Has been cancelled
Telegram Mini App Shop Builder / Run Frontend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run Backend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run PHP_CodeSniffer (push) Has been cancelled
Telegram Mini App Shop Builder / Build module. (push) Has been cancelled
Telegram Mini App Shop Builder / release (push) Has been cancelled
Some checks failed
Telegram Mini App Shop Builder / Compute version metadata (push) Has been cancelled
Telegram Mini App Shop Builder / Run Frontend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run Backend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run PHP_CodeSniffer (push) Has been cancelled
Telegram Mini App Shop Builder / Build module. (push) Has been cancelled
Telegram Mini App Shop Builder / release (push) Has been cancelled
This commit is contained in:
175
backend/src/bastion/ScheduledTasks/AcmeShopPulseSendEventsTask.php
Executable file
175
backend/src/bastion/ScheduledTasks/AcmeShopPulseSendEventsTask.php
Executable file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace Bastion\ScheduledTasks;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Acme\ECommerceFramework\Cache\CacheInterface;
|
||||
use Acme\ECommerceFramework\Config\Settings;
|
||||
use Acme\ECommerceFramework\Scheduler\TaskInterface;
|
||||
use Acme\ECommerceFramework\AcmeShopPulse\AcmeShopEvent;
|
||||
use Acme\ECommerceFramework\AcmeShopPulse\AcmeShopPulseEventsSender;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
|
||||
class AcmeShopPulseSendEventsTask implements TaskInterface
|
||||
{
|
||||
private AcmeShopEvent $eventModel;
|
||||
private AcmeShopPulseEventsSender $eventsSender;
|
||||
private LoggerInterface $logger;
|
||||
private CacheInterface $cache;
|
||||
private Settings $settings;
|
||||
private int $maxAttempts;
|
||||
private int $batchSize;
|
||||
|
||||
public function __construct(
|
||||
Settings $settings,
|
||||
AcmeShopEvent $eventModel,
|
||||
AcmeShopPulseEventsSender $eventsSender,
|
||||
LoggerInterface $logger,
|
||||
CacheInterface $cache
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->eventModel = $eventModel;
|
||||
$this->eventsSender = $eventsSender;
|
||||
$this->logger = $logger;
|
||||
$this->cache = $cache;
|
||||
|
||||
// Получаем конфигурацию из настроек пользователя
|
||||
$this->maxAttempts = (int) $this->settings->get('pulse.max_attempts', env('PULSE_MAX_ATTEMPTS', 3));
|
||||
$this->batchSize = (int) $this->settings->get('pulse.batch_size', env('PULSE_BATCH_SIZE', 50));
|
||||
}
|
||||
|
||||
public function execute(): void
|
||||
{
|
||||
try {
|
||||
// Получаем события со статусом pending
|
||||
$events = $this->eventModel->findPending($this->batchSize);
|
||||
|
||||
if (empty($events)) {
|
||||
$this->logger->debug('No pending events to send');
|
||||
return;
|
||||
}
|
||||
|
||||
$count = count($events);
|
||||
$this->logger->info("Processing pending events: $count", [
|
||||
'count' => $count,
|
||||
]);
|
||||
|
||||
$processed = 0;
|
||||
$succeeded = 0;
|
||||
$failed = 0;
|
||||
|
||||
foreach ($events as $event) {
|
||||
try {
|
||||
$result = $this->processEvent($event);
|
||||
$result ? $succeeded++ : $failed++;
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error("Failed to process event {$event['id']}: " . $e->getMessage(), [
|
||||
'event_id' => $event['id'],
|
||||
'event' => $event['event'] ?? null,
|
||||
'payload' => $event['payload'] ?? null,
|
||||
'exception' => $e,
|
||||
]);
|
||||
$failed++;
|
||||
} finally {
|
||||
$processed++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info("Events processing completed", [
|
||||
'processed' => $processed,
|
||||
'succeeded' => $succeeded,
|
||||
'failed' => $failed,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error("AcmeShopPulseSendEventsTask failed: " . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
} finally {
|
||||
// Сбрасываем кеш статистики после каждого прогона
|
||||
$this->clearStatsCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработать одно событие
|
||||
*
|
||||
* @param array $event Данные события из БД
|
||||
* @return bool true если событие успешно отправлено, false если требуется повторная попытка
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function processEvent(array $event): bool
|
||||
{
|
||||
$eventId = (int) $event['id'];
|
||||
$attemptsCount = (int) $event['attempts_count'];
|
||||
|
||||
try {
|
||||
// Пытаемся отправить событие
|
||||
$success = $this->eventsSender->sendEvent($event);
|
||||
|
||||
if ($success) {
|
||||
// Успешная отправка
|
||||
$this->eventModel->updateStatus($eventId, 'sent');
|
||||
$this->logger->debug("Event {$eventId} sent successfully", [
|
||||
'event_id' => $eventId,
|
||||
'event' => $event['event'],
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// AcmeShop Pulse не вернул подтверждение
|
||||
$errorReason = 'No confirmation received from AcmeShop Pulse';
|
||||
$this->handleFailedAttempt($eventId, $attemptsCount, $errorReason);
|
||||
} catch (GuzzleException $e) {
|
||||
// Ошибка HTTP запроса
|
||||
$errorReason = 'HTTP error: ' . $e->getMessage();
|
||||
$this->handleFailedAttempt($eventId, $attemptsCount, $errorReason);
|
||||
} catch (Throwable $e) {
|
||||
// Другие ошибки (валидация, подпись и т.д.)
|
||||
$errorReason = 'Error: ' . $e->getMessage();
|
||||
$this->handleFailedAttempt($eventId, $attemptsCount, $errorReason);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработать неудачную попытку отправки
|
||||
*
|
||||
* @param int $eventId ID события
|
||||
* @param int $currentAttempts Текущее количество попыток
|
||||
* @param string $errorReason Причина ошибки
|
||||
*/
|
||||
private function handleFailedAttempt(int $eventId, int $currentAttempts, string $errorReason): void
|
||||
{
|
||||
$newAttempts = $currentAttempts + 1;
|
||||
|
||||
if ($newAttempts >= $this->maxAttempts) {
|
||||
// Превышен лимит попыток - переводим в failed
|
||||
$this->eventModel->updateStatus($eventId, 'failed', $errorReason);
|
||||
$this->logger->warning("Event {$eventId} marked as failed after {$newAttempts} attempts", [
|
||||
'event_id' => $eventId,
|
||||
'attempts' => $newAttempts,
|
||||
'error' => $errorReason,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Увеличиваем счетчик попыток, оставляем статус pending
|
||||
$this->eventModel->incrementAttempts($eventId);
|
||||
$this->logger->debug("Event {$eventId} attempt failed, will retry", [
|
||||
'event_id' => $eventId,
|
||||
'attempts' => $newAttempts,
|
||||
'max_attempts' => $this->maxAttempts,
|
||||
'error' => $errorReason,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сбросить кеш статистики
|
||||
*/
|
||||
private function clearStatsCache(): void
|
||||
{
|
||||
$this->cache->delete('acmeshop_pulse_stats');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user