248 lines
8.5 KiB
JavaScript
248 lines
8.5 KiB
JavaScript
// ============================================
|
|
// 📦 Модуль подключения к MySQL базе данных
|
|
// ============================================
|
|
|
|
const mysql = require('mysql2/promise'); // ✅ Используем promise-версию
|
|
const path = require('path');
|
|
const fs = require('fs-extra');
|
|
const { logger } = require('./logger');
|
|
|
|
// Загружаем переменные окружения
|
|
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 fs.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<Array>} - Результат запроса
|
|
*/
|
|
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
|
|
}; |