// ============================================ // 📦 Модуль подключения к MySQL базе данных // ============================================ const mysql = require('mysql2/promise'); // ✅ Используем promise-версию const path = require('path'); const fs = require('fs').promises; const { logger } = require('./logger'); async function readJson(filePath) { const data = await fs.readFile(filePath, 'utf8'); return JSON.parse(data); } // Загружаем переменные окружения require('dotenv').config({ path: path.join(__dirname, '../../config.env') }); // ============================================ // 🔧 Конфигурация подключения к MySQL // ============================================ const dbConfig = { host: process.env.DB_HOST || 'localhost', user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'test', port: process.env.DB_PORT || 3306, waitForConnections: true, connectionLimit: 10, queueLimit: 0, // Дополнительные настройки charset: 'utf8mb4', timezone: '+00:00' }; // ============================================ // 📊 Глобальные переменные // ============================================ let pool = null; // Пул соединений let isDBConnected = false; let lastError = null; let connectionAttempts = 0; let isReconnecting = false; // ✅ Функция для проверки testMode из config.json async function checkTestMode() { try { const configPath = path.join(__dirname, '../../config.json'); const config = await readJson(configPath); return config.testMode === true; } catch (err) { logger.warn('⚠️ Не удалось прочитать config.json для проверки testMode', { error: err.message }); return false; } } // ============================================ // 🔄 Функция подключения с повторными попытками // ============================================ async function connectDB(retries = 5, delay = 3000) { // ✅ Проверяем testMode const testMode = await checkTestMode(); // 🔧 Если включен тестовый режим - пропускаем подключение if (testMode) { logger.info('🔧 ТЕСТОВЫЙ РЕЖИМ: Подключение к БД отключено'); logger.info('📊 Используются mock-данные из config.json'); isDBConnected = false; lastError = 'Test mode active - database connection disabled'; return false; } // Если уже подключены if (isDBConnected && pool) { logger.verbose('✅ БД уже подключена'); return true; } // Если идет переподключение if (isReconnecting) { logger.warn('⏳ Идет переподключение к БД, ожидаем...'); return false; } logger.info(`🔄 Подключение к MySQL... (попытка 1/${retries})`); logger.info(`📊 Хост: ${dbConfig.host}, БД: ${dbConfig.database}, Порт: ${dbConfig.port}`); // Цикл повторных попыток for (let i = 0; i < retries; i++) { try { connectionAttempts++; // Создаем пул соединений pool = mysql.createPool(dbConfig); // Проверяем подключение const connection = await pool.getConnection(); await connection.ping(); connection.release(); // ✅ Успешно подключились isDBConnected = true; lastError = null; isReconnecting = false; logger.info('✅ Успешно подключено к MySQL'); logger.info(`📊 Пул соединений: макс. ${dbConfig.connectionLimit}`); return true; } catch (err) { lastError = err.message; isDBConnected = false; pool = null; logger.warn(`❌ Попытка ${i + 1}/${retries} не удалась: ${err.message}`); if (i < retries - 1) { logger.info(`⏳ Повторная попытка через ${delay/1000} секунд...`); await new Promise(resolve => setTimeout(resolve, delay)); } else { logger.error('❌ Все попытки подключения к БД исчерпаны'); startBackgroundReconnection(); } } } return false; } // ============================================ // 🔄 Фоновое переподключение // ============================================ async function reconnectToDatabase() { if (isReconnecting) { logger.warn('⏳ Переподключение уже запущено'); return; } isReconnecting = true; logger.info('🔄 Запущено фоновое переподключение к БД...'); const success = await connectDB(10, 5000); if (success) { logger.info('✅ Переподключение к БД успешно выполнено'); } else { logger.error('❌ Переподключение к БД не удалось'); setTimeout(() => { logger.info('🔄 Запускаем повторную попытку переподключения...'); reconnectToDatabase(); }, 30000); } isReconnecting = false; } function startBackgroundReconnection() { if (!isReconnecting) { logger.info('🔄 Запускаем фоновое переподключение...'); reconnectToDatabase(); } } // ============================================ // 📊 Функция выполнения запросов // ============================================ /** * Выполняет SQL-запрос с параметрами * @param {string} sql - SQL-запрос с плейсхолдерами ? * @param {Array} params - Параметры для подстановки * @returns {Promise} - Результат запроса */ async function query(sql, params = []) { if (!isDBConnected || !pool) { throw new Error('База данных не подключена'); } try { const [rows] = await pool.execute(sql, params); return rows; } catch (err) { logger.error('❌ Ошибка выполнения запроса', { sql, error: err.message }); throw err; } } /** * Выполняет SQL-запрос и возвращает первый результат */ async function queryOne(sql, params = []) { const rows = await query(sql, params); return rows.length > 0 ? rows[0] : null; } // ============================================ // 📊 Функции для получения статуса БД // ============================================ async function getDBStatus() { const testMode = await checkTestMode(); return { connected: isDBConnected, lastError: lastError, connectionAttempts: connectionAttempts, isReconnecting: isReconnecting, testMode: testMode, dbType: 'MySQL', host: dbConfig.host, database: dbConfig.database }; } function isDBConnectedFn() { return isDBConnected; } // ============================================ // 🧹 Закрытие соединения // ============================================ async function closeDB() { try { if (pool) { await pool.end(); pool = null; isDBConnected = false; logger.info('🔒 Соединение с MySQL закрыто'); } } catch (err) { logger.error('❌ Ошибка при закрытии соединения с БД', { error: err.message }); } } // ============================================ // 📤 Экспорт модуля // ============================================ module.exports = { pool, // Пул соединений query, // Выполнение запросов queryOne, // Выполнение запроса с одним результатом connectDB, // Функция подключения getDBStatus, // Получение статуса isDBConnected: isDBConnectedFn, closeDB, reconnectToDatabase };