/
itp_practice
/
itp_backend
Обзор
Документация
Войти
/
itp_practice
/
itp_backend
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
app/Services/TapAppService.php
637 строк
22 KB
vivanenko
run taps
29 май 2025, 21:05
29 май 2025, 21:05
b91be9d
Код
Авторство
О чём код?
<?php namespace App\Services; use App\Models\Order; use App\Models\UserTariffSubscription; use App\Models\Tap; use App\Enums\TapStatus; use App\Enums\TapProcessState; use App\Services\ProcessManager\SupervisorService; use Illuminate\Support\Facades\Log; use Illuminate\Support\Collection; /** * Сервис для управления тап-приложениями * Реализует бизнес-логику инициализации и управления тапами * * Следует принципам SOLID: * - Single Responsibility: Управление жизненным циклом тап-приложений * - Dependency Inversion: Зависит от абстракций (SupervisorService) */ class TapAppService { public function __construct( private readonly SupervisorService $supervisorService ) {} /** * Инициализирует и запускает тап-приложения для заказа * Business Logic Layer - основная бизнес-логика автоматизации */ public function initializeTapAppsForOrder(Order $order): array { Log::info('Starting tap apps initialization for order', [ 'order_id' => $order->id, 'user_id' => $order->user_id, ]); // Data Access Layer - получаем тапы для запуска $tapsToStart = $this->getTapsForOrder($order); if ($tapsToStart->isEmpty()) { Log::warning('No taps found for order initialization', [ 'order_id' => $order->id, 'user_id' => $order->user_id, ]); return [ 'initialized_taps' => [], 'started_taps' => [], 'failed_taps' => [], 'synced_taps' => [], 'subscriptions_processed' => 0, ]; } $results = [ 'initialized_taps' => [], 'started_taps' => [], 'failed_taps' => [], 'synced_taps' => [], ]; // Business Logic Layer - обработка каждого тапа foreach ($tapsToStart as $tap) { try { // Инициализация тапа $this->initializeTap($tap); $results['initialized_taps'][] = $tap; // Автоматический запуск тапа if ($this->startTap($tap->id, $tap->crm_user_id)) { $results['started_taps'][] = $tap; Log::info('Tap started successfully', [ 'tap_id' => $tap->id, 'tap_app' => $tap->tapApp->name, 'user_id' => $tap->crm_user_id, ]); } else { $results['failed_taps'][] = $tap; } } catch (\Exception $e) { $results['failed_taps'][] = $tap; Log::error('Failed to initialize tap', [ 'tap_id' => $tap->id, 'order_id' => $order->id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); } } // Финальная синхронизация статусов всех запущенных тапов if (!empty($results['started_taps'])) { Log::info('Starting final status synchronization for started taps', [ 'order_id' => $order->id, 'started_taps_count' => count($results['started_taps']), ]); // Небольшая задержка для стабилизации процессов sleep(3); foreach ($results['started_taps'] as $tap) { try { $syncResult = $this->syncTapStatus($tap->id); if ($syncResult['synced']) { $results['synced_taps'][] = $tap; } } catch (\Exception $e) { Log::warning('Failed to sync tap status after start', [ 'tap_id' => $tap->id, 'error' => $e->getMessage(), ]); } } } Log::info('Tap apps initialization completed', [ 'order_id' => $order->id, 'total_taps' => $tapsToStart->count(), 'initialized_count' => count($results['initialized_taps']), 'started_count' => count($results['started_taps']), 'synced_count' => count($results['synced_taps']), 'failed_count' => count($results['failed_taps']), ]); $results['subscriptions_processed'] = 1; // Для совместимости с Job return $results; } /** * Data Access Layer - получает тапы для заказа согласно бизнес-требованиям * * SQL эквивалент: * SELECT t.* FROM taps t * INNER JOIN user_tariff_subscription_taps utst ON t.id = utst.tap_id * INNER JOIN user_tariff_subscriptions uts ON utst.user_tariff_subscription_id = uts.id * WHERE t.status = 0 AND t.crm_user_id = order.user_id AND uts.order_id = order.id */ private function getTapsForOrder(Order $order): Collection { return Tap::where('status', TapStatus::NEW) ->where('crm_user_id', $order->user_id) ->whereHas('subscriptions', function ($query) use ($order) { $query->where('order_id', $order->id); }) ->with(['tapApp', 'subscriptions']) ->get(); } /** * Business Logic Layer - инициализация тапа */ private function initializeTap(Tap $tap): void { Log::info('Initializing tap', [ 'tap_id' => $tap->id, 'tap_app_name' => $tap->tapApp->name, 'tap_app_work_name' => $tap->tapApp->work_name, 'user_id' => $tap->crm_user_id, ]); // Обновляем состояние тапа на "инициализирован" $tap->updateProcessState(TapProcessState::STARTING, [ 'initialized_at' => now()->toDateTimeString(), 'initialization_source' => 'order_processing', ]); } /** * Business Logic Layer - запуск конкретного тапа * Интеграция с Infrastructure Layer (SupervisorService) */ public function startTap(int $tapId, int $userId): bool { // Data Access Layer - получение тапа $tap = Tap::where('id', $tapId) ->where('crm_user_id', $userId) ->with('tapApp') ->first(); if (!$tap) { throw new \InvalidArgumentException("Tap {$tapId} not found for user {$userId}"); } if ($tap->status === TapStatus::ACTIVE) { Log::info('Tap is already active', [ 'tap_id' => $tapId, 'user_id' => $userId, ]); return true; } Log::info('Starting tap process', [ 'tap_id' => $tapId, 'user_id' => $userId, 'tap_app_name' => $tap->tapApp->name, ]); try { // Infrastructure Layer - запуск процесса через Supervisor $processStarted = $this->supervisorService->startTapProcess($tapId); if ($processStarted) { // Business Logic Layer - обновление состояния в БД $tap->update([ 'status' => TapStatus::ACTIVE, 'process_state' => TapProcessState::RUNNING, 'process_info' => array_merge($tap->process_info ?? [], [ 'started_at' => now()->toDateTimeString(), 'supervisor_managed' => true, ]), ]); Log::info('Tap started successfully', [ 'tap_id' => $tapId, 'user_id' => $userId, ]); return true; } else { // Обновляем состояние на ошибку $tap->markAsError('Failed to start supervisor process'); Log::error('Failed to start tap process', [ 'tap_id' => $tapId, 'user_id' => $userId, ]); return false; } } catch (\Exception $e) { $tap->markAsError($e->getMessage()); Log::error('Exception during tap start', [ 'tap_id' => $tapId, 'user_id' => $userId, 'error' => $e->getMessage(), ]); return false; } } /** * Business Logic Layer - остановка конкретного тапа * Интеграция с Infrastructure Layer (SupervisorService) */ public function stopTap(int $tapId, int $userId): bool { // Data Access Layer - получение тапа $tap = Tap::where('id', $tapId) ->where('crm_user_id', $userId) ->with('tapApp') ->first(); if (!$tap) { throw new \InvalidArgumentException("Tap {$tapId} not found for user {$userId}"); } if ($tap->status !== TapStatus::ACTIVE) { Log::info('Tap is not active', [ 'tap_id' => $tapId, 'user_id' => $userId, 'current_status' => $tap->status->label(), ]); return true; } Log::info('Stopping tap process', [ 'tap_id' => $tapId, 'user_id' => $userId, 'tap_app_name' => $tap->tapApp->name, ]); try { // Infrastructure Layer - остановка процесса через Supervisor $processStopped = $this->supervisorService->stopTapProcess($tapId); if ($processStopped) { // Business Logic Layer - обновление состояния в БД $tap->update([ 'status' => TapStatus::DISABLED, 'process_state' => TapProcessState::STOPPED, 'process_info' => array_merge($tap->process_info ?? [], [ 'stopped_at' => now()->toDateTimeString(), 'stop_reason' => 'manual_stop', ]), ]); Log::info('Tap stopped successfully', [ 'tap_id' => $tapId, 'user_id' => $userId, ]); return true; } else { Log::error('Failed to stop tap process', [ 'tap_id' => $tapId, 'user_id' => $userId, ]); return false; } } catch (\Exception $e) { Log::error('Exception during tap stop', [ 'tap_id' => $tapId, 'user_id' => $userId, 'error' => $e->getMessage(), ]); return false; } } /** * Business Logic Layer - перезапуск тапа */ public function restartTap(int $tapId, int $userId): bool { Log::info('Restarting tap', [ 'tap_id' => $tapId, 'user_id' => $userId, ]); try { // Сначала останавливаем $this->stopTap($tapId, $userId); // Небольшая задержка sleep(2); // Затем запускаем return $this->startTap($tapId, $userId); } catch (\Exception $e) { Log::error('Failed to restart tap', [ 'tap_id' => $tapId, 'user_id' => $userId, 'error' => $e->getMessage(), ]); return false; } } /** * Data Access Layer - получение статуса тапа из Supervisor */ public function getTapProcessStatus(int $tapId): array { try { $supervisorStatus = $this->supervisorService->getTapProcessStatus($tapId); // Data Access Layer - получение данных из БД $tap = Tap::with('tapApp')->find($tapId); return [ 'tap_id' => $tapId, 'database_status' => $tap ? [ 'status' => $tap->status, 'process_state' => $tap->process_state, 'last_check' => $tap->last_process_check, ] : null, 'supervisor_status' => $supervisorStatus, 'is_running' => $supervisorStatus && in_array($supervisorStatus['statename'], ['RUNNING', 'STARTING']), ]; } catch (\Exception $e) { Log::error('Failed to get tap process status', [ 'tap_id' => $tapId, 'error' => $e->getMessage(), ]); return [ 'tap_id' => $tapId, 'error' => $e->getMessage(), 'is_running' => false, ]; } } /** * Business Logic Layer - получение статуса всех тапов пользователя */ public function getUserTapsStatus(int $userId): array { // Data Access Layer - получение тапов пользователя $taps = Tap::where('crm_user_id', $userId) ->with('tapApp') ->get(); $tapsWithStatus = []; foreach ($taps as $tap) { $processStatus = $this->getTapProcessStatus($tap->id); $tapsWithStatus[] = array_merge($tap->toArray(), [ 'process_status' => $processStatus, ]); } return [ 'user_id' => $userId, 'total_taps' => $taps->count(), 'active_taps' => $taps->where('status', TapStatus::ACTIVE)->count(), 'new_taps' => $taps->where('status', TapStatus::NEW)->count(), 'running_processes' => collect($tapsWithStatus) ->where('process_status.is_running', true)->count(), 'taps' => $tapsWithStatus, ]; } /** * Business Logic Layer - массовый перезапуск тапов пользователя */ public function restartUserTaps(int $userId): array { // Data Access Layer - получение активных тапов $activeTaps = Tap::where('crm_user_id', $userId) ->where('status', TapStatus::ACTIVE) ->get(); $results = [ 'restarted' => [], 'failed' => [], ]; foreach ($activeTaps as $tap) { try { if ($this->restartTap($tap->id, $userId)) { $results['restarted'][] = $tap; } else { $results['failed'][] = $tap; } } catch (\Exception $e) { $results['failed'][] = $tap; Log::error('Failed to restart user tap', [ 'tap_id' => $tap->id, 'user_id' => $userId, 'error' => $e->getMessage(), ]); } } Log::info('User taps restart completed', [ 'user_id' => $userId, 'total_taps' => $activeTaps->count(), 'restarted_count' => count($results['restarted']), 'failed_count' => count($results['failed']), ]); return $results; } /** * Infrastructure Layer - получение логов тапа */ public function getTapLogs(int $tapId, int $offset = 0, int $length = 1000): ?string { try { return $this->supervisorService->readTapProcessLog($tapId, $offset, $length); } catch (\Exception $e) { Log::error('Failed to get tap logs', [ 'tap_id' => $tapId, 'error' => $e->getMessage(), ]); return null; } } /** * Business Logic Layer - синхронизация состояния тапов с Supervisor */ public function syncTapsWithSupervisor(int $userId): array { $userTaps = Tap::where('crm_user_id', $userId)->get(); $syncResults = [ 'synced' => [], 'mismatched' => [], 'errors' => [], ]; foreach ($userTaps as $tap) { try { $supervisorStatus = $this->supervisorService->getTapProcessStatus($tap->id); if ($supervisorStatus) { $isSupvisorRunning = in_array($supervisorStatus['statename'], ['RUNNING', 'STARTING']); $isDatabaseActive = $tap->status === TapStatus::ACTIVE; if ($isSupvisorRunning !== $isDatabaseActive) { $syncResults['mismatched'][] = [ 'tap_id' => $tap->id, 'database_status' => $tap->status->label(), 'supervisor_status' => $supervisorStatus['statename'], ]; } else { $syncResults['synced'][] = $tap->id; } } } catch (\Exception $e) { $syncResults['errors'][] = [ 'tap_id' => $tap->id, 'error' => $e->getMessage(), ]; } } return $syncResults; } /** * Business Logic Layer - синхронизация статуса конкретного тапа с Supervisor */ public function syncTapStatus(int $tapId): array { try { // Data Access Layer - получение тапа из БД $tap = Tap::with('tapApp')->find($tapId); if (!$tap) { return [ 'synced' => false, 'error' => 'Tap not found in database', ]; } // Infrastructure Layer - получение статуса из Supervisor $supervisorStatus = $this->supervisorService->getTapProcessStatus($tapId); if (!$supervisorStatus) { Log::warning('No supervisor status found for tap', [ 'tap_id' => $tapId, ]); return [ 'synced' => false, 'error' => 'No supervisor status available', ]; } $supervisorStateName = $supervisorStatus['statename'] ?? 'UNKNOWN'; $isProcessRunning = in_array($supervisorStateName, ['RUNNING', 'STARTING']); // Business Logic Layer - определение нового статуса $newStatus = $this->mapSupervisorStatusToTapStatus($supervisorStateName); $newProcessState = $this->mapSupervisorStatusToProcessState($supervisorStateName); $statusChanged = false; // Обновление статуса если необходимо if ($tap->status !== $newStatus || $tap->process_state !== $newProcessState) { $oldStatus = $tap->status; $oldProcessState = $tap->process_state; $tap->update([ 'status' => $newStatus, 'process_state' => $newProcessState, 'process_info' => array_merge($tap->process_info ?? [], [ 'last_sync_at' => now()->toDateTimeString(), 'supervisor_state' => $supervisorStateName, 'sync_source' => 'status_synchronization', ]), 'last_process_check' => now(), ]); $statusChanged = true; Log::info('Tap status synchronized', [ 'tap_id' => $tapId, 'old_status' => $oldStatus->label(), 'new_status' => $newStatus->label(), 'old_process_state' => $oldProcessState->label(), 'new_process_state' => $newProcessState->label(), 'supervisor_state' => $supervisorStateName, ]); } else { // Обновляем только время последней проверки $tap->recordProcessCheck(); } return [ 'synced' => true, 'status_changed' => $statusChanged, 'database_status' => $newStatus->label(), 'process_state' => $newProcessState->label(), 'supervisor_status' => $supervisorStateName, 'is_running' => $isProcessRunning, ]; } catch (\Exception $e) { Log::error('Failed to sync tap status', [ 'tap_id' => $tapId, 'error' => $e->getMessage(), ]); return [ 'synced' => false, 'error' => $e->getMessage(), ]; } } /** * Business Logic Layer - маппинг статуса Supervisor в TapStatus */ private function mapSupervisorStatusToTapStatus(string $supervisorState): TapStatus { return match($supervisorState) { 'RUNNING' => TapStatus::ACTIVE, 'STARTING' => TapStatus::ACTIVE, 'STOPPED', 'STOPPING' => TapStatus::DISABLED, 'FATAL', 'FAILED' => TapStatus::ERROR, 'BACKOFF' => TapStatus::ERROR, 'EXITED' => TapStatus::DISABLED, default => TapStatus::NEW, }; } /** * Business Logic Layer - маппинг статуса Supervisor в TapProcessState */ private function mapSupervisorStatusToProcessState(string $supervisorState): TapProcessState { return match($supervisorState) { 'RUNNING' => TapProcessState::RUNNING, 'STARTING' => TapProcessState::STARTING, 'STOPPED', 'STOPPING' => TapProcessState::STOPPED, 'FATAL', 'FAILED' => TapProcessState::FATAL, 'BACKOFF' => TapProcessState::BACKOFF, 'EXITED' => TapProcessState::EXITED, default => TapProcessState::UNKNOWN, }; } }