From: Aleksey Filippov Date: Wed, 24 Dec 2025 09:37:54 +0000 (+0300) Subject: Доработка документации добавление схемы БД X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=0902ea64d940b63751d686104d93f998fb8b4f8a;p=erp24_rep%2Fyii-erp24%2F.git Доработка документации добавление схемы БД --- diff --git a/erp24/controllers/DiagnosticController.php b/erp24/controllers/DiagnosticController.php new file mode 100644 index 00000000..0e5e6f59 --- /dev/null +++ b/erp24/controllers/DiagnosticController.php @@ -0,0 +1,683 @@ + [ + 'class' => AccessControl::class, + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['@'], + 'matchCallback' => function () { + // Доступ для: суперадмин (1), администраторы (50), IT (81) + $admin = Yii::$app->user->identity; + if (!$admin) { + return false; + } + // Проверяем group_id + $groupId = $admin->group_id ?? null; + // AdminGroup::GROUP_ADMINISTRATORS = 50, AdminGroup::GROUP_IT = 81 + $allowedGroups = [1, 50, 81]; + return in_array((int)$groupId, $allowedGroups, true); + } + ], + ], + 'denyCallback' => function () { + if (Yii::$app->user->isGuest) { + return Yii::$app->response->redirect(['/site/login']); + } + throw new \yii\web\ForbiddenHttpException('Доступ запрещён. Требуются права администратора.'); + }, + ], + ]; + } + + /** + * Главная страница диагностики + */ + public function actionIndex(): string + { + $envVars = $this->getEnvVariablesStatus(); + $connections = $this->getConnectionsStatus(); + + return $this->render('index', [ + 'envVars' => $envVars, + 'connections' => $connections, + ]); + } + + /** + * Тестирование PostgreSQL подключения + */ + public function actionTestPostgres(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + try { + $db = Yii::$app->db; + $startTime = microtime(true); + $db->open(); + $result = $db->createCommand('SELECT version() as version, NOW() as server_time')->queryOne(); + $duration = round((microtime(true) - $startTime) * 1000, 2); + + return $this->asJson([ + 'success' => true, + 'message' => 'PostgreSQL подключение успешно', + 'data' => [ + 'version' => $result['version'] ?? 'N/A', + 'server_time' => $result['server_time'] ?? 'N/A', + 'dsn' => $this->maskDsn($db->dsn), + 'duration_ms' => $duration, + ], + ]); + } catch (\Exception $e) { + return $this->asJson([ + 'success' => false, + 'message' => 'Ошибка подключения к PostgreSQL', + 'error' => $e->getMessage(), + ]); + } + } + + /** + * Тестирование внешней БД (dbRemote) + */ + public function actionTestDbRemote(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + // Проверяем наличие компонента + if (!Yii::$app->has('dbRemote')) { + return $this->asJson([ + 'success' => false, + 'message' => 'Компонент dbRemote не сконфигурирован', + 'hint' => 'Установите DB_REMOTE_HOST в .env файле', + ]); + } + + try { + $db = Yii::$app->dbRemote; + $startTime = microtime(true); + $db->open(); + $result = $db->createCommand('SELECT VERSION() as version, NOW() as server_time')->queryOne(); + $duration = round((microtime(true) - $startTime) * 1000, 2); + + return $this->asJson([ + 'success' => true, + 'message' => 'Подключение к внешней БД успешно', + 'data' => [ + 'version' => $result['version'] ?? 'N/A', + 'server_time' => $result['server_time'] ?? 'N/A', + 'dsn' => $this->maskDsn($db->dsn), + 'duration_ms' => $duration, + ], + ]); + } catch (\Exception $e) { + return $this->asJson([ + 'success' => false, + 'message' => 'Ошибка подключения к внешней БД', + 'error' => $e->getMessage(), + ]); + } + } + + /** + * Тестирование RabbitMQ подключения + */ + public function actionTestRabbitmq(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + $host = getenv('RABBIT_HOST') ?: 'localhost'; + $user = getenv('RABBIT_USER') ?: ''; + $password = getenv('RABBIT_PASSWORD') ?: ''; + $port = 5672; + + if (empty($user) || empty($password)) { + return $this->asJson([ + 'success' => false, + 'message' => 'RabbitMQ credentials не настроены', + 'hint' => 'Установите RABBIT_USER и RABBIT_PASSWORD в .env файле', + ]); + } + + try { + $startTime = microtime(true); + $connection = new AMQPStreamConnection($host, $port, $user, $password); + $channel = $connection->channel(); + + // Проверяем очередь telegram-queue + $queueInfo = $channel->queue_declare('telegram-queue', true, true, false, false); + $duration = round((microtime(true) - $startTime) * 1000, 2); + + $channel->close(); + $connection->close(); + + return $this->asJson([ + 'success' => true, + 'message' => 'RabbitMQ подключение успешно', + 'data' => [ + 'host' => $host, + 'port' => $port, + 'user' => $user, + 'queue_name' => 'telegram-queue', + 'messages_count' => $queueInfo[1] ?? 0, + 'consumers_count' => $queueInfo[2] ?? 0, + 'duration_ms' => $duration, + ], + ]); + } catch (\Exception $e) { + return $this->asJson([ + 'success' => false, + 'message' => 'Ошибка подключения к RabbitMQ', + 'error' => $e->getMessage(), + 'config' => [ + 'host' => $host, + 'port' => $port, + 'user' => $user, + ], + ]); + } + } + + /** + * Тестирование Telegram бота (основной) + */ + public function actionTestTelegram(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + $token = getenv('TELEGRAM_BOT_TOKEN') ?: ''; + + if (empty($token)) { + return $this->asJson([ + 'success' => false, + 'message' => 'TELEGRAM_BOT_TOKEN не настроен', + 'hint' => 'Установите TELEGRAM_BOT_TOKEN в .env файле', + ]); + } + + // Проверяем, что токен не placeholder + if (strpos($token, '000000000') === 0) { + return $this->asJson([ + 'success' => false, + 'message' => 'TELEGRAM_BOT_TOKEN содержит placeholder значение', + 'hint' => 'Замените placeholder на реальный токен бота', + ]); + } + + try { + $client = new Client(['timeout' => 10]); + $url = "https://api.telegram.org/bot{$token}/getMe"; + + $startTime = microtime(true); + $response = $client->get($url); + $duration = round((microtime(true) - $startTime) * 1000, 2); + + $data = json_decode($response->getBody()->getContents(), true); + + if ($data['ok'] ?? false) { + return $this->asJson([ + 'success' => true, + 'message' => 'Telegram бот подключен успешно', + 'data' => [ + 'bot_id' => $data['result']['id'] ?? 'N/A', + 'bot_name' => $data['result']['first_name'] ?? 'N/A', + 'bot_username' => '@' . ($data['result']['username'] ?? 'N/A'), + 'can_read_messages' => $data['result']['can_read_all_group_messages'] ?? false, + 'duration_ms' => $duration, + ], + ]); + } + + return $this->asJson([ + 'success' => false, + 'message' => 'Telegram API вернул ошибку', + 'error' => $data['description'] ?? 'Unknown error', + ]); + } catch (GuzzleException $e) { + return $this->asJson([ + 'success' => false, + 'message' => 'Ошибка подключения к Telegram API', + 'error' => $e->getMessage(), + ]); + } + } + + /** + * Тестирование Telegram Salebot бота + */ + public function actionTestTelegramSalebot(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + $token = getenv('TELEGRAM_BOT_TOKEN_SALEBOT') ?: ''; + + if (empty($token)) { + return $this->asJson([ + 'success' => false, + 'message' => 'TELEGRAM_BOT_TOKEN_SALEBOT не настроен', + 'hint' => 'Установите TELEGRAM_BOT_TOKEN_SALEBOT в .env файле', + ]); + } + + if (strpos($token, '000000000') === 0) { + return $this->asJson([ + 'success' => false, + 'message' => 'TELEGRAM_BOT_TOKEN_SALEBOT содержит placeholder значение', + 'hint' => 'Замените placeholder на реальный токен бота', + ]); + } + + try { + $client = new Client(['timeout' => 10]); + $url = "https://api.telegram.org/bot{$token}/getMe"; + + $startTime = microtime(true); + $response = $client->get($url); + $duration = round((microtime(true) - $startTime) * 1000, 2); + + $data = json_decode($response->getBody()->getContents(), true); + + if ($data['ok'] ?? false) { + return $this->asJson([ + 'success' => true, + 'message' => 'Telegram Salebot бот подключен успешно', + 'data' => [ + 'bot_id' => $data['result']['id'] ?? 'N/A', + 'bot_name' => $data['result']['first_name'] ?? 'N/A', + 'bot_username' => '@' . ($data['result']['username'] ?? 'N/A'), + 'duration_ms' => $duration, + ], + ]); + } + + return $this->asJson([ + 'success' => false, + 'message' => 'Telegram Salebot API вернул ошибку', + 'error' => $data['description'] ?? 'Unknown error', + ]); + } catch (GuzzleException $e) { + return $this->asJson([ + 'success' => false, + 'message' => 'Ошибка подключения к Telegram Salebot API', + 'error' => $e->getMessage(), + ]); + } + } + + /** + * Отправка тестового сообщения в Telegram + */ + public function actionSendTestMessage(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + $chatId = Yii::$app->request->post('chat_id'); + $botType = Yii::$app->request->post('bot_type', 'main'); // main или salebot + + if (empty($chatId)) { + return $this->asJson([ + 'success' => false, + 'message' => 'Не указан chat_id', + ]); + } + + $token = $botType === 'salebot' + ? getenv('TELEGRAM_BOT_TOKEN_SALEBOT') + : getenv('TELEGRAM_BOT_TOKEN'); + + if (empty($token) || strpos($token, '000000000') === 0) { + return $this->asJson([ + 'success' => false, + 'message' => 'Токен бота не настроен или является placeholder', + ]); + } + + try { + $client = new Client(['timeout' => 10]); + $url = "https://api.telegram.org/bot{$token}/sendMessage"; + + $message = "🔧 *Тестовое сообщение ERP24*\n\n" + . "Время: " . date('Y-m-d H:i:s') . "\n" + . "Сервер: " . gethostname() . "\n" + . "Бот: " . ($botType === 'salebot' ? 'Salebot' : 'Main') . "\n" + . "Пользователь: " . (Yii::$app->user->identity->name ?? 'N/A'); + + $response = $client->post($url, [ + 'json' => [ + 'chat_id' => $chatId, + 'text' => $message, + 'parse_mode' => 'Markdown', + ], + ]); + + $data = json_decode($response->getBody()->getContents(), true); + + if ($data['ok'] ?? false) { + return $this->asJson([ + 'success' => true, + 'message' => 'Тестовое сообщение отправлено успешно', + 'data' => [ + 'message_id' => $data['result']['message_id'] ?? 'N/A', + 'chat_id' => $chatId, + ], + ]); + } + + return $this->asJson([ + 'success' => false, + 'message' => 'Ошибка отправки сообщения', + 'error' => $data['description'] ?? 'Unknown error', + ]); + } catch (GuzzleException $e) { + return $this->asJson([ + 'success' => false, + 'message' => 'Ошибка отправки сообщения', + 'error' => $e->getMessage(), + ]); + } + } + + /** + * Тестирование Queue компонента (AMQP) + */ + public function actionTestQueue(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + if (!Yii::$app->has('queue')) { + return $this->asJson([ + 'success' => false, + 'message' => 'Компонент queue не сконфигурирован', + ]); + } + + try { + $queue = Yii::$app->queue; + $dsn = $queue->dsn ?? 'N/A'; + + return $this->asJson([ + 'success' => true, + 'message' => 'Queue компонент сконфигурирован', + 'data' => [ + 'dsn' => $this->maskDsn($dsn), + 'queue_name' => $queue->queueName ?? 'N/A', + 'ttr' => $queue->ttr ?? 'N/A', + 'attempts' => $queue->attempts ?? 'N/A', + ], + ]); + } catch (\Exception $e) { + return $this->asJson([ + 'success' => false, + 'message' => 'Ошибка получения конфигурации Queue', + 'error' => $e->getMessage(), + ]); + } + } + + /** + * Полная проверка всех переменных окружения + */ + public function actionCheckEnv(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + return $this->asJson([ + 'success' => true, + 'data' => $this->getEnvVariablesStatus(), + ]); + } + + /** + * Запуск всех тестов + */ + public function actionRunAllTests(): Response + { + Yii::$app->response->format = Response::FORMAT_JSON; + + $results = []; + + // PostgreSQL + $pgResult = $this->actionTestPostgres(); + $results['postgresql'] = json_decode($pgResult->content, true); + + // dbRemote + $remoteResult = $this->actionTestDbRemote(); + $results['db_remote'] = json_decode($remoteResult->content, true); + + // RabbitMQ + $rabbitResult = $this->actionTestRabbitmq(); + $results['rabbitmq'] = json_decode($rabbitResult->content, true); + + // Telegram Main + $telegramResult = $this->actionTestTelegram(); + $results['telegram_main'] = json_decode($telegramResult->content, true); + + // Telegram Salebot + $salebotResult = $this->actionTestTelegramSalebot(); + $results['telegram_salebot'] = json_decode($salebotResult->content, true); + + // Queue + $queueResult = $this->actionTestQueue(); + $results['queue'] = json_decode($queueResult->content, true); + + // Env vars + $results['env_variables'] = $this->getEnvVariablesStatus(); + + // Summary + $successCount = 0; + $failCount = 0; + foreach (['postgresql', 'db_remote', 'rabbitmq', 'telegram_main', 'telegram_salebot', 'queue'] as $key) { + if ($results[$key]['success'] ?? false) { + $successCount++; + } else { + $failCount++; + } + } + + return $this->asJson([ + 'success' => $failCount === 0, + 'summary' => [ + 'total' => $successCount + $failCount, + 'passed' => $successCount, + 'failed' => $failCount, + ], + 'results' => $results, + ]); + } + + /** + * Получение статуса переменных окружения + */ + private function getEnvVariablesStatus(): array + { + $required = [ + 'APP_ENV' => 'Окружение приложения', + 'POSTGRES_PASSWORD' => 'Пароль PostgreSQL', + 'RABBIT_USER' => 'Пользователь RabbitMQ', + 'RABBIT_PASSWORD' => 'Пароль RabbitMQ', + 'TELEGRAM_BOT_TOKEN' => 'Токен Telegram бота', + 'COOKIE_VALIDATION_KEY' => 'Ключ валидации cookie', + ]; + + $optional = [ + 'POSTGRES_HOSTNAME' => 'Хост PostgreSQL', + 'POSTGRES_PORT' => 'Порт PostgreSQL', + 'POSTGRES_SCHEMA' => 'Схема PostgreSQL', + 'POSTGRES_USER' => 'Пользователь PostgreSQL', + 'RABBIT_HOST' => 'Хост RabbitMQ', + 'TELEGRAM_BOT_TOKEN_SALEBOT' => 'Токен Salebot', + 'DB_REMOTE_HOST' => 'Хост внешней БД', + 'DB_REMOTE_PORT' => 'Порт внешней БД', + 'DB_REMOTE_SCHEMA' => 'Схема внешней БД', + 'DB_REMOTE_USER' => 'Пользователь внешней БД', + 'DB_REMOTE_PASSWORD' => 'Пароль внешней БД', + 'WHATSAPP_API_KEY' => 'API ключ WhatsApp', + 'YANDEX_MARKET_API_KEY' => 'API ключ Яндекс.Маркет', + 'SWITCH_USER_COOKIE_PASSWORD' => 'Пароль переключения пользователя', + 'COOKIE_VALIDATION_KEY_API2' => 'Ключ валидации cookie API2', + ]; + + $cameras = []; + for ($i = 1; $i <= 5; $i++) { + $cameras["CAMERA_{$i}_LOGIN"] = "Логин камеры $i"; + $cameras["CAMERA_{$i}_PASSWORD"] = "Пароль камеры $i"; + } + + $result = [ + 'required' => [], + 'optional' => [], + 'cameras' => [], + ]; + + foreach ($required as $var => $description) { + $value = getenv($var); + $result['required'][$var] = [ + 'description' => $description, + 'set' => $value !== false && $value !== '', + 'value' => $this->maskValue($var, $value), + 'is_placeholder' => $this->isPlaceholder($value), + ]; + } + + foreach ($optional as $var => $description) { + $value = getenv($var); + $result['optional'][$var] = [ + 'description' => $description, + 'set' => $value !== false && $value !== '', + 'value' => $this->maskValue($var, $value), + ]; + } + + foreach ($cameras as $var => $description) { + $value = getenv($var); + $result['cameras'][$var] = [ + 'description' => $description, + 'set' => $value !== false && $value !== '', + ]; + } + + return $result; + } + + /** + * Получение статуса подключений + */ + private function getConnectionsStatus(): array + { + return [ + 'postgresql' => [ + 'name' => 'PostgreSQL (основная БД)', + 'configured' => Yii::$app->has('db'), + ], + 'db_remote' => [ + 'name' => 'MySQL (внешняя БД)', + 'configured' => Yii::$app->has('dbRemote'), + ], + 'rabbitmq' => [ + 'name' => 'RabbitMQ', + 'configured' => !empty(getenv('RABBIT_USER')) && !empty(getenv('RABBIT_PASSWORD')), + ], + 'telegram' => [ + 'name' => 'Telegram Bot (основной)', + 'configured' => !empty(getenv('TELEGRAM_BOT_TOKEN')) && strpos(getenv('TELEGRAM_BOT_TOKEN'), '000000000') !== 0, + ], + 'telegram_salebot' => [ + 'name' => 'Telegram Bot (Salebot)', + 'configured' => !empty(getenv('TELEGRAM_BOT_TOKEN_SALEBOT')) && strpos(getenv('TELEGRAM_BOT_TOKEN_SALEBOT'), '000000000') !== 0, + ], + 'queue' => [ + 'name' => 'Queue (AMQP)', + 'configured' => Yii::$app->has('queue'), + ], + ]; + } + + /** + * Маскирование DSN для безопасного отображения + */ + private function maskDsn(string $dsn): string + { + // Маскируем пароль в DSN + return preg_replace('/:([^:@]+)@/', ':***@', $dsn); + } + + /** + * Маскирование значений для безопасного отображения + */ + private function maskValue(string $var, $value): string + { + if ($value === false || $value === '') { + return '(не установлено)'; + } + + $sensitiveVars = ['PASSWORD', 'TOKEN', 'KEY', 'SECRET']; + foreach ($sensitiveVars as $sensitive) { + if (stripos($var, $sensitive) !== false) { + $length = strlen($value); + if ($length <= 4) { + return '****'; + } + return substr($value, 0, 4) . str_repeat('*', min($length - 4, 20)); + } + } + + return $value; + } + + /** + * Проверка на placeholder значение + */ + private function isPlaceholder($value): bool + { + if ($value === false || $value === '') { + return false; + } + + $placeholders = [ + '000000000', + 'dev_password', + 'dev_cookie_key', + 'dev_rabbit', + 'change_me', + ]; + + foreach ($placeholders as $placeholder) { + if (stripos($value, $placeholder) !== false) { + return true; + } + } + + return false; + } +} diff --git a/erp24/views/diagnostic/index.php b/erp24/views/diagnostic/index.php new file mode 100644 index 00000000..55359f2b --- /dev/null +++ b/erp24/views/diagnostic/index.php @@ -0,0 +1,824 @@ +title = 'Диагностика системы'; +$this->params['breadcrumbs'][] = $this->title; +?> + + + +
+
+

🔧 Диагностика системы ERP24

+

Тестирование подключений и проверка конфигурации .env

+
+ + + + + +
+ +
+ + +
+
+

🔌 Тестирование подключений

+
+
+ +
+
🐘
+
+
PostgreSQL (основная БД)
+
+ + Сконфигурирован + + Не настроен + +
+
+
+ +
+
+
+ + +
+
🗄️
+
+
MySQL (внешняя БД - dbRemote)
+
+ + Сконфигурирован + + Не настроен (опционально) + +
+
+
+ +
+
+
+ + +
+
🐰
+
+
RabbitMQ
+
+ + Сконфигурирован + + Не настроен + +
+
+
+ +
+
+
+ + +
+
📬
+
+
Queue (AMQP компонент)
+
+ + Сконфигурирован + + Не настроен + +
+
+
+ +
+
+
+
+
+ + +
+
+

📱 Telegram боты

+
+
+ +
+
📱
+
+
Telegram Bot (основной)
+
+ + Сконфигурирован + + Не настроен + +
+
+
+ +
+
+
+ + +
+
🤖
+
+
Telegram Bot (Salebot)
+
+ + Сконфигурирован + + Не настроен (опционально) + +
+
+
+ +
+
+
+ + +
+

📤 Отправить тестовое сообщение

+ + + + + + + +
+
+
+
+ + +
+
+

🔐 Обязательные переменные окружения

+
+
+
+ + + + + + + + + + + $info): ?> + + + + + + + + +
ПеременнаяОписаниеЗначениеСтатус
+ + Не установлена + + Placeholder + + OK + +
+
+
+
+ + +
+
+ +
+
+
+ + + + + + + + + + + $info): ?> + + + + + + + + +
ПеременнаяОписаниеЗначениеСтатус
+ + Установлена + + Не установлена + +
+
+
+
+ + +
+
+ +
+
+
+ + + + + + + + + + $info): ?> + + + + + + + +
ПеременнаяОписаниеСтатус
+ + Установлена + + Не установлена + +
+
+
+
+
+ +