const mysql = require('mysql2/promise') require('dotenv').config() function requireEnv(name, fallback) { const value = process.env[name] || fallback if (!value && !fallback) { throw new Error(`Missing required environment variable: ${name}. Check .env file.`) } return value } const config = { host: requireEnv('DB_HOST', 'localhost'), port: parseInt(requireEnv('DB_PORT', '3306')), user: requireEnv('DB_USER', 'root'), password: requireEnv('DB_PASSWORD', ''), database: requireEnv('DB_NAME', 'miniprogram'), waitForConnections: true, connectionLimit: parseInt(process.env.DB_POOL_LIMIT) || 10, queueLimit: 0 } const SLOW_QUERY_MS = parseInt(process.env.SLOW_QUERY_MS) || 500 const pool = mysql.createPool(config) const queryStats = { total: 0, slow: 0, errors: 0, totalDurationMs: 0, lastSlow: [] } function recordSlow(sql, durationMs, params) { queryStats.slow += 1 const entry = { sql: sql.replace(/\s+/g, ' ').slice(0, 200), durationMs: Math.round(durationMs), paramCount: Array.isArray(params) ? params.length : 0, at: new Date().toISOString() } queryStats.lastSlow.unshift(entry) if (queryStats.lastSlow.length > 20) queryStats.lastSlow.pop() console.warn('[SLOW QUERY]', entry.durationMs + 'ms', entry.sql) } async function query(sql, params = []) { const start = Date.now() const connection = await pool.getConnection() try { const [rows] = await connection.execute(sql, params) const duration = Date.now() - start queryStats.total += 1 queryStats.totalDurationMs += duration if (duration >= SLOW_QUERY_MS) recordSlow(sql, duration, params) return rows } catch (err) { queryStats.errors += 1 throw err } finally { connection.release() } } function getPoolMetrics() { const p = pool.pool || pool return { connectionLimit: config.connectionLimit, allConnections: (p._allConnections && p._allConnections.length) || 0, freeConnections: (p._freeConnections && p._freeConnections.length) || 0, acquiringConnections: (p._acquiringConnections && p._acquiringConnections.length) || 0, queue: (p._connectionQueue && p._connectionQueue.length) || 0 } } function getQueryStats() { const avg = queryStats.total > 0 ? Math.round(queryStats.totalDurationMs / queryStats.total) : 0 return { total: queryStats.total, slow: queryStats.slow, errors: queryStats.errors, avgDurationMs: avg, lastSlow: queryStats.lastSlow.slice(0, 10) } } function resetQueryStats() { queryStats.total = 0 queryStats.slow = 0 queryStats.errors = 0 queryStats.totalDurationMs = 0 queryStats.lastSlow = [] } async function initDatabase() { try { const connection = await mysql.createConnection({ host: config.host, port: config.port, user: config.user, password: config.password }) await connection.query(`CREATE DATABASE IF NOT EXISTS ${config.database} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`) await connection.query(`USE ${config.database}`) const fs = require('fs') const path = require('path') const schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf8') const statements = schema.split(';').filter(s => s.trim()) for (const statement of statements) { try { await connection.query(statement) } catch (e) { if (/Duplicate column name|already exists|exists/i.test(e.message)) continue throw e } } await connection.end() console.log('Database initialized successfully') } catch (error) { console.error('Failed to initialize database:', error) throw error } } async function transaction(callback) { const connection = await pool.getConnection() try { await connection.beginTransaction() const result = await callback(connection) await connection.commit() return result } catch (error) { await connection.rollback() throw error } finally { connection.release() } } let healthCheckTimer = null function startHealthCheck(intervalMs = 30000) { if (healthCheckTimer) clearInterval(healthCheckTimer) healthCheckTimer = setInterval(async () => { try { const connection = await pool.getConnection() await connection.ping() connection.release() } catch (err) { console.error('Database health check failed:', err.message) } }, intervalMs) } function stopHealthCheck() { if (healthCheckTimer) { clearInterval(healthCheckTimer) healthCheckTimer = null } } module.exports = { pool, query, transaction, initDatabase, startHealthCheck, stopHealthCheck, getPoolMetrics, getQueryStats, resetQueryStats, config }