修正資料庫未關閉問題
This commit is contained in:
85
app/api/admin/connection-monitor/route.ts
Normal file
85
app/api/admin/connection-monitor/route.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// =====================================================
|
||||||
|
// 連線監控 API
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { connectionMonitor } from '@/lib/connection-monitor';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 獲取連線統計
|
||||||
|
const stats = await connectionMonitor.getConnectionStats();
|
||||||
|
|
||||||
|
// 獲取監控狀態
|
||||||
|
const isMonitoring = connectionMonitor.isCurrentlyMonitoring();
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
stats,
|
||||||
|
isMonitoring,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('獲取連線監控數據失敗:', error);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: '獲取連線監控數據失敗',
|
||||||
|
error: error instanceof Error ? error.message : '未知錯誤'
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { action } = body;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'start_monitoring':
|
||||||
|
const interval = body.interval || 30000;
|
||||||
|
connectionMonitor.startMonitoring(interval);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: `連線監控已啟動,檢查間隔: ${interval}ms`
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'stop_monitoring':
|
||||||
|
connectionMonitor.stopMonitoring();
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: '連線監控已停止'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'force_cleanup':
|
||||||
|
await connectionMonitor.forceCleanup();
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: '強制清理連線完成'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'check_health':
|
||||||
|
const healthStats = await connectionMonitor.checkConnectionHealth();
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: healthStats
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: '無效的操作'
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('執行連線監控操作失敗:', error);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: '執行連線監控操作失敗',
|
||||||
|
error: error instanceof Error ? error.message : '未知錯誤'
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
66
app/api/admin/force-cleanup/route.ts
Normal file
66
app/api/admin/force-cleanup/route.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// =====================================================
|
||||||
|
// 強制清理連線 API
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { db } from '@/lib/database';
|
||||||
|
import { connectionMonitor } from '@/lib/connection-monitor';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
console.log('🧹 開始強制清理資料庫連線...');
|
||||||
|
|
||||||
|
// 獲取清理前的連線狀態
|
||||||
|
const beforeStats = await connectionMonitor.getConnectionStats();
|
||||||
|
console.log(`清理前連線數: ${beforeStats.activeConnections}`);
|
||||||
|
|
||||||
|
// 強制關閉連線池
|
||||||
|
try {
|
||||||
|
await db.close();
|
||||||
|
console.log('✅ 主要資料庫連線池已關閉');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 關閉主要連線池失敗:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待一段時間讓連線完全關閉
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
|
// 重新初始化連線池
|
||||||
|
try {
|
||||||
|
// 重新創建連線池實例
|
||||||
|
const { Database } = await import('@/lib/database');
|
||||||
|
const newDb = Database.getInstance();
|
||||||
|
console.log('✅ 資料庫連線池已重新初始化');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 重新初始化連線池失敗:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 獲取清理後的連線狀態
|
||||||
|
const afterStats = await connectionMonitor.getConnectionStats();
|
||||||
|
console.log(`清理後連線數: ${afterStats.activeConnections}`);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: '強制清理完成',
|
||||||
|
data: {
|
||||||
|
before: {
|
||||||
|
activeConnections: beforeStats.activeConnections,
|
||||||
|
usagePercentage: beforeStats.usagePercentage
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
activeConnections: afterStats.activeConnections,
|
||||||
|
usagePercentage: afterStats.usagePercentage
|
||||||
|
},
|
||||||
|
cleaned: beforeStats.activeConnections - afterStats.activeConnections
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('強制清理失敗:', error);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: '強制清理失敗',
|
||||||
|
error: error instanceof Error ? error.message : '未知錯誤'
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
70
app/api/admin/kill-connections/route.ts
Normal file
70
app/api/admin/kill-connections/route.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// =====================================================
|
||||||
|
// 強制終止連線 API
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { db } from '@/lib/database';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
console.log('💀 開始強制終止所有資料庫連線...');
|
||||||
|
|
||||||
|
// 獲取所有 AI_Platform 的連線
|
||||||
|
const connections = await db.query(`
|
||||||
|
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE
|
||||||
|
FROM INFORMATION_SCHEMA.PROCESSLIST
|
||||||
|
WHERE USER = 'AI_Platform' AND DB = 'db_AI_Platform'
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log(`找到 ${connections.length} 個 AI_Platform 連線`);
|
||||||
|
|
||||||
|
const killedConnections = [];
|
||||||
|
|
||||||
|
// 終止每個連線
|
||||||
|
for (const conn of connections) {
|
||||||
|
try {
|
||||||
|
await db.query(`KILL CONNECTION ${conn.ID}`);
|
||||||
|
killedConnections.push({
|
||||||
|
id: conn.ID,
|
||||||
|
host: conn.HOST,
|
||||||
|
time: conn.TIME,
|
||||||
|
command: conn.COMMAND
|
||||||
|
});
|
||||||
|
console.log(`✅ 已終止連線 ${conn.ID} (閒置 ${conn.TIME} 秒)`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 終止連線 ${conn.ID} 失敗:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待連線完全關閉
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
|
// 檢查剩餘連線
|
||||||
|
const remainingConnections = await db.query(`
|
||||||
|
SELECT COUNT(*) as count
|
||||||
|
FROM INFORMATION_SCHEMA.PROCESSLIST
|
||||||
|
WHERE USER = 'AI_Platform' AND DB = 'db_AI_Platform'
|
||||||
|
`);
|
||||||
|
|
||||||
|
const remainingCount = remainingConnections[0]?.count || 0;
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: '強制終止連線完成',
|
||||||
|
data: {
|
||||||
|
totalFound: connections.length,
|
||||||
|
killed: killedConnections.length,
|
||||||
|
remaining: remainingCount,
|
||||||
|
killedConnections: killedConnections
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('強制終止連線失敗:', error);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: '強制終止連線失敗',
|
||||||
|
error: error instanceof Error ? error.message : '未知錯誤'
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
67
app/api/test-connection/route.ts
Normal file
67
app/api/test-connection/route.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// =====================================================
|
||||||
|
// 連線測試 API - 驗證連線釋放
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { connectionMonitor } from '@/lib/connection-monitor';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 獲取測試前的連線狀態
|
||||||
|
const beforeStats = await connectionMonitor.getConnectionStats();
|
||||||
|
|
||||||
|
// 執行一些測試查詢
|
||||||
|
const testQueries = [
|
||||||
|
'SELECT 1 as test1',
|
||||||
|
'SELECT 2 as test2',
|
||||||
|
'SELECT 3 as test3',
|
||||||
|
'SELECT COUNT(*) as user_count FROM users',
|
||||||
|
'SELECT COUNT(*) as app_count FROM apps'
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('🧪 開始連線測試...');
|
||||||
|
console.log(`測試前連線數: ${beforeStats.activeConnections}`);
|
||||||
|
|
||||||
|
// 執行測試查詢
|
||||||
|
for (let i = 0; i < testQueries.length; i++) {
|
||||||
|
const { db } = await import('@/lib/database');
|
||||||
|
await db.query(testQueries[i]);
|
||||||
|
console.log(`✅ 完成測試查詢 ${i + 1}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待一小段時間讓連線釋放
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// 獲取測試後的連線狀態
|
||||||
|
const afterStats = await connectionMonitor.getConnectionStats();
|
||||||
|
|
||||||
|
console.log(`測試後連線數: ${afterStats.activeConnections}`);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: '連線測試完成',
|
||||||
|
data: {
|
||||||
|
before: {
|
||||||
|
activeConnections: beforeStats.activeConnections,
|
||||||
|
usagePercentage: beforeStats.usagePercentage
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
activeConnections: afterStats.activeConnections,
|
||||||
|
usagePercentage: afterStats.usagePercentage
|
||||||
|
},
|
||||||
|
difference: {
|
||||||
|
connectionChange: afterStats.activeConnections - beforeStats.activeConnections,
|
||||||
|
isReleased: afterStats.activeConnections <= beforeStats.activeConnections
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('連線測試失敗:', error);
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: '連線測試失敗',
|
||||||
|
error: error instanceof Error ? error.message : '未知錯誤'
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
206
lib/connection-monitor.ts
Normal file
206
lib/connection-monitor.ts
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
// =====================================================
|
||||||
|
// 資料庫連線監控服務
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
import { db } from './database';
|
||||||
|
import { dbFailover } from './database-failover';
|
||||||
|
|
||||||
|
export interface ConnectionStats {
|
||||||
|
totalConnections: number;
|
||||||
|
activeConnections: number;
|
||||||
|
idleConnections: number;
|
||||||
|
queuedRequests: number;
|
||||||
|
maxConnections: number;
|
||||||
|
usagePercentage: number;
|
||||||
|
isHealthy: boolean;
|
||||||
|
lastCheck: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConnectionMonitor {
|
||||||
|
private static instance: ConnectionMonitor;
|
||||||
|
private stats: ConnectionStats | null = null;
|
||||||
|
private checkInterval: NodeJS.Timeout | null = null;
|
||||||
|
private isMonitoring = false;
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): ConnectionMonitor {
|
||||||
|
if (!ConnectionMonitor.instance) {
|
||||||
|
ConnectionMonitor.instance = new ConnectionMonitor();
|
||||||
|
}
|
||||||
|
return ConnectionMonitor.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 開始監控
|
||||||
|
public startMonitoring(intervalMs: number = 30000): void {
|
||||||
|
if (this.isMonitoring) {
|
||||||
|
console.log('⚠️ 連線監控已在運行中');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isMonitoring = true;
|
||||||
|
console.log(`🔍 開始連線監控,檢查間隔: ${intervalMs}ms`);
|
||||||
|
|
||||||
|
this.checkInterval = setInterval(async () => {
|
||||||
|
await this.checkConnectionHealth();
|
||||||
|
}, intervalMs);
|
||||||
|
|
||||||
|
// 立即執行一次檢查
|
||||||
|
this.checkConnectionHealth();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止監控
|
||||||
|
public stopMonitoring(): void {
|
||||||
|
if (this.checkInterval) {
|
||||||
|
clearInterval(this.checkInterval);
|
||||||
|
this.checkInterval = null;
|
||||||
|
}
|
||||||
|
this.isMonitoring = false;
|
||||||
|
console.log('🛑 連線監控已停止');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查連線健康狀態
|
||||||
|
public async checkConnectionHealth(): Promise<ConnectionStats> {
|
||||||
|
try {
|
||||||
|
const stats = await this.getConnectionStats();
|
||||||
|
this.stats = stats;
|
||||||
|
|
||||||
|
// 記錄連線狀態
|
||||||
|
console.log(`📊 資料庫連線狀態: ${stats.activeConnections}/${stats.maxConnections} (${stats.usagePercentage.toFixed(1)}%)`);
|
||||||
|
|
||||||
|
// 檢查是否健康
|
||||||
|
if (stats.usagePercentage > 80) {
|
||||||
|
console.warn(`⚠️ 資料庫連線使用率過高: ${stats.usagePercentage.toFixed(1)}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.usagePercentage > 95) {
|
||||||
|
console.error(`🚨 資料庫連線使用率危險: ${stats.usagePercentage.toFixed(1)}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 連線健康檢查失敗:', error);
|
||||||
|
return {
|
||||||
|
totalConnections: 0,
|
||||||
|
activeConnections: 0,
|
||||||
|
idleConnections: 0,
|
||||||
|
queuedRequests: 0,
|
||||||
|
maxConnections: 0,
|
||||||
|
usagePercentage: 0,
|
||||||
|
isHealthy: false,
|
||||||
|
lastCheck: new Date()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 獲取連線統計
|
||||||
|
public async getConnectionStats(): Promise<ConnectionStats> {
|
||||||
|
try {
|
||||||
|
// 檢查備援狀態
|
||||||
|
const failoverStatus = db.getFailoverStatus();
|
||||||
|
|
||||||
|
if (failoverStatus?.isEnabled) {
|
||||||
|
// 使用備援系統獲取統計
|
||||||
|
const result = await dbFailover.queryOne<{
|
||||||
|
current_connections: number;
|
||||||
|
max_connections: number;
|
||||||
|
}>('SHOW STATUS LIKE "Threads_connected"');
|
||||||
|
|
||||||
|
const maxResult = await dbFailover.queryOne<{
|
||||||
|
Variable_name: string;
|
||||||
|
Value: string;
|
||||||
|
}>('SHOW VARIABLES LIKE "max_connections"');
|
||||||
|
|
||||||
|
const currentConnections = result?.current_connections || 0;
|
||||||
|
const maxConnections = parseInt(maxResult?.Value || '0');
|
||||||
|
const usagePercentage = maxConnections > 0 ? (currentConnections / maxConnections) * 100 : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalConnections: currentConnections,
|
||||||
|
activeConnections: currentConnections,
|
||||||
|
idleConnections: 0, // MySQL 不直接提供空閒連線數
|
||||||
|
queuedRequests: 0,
|
||||||
|
maxConnections,
|
||||||
|
usagePercentage,
|
||||||
|
isHealthy: usagePercentage < 90,
|
||||||
|
lastCheck: new Date()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 使用主資料庫獲取統計
|
||||||
|
const result = await db.queryOne<{
|
||||||
|
current_connections: number;
|
||||||
|
max_connections: number;
|
||||||
|
}>('SHOW STATUS LIKE "Threads_connected"');
|
||||||
|
|
||||||
|
const maxResult = await db.queryOne<{
|
||||||
|
Variable_name: string;
|
||||||
|
Value: string;
|
||||||
|
}>('SHOW VARIABLES LIKE "max_connections"');
|
||||||
|
|
||||||
|
const currentConnections = result?.current_connections || 0;
|
||||||
|
const maxConnections = parseInt(maxResult?.Value || '0');
|
||||||
|
const usagePercentage = maxConnections > 0 ? (currentConnections / maxConnections) * 100 : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalConnections: currentConnections,
|
||||||
|
activeConnections: currentConnections,
|
||||||
|
idleConnections: 0,
|
||||||
|
queuedRequests: 0,
|
||||||
|
maxConnections,
|
||||||
|
usagePercentage,
|
||||||
|
isHealthy: usagePercentage < 90,
|
||||||
|
lastCheck: new Date()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('獲取連線統計失敗:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 獲取當前統計
|
||||||
|
public getCurrentStats(): ConnectionStats | null {
|
||||||
|
return this.stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 強制清理連線
|
||||||
|
public async forceCleanup(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('🧹 開始強制清理資料庫連線...');
|
||||||
|
|
||||||
|
// 獲取當前連線數
|
||||||
|
const stats = await this.getConnectionStats();
|
||||||
|
console.log(`清理前連線數: ${stats.activeConnections}`);
|
||||||
|
|
||||||
|
// 強制關閉所有空閒連線
|
||||||
|
try {
|
||||||
|
const { db } = await import('./database');
|
||||||
|
await db.close();
|
||||||
|
console.log('✅ 資料庫連線池已關閉');
|
||||||
|
|
||||||
|
// 等待連線完全關閉
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// 重新初始化
|
||||||
|
const { Database } = await import('./database');
|
||||||
|
const newDb = Database.getInstance();
|
||||||
|
console.log('✅ 資料庫連線池已重新初始化');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 強制清理連線池失敗:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ 連線清理完成');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 連線清理失敗:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查是否正在監控
|
||||||
|
public isCurrentlyMonitoring(): boolean {
|
||||||
|
return this.isMonitoring;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 導出單例實例
|
||||||
|
export const connectionMonitor = ConnectionMonitor.getInstance();
|
@@ -462,8 +462,24 @@ export class DatabaseFailover {
|
|||||||
try {
|
try {
|
||||||
connection = await this.getConnection();
|
connection = await this.getConnection();
|
||||||
const [rows] = await connection.execute(sql, params);
|
const [rows] = await connection.execute(sql, params);
|
||||||
|
|
||||||
|
// 完全關閉連線
|
||||||
|
await connection.destroy();
|
||||||
|
connection = null;
|
||||||
|
console.log('✅ 備援查詢連線已完全關閉');
|
||||||
|
|
||||||
return rows as T[];
|
return rows as T[];
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
// 確保連線被關閉
|
||||||
|
if (connection) {
|
||||||
|
try {
|
||||||
|
await connection.destroy();
|
||||||
|
} catch (destroyError) {
|
||||||
|
console.error('關閉連線時發生錯誤:', destroyError);
|
||||||
|
}
|
||||||
|
connection = null;
|
||||||
|
}
|
||||||
|
|
||||||
console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
|
console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
|
||||||
console.error('查詢錯誤詳情:', {
|
console.error('查詢錯誤詳情:', {
|
||||||
code: error.code,
|
code: error.code,
|
||||||
@@ -540,36 +556,60 @@ export class DatabaseFailover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 執行插入
|
// 執行插入 - 每次查詢完就完全關閉連線
|
||||||
public async insert(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
public async insert(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||||||
const connection = await this.getConnection();
|
let connection;
|
||||||
try {
|
try {
|
||||||
|
connection = await this.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
return result as mysql.ResultSetHeader;
|
return result as mysql.ResultSetHeader;
|
||||||
} finally {
|
} finally {
|
||||||
connection.release();
|
if (connection) {
|
||||||
|
try {
|
||||||
|
await connection.destroy();
|
||||||
|
console.log('✅ 備援插入連線已完全關閉');
|
||||||
|
} catch (destroyError) {
|
||||||
|
console.error('關閉連線時發生錯誤:', destroyError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 執行更新
|
// 執行更新 - 每次查詢完就完全關閉連線
|
||||||
public async update(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
public async update(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||||||
const connection = await this.getConnection();
|
let connection;
|
||||||
try {
|
try {
|
||||||
|
connection = await this.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
return result as mysql.ResultSetHeader;
|
return result as mysql.ResultSetHeader;
|
||||||
} finally {
|
} finally {
|
||||||
connection.release();
|
if (connection) {
|
||||||
|
try {
|
||||||
|
await connection.destroy();
|
||||||
|
console.log('✅ 備援更新連線已完全關閉');
|
||||||
|
} catch (destroyError) {
|
||||||
|
console.error('關閉連線時發生錯誤:', destroyError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 執行刪除
|
// 執行刪除 - 每次查詢完就完全關閉連線
|
||||||
public async delete(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
public async delete(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
|
||||||
const connection = await this.getConnection();
|
let connection;
|
||||||
try {
|
try {
|
||||||
|
connection = await this.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
return result as mysql.ResultSetHeader;
|
return result as mysql.ResultSetHeader;
|
||||||
} finally {
|
} finally {
|
||||||
connection.release();
|
if (connection) {
|
||||||
|
try {
|
||||||
|
await connection.destroy();
|
||||||
|
console.log('✅ 備援刪除連線已完全關閉');
|
||||||
|
} catch (destroyError) {
|
||||||
|
console.error('關閉連線時發生錯誤:', destroyError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,10 +6,11 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
import { db } from './database';
|
import { db } from './database';
|
||||||
import { dbMonitor } from './database-monitor';
|
import { dbMonitor } from './database-monitor';
|
||||||
import { dbShutdownManager } from './database-shutdown-manager';
|
import { dbShutdownManager } from './database-shutdown-manager';
|
||||||
|
import { connectionMonitor } from './connection-monitor';
|
||||||
|
|
||||||
// 連線池狀態追蹤
|
// 連線池狀態追蹤
|
||||||
let connectionCount = 0;
|
let connectionCount = 0;
|
||||||
const maxConnections = 3; // 與資料庫配置保持一致
|
const maxConnections = 20; // 與資料庫配置保持一致
|
||||||
|
|
||||||
export function withDatabaseConnection<T extends any[]>(
|
export function withDatabaseConnection<T extends any[]>(
|
||||||
handler: (...args: T) => Promise<NextResponse>
|
handler: (...args: T) => Promise<NextResponse>
|
||||||
@@ -87,11 +88,12 @@ export async function checkConnectionHealth(): Promise<{
|
|||||||
usagePercentage: number;
|
usagePercentage: number;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const stats = dbMonitor.getConnectionStats();
|
// 使用連線監控器獲取詳細統計
|
||||||
|
const stats = await connectionMonitor.getConnectionStats();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isHealthy: stats.usagePercentage < 80,
|
isHealthy: stats.isHealthy,
|
||||||
connectionCount: stats.currentConnections,
|
connectionCount: stats.activeConnections,
|
||||||
maxConnections: stats.maxConnections,
|
maxConnections: stats.maxConnections,
|
||||||
usagePercentage: stats.usagePercentage
|
usagePercentage: stats.usagePercentage
|
||||||
};
|
};
|
||||||
|
@@ -6,7 +6,7 @@ import { db } from './database';
|
|||||||
import { PoolConnection } from 'mysql2/promise';
|
import { PoolConnection } from 'mysql2/promise';
|
||||||
|
|
||||||
export abstract class DatabaseServiceBase {
|
export abstract class DatabaseServiceBase {
|
||||||
// 靜態安全查詢方法 - 確保連線關閉
|
// 靜態安全查詢方法 - 每次查詢完就完全關閉連線
|
||||||
static async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
static async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||||
let connection: PoolConnection | null = null;
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
@@ -17,25 +17,27 @@ export abstract class DatabaseServiceBase {
|
|||||||
// 執行查詢
|
// 執行查詢
|
||||||
const [rows] = await connection.execute(sql, params);
|
const [rows] = await connection.execute(sql, params);
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線(不是釋放回連線池)
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 靜態查詢連線已完全關閉');
|
||||||
|
|
||||||
return rows as T[];
|
return rows as T[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 確保連線被釋放
|
// 確保連線被關閉
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉靜態查詢連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 實例安全查詢方法 - 確保連線關閉
|
// 實例安全查詢方法 - 每次查詢完就完全關閉連線
|
||||||
protected async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
protected async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||||
let connection: PoolConnection | null = null;
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
@@ -46,18 +48,20 @@ export abstract class DatabaseServiceBase {
|
|||||||
// 執行查詢
|
// 執行查詢
|
||||||
const [rows] = await connection.execute(sql, params);
|
const [rows] = await connection.execute(sql, params);
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線(不是釋放回連線池)
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 連線已完全關閉');
|
||||||
|
|
||||||
return rows as T[];
|
return rows as T[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 確保連線被釋放
|
// 確保連線被關閉
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -76,7 +80,7 @@ export abstract class DatabaseServiceBase {
|
|||||||
return results.length > 0 ? results[0] : null;
|
return results.length > 0 ? results[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 靜態安全插入方法
|
// 靜態安全插入方法 - 每次查詢完就完全關閉連線
|
||||||
static async safeInsert(sql: string, params?: any[]): Promise<any> {
|
static async safeInsert(sql: string, params?: any[]): Promise<any> {
|
||||||
let connection: PoolConnection | null = null;
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
@@ -84,24 +88,26 @@ export abstract class DatabaseServiceBase {
|
|||||||
connection = await db.getConnection();
|
connection = await db.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 靜態插入連線已完全關閉');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉靜態插入連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 實例安全插入方法
|
// 實例安全插入方法 - 每次查詢完就完全關閉連線
|
||||||
protected async safeInsert(sql: string, params?: any[]): Promise<any> {
|
protected async safeInsert(sql: string, params?: any[]): Promise<any> {
|
||||||
let connection: PoolConnection | null = null;
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
@@ -109,24 +115,26 @@ export abstract class DatabaseServiceBase {
|
|||||||
connection = await db.getConnection();
|
connection = await db.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 實例插入連線已完全關閉');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉實例插入連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 靜態安全更新方法
|
// 靜態安全更新方法 - 每次查詢完就完全關閉連線
|
||||||
static async safeUpdate(sql: string, params?: any[]): Promise<any> {
|
static async safeUpdate(sql: string, params?: any[]): Promise<any> {
|
||||||
let connection: PoolConnection | null = null;
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
@@ -134,24 +142,26 @@ export abstract class DatabaseServiceBase {
|
|||||||
connection = await db.getConnection();
|
connection = await db.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 靜態更新連線已完全關閉');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉靜態更新連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 實例安全更新方法
|
// 實例安全更新方法 - 每次查詢完就完全關閉連線
|
||||||
protected async safeUpdate(sql: string, params?: any[]): Promise<any> {
|
protected async safeUpdate(sql: string, params?: any[]): Promise<any> {
|
||||||
let connection: PoolConnection | null = null;
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
@@ -159,24 +169,26 @@ export abstract class DatabaseServiceBase {
|
|||||||
connection = await db.getConnection();
|
connection = await db.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 實例更新連線已完全關閉');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉實例更新連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 靜態安全刪除方法
|
// 靜態安全刪除方法 - 每次查詢完就完全關閉連線
|
||||||
static async safeDelete(sql: string, params?: any[]): Promise<any> {
|
static async safeDelete(sql: string, params?: any[]): Promise<any> {
|
||||||
let connection: PoolConnection | null = null;
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
@@ -184,24 +196,26 @@ export abstract class DatabaseServiceBase {
|
|||||||
connection = await db.getConnection();
|
connection = await db.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 靜態刪除連線已完全關閉');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉靜態刪除連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 實例安全刪除方法
|
// 實例安全刪除方法 - 每次查詢完就完全關閉連線
|
||||||
protected async safeDelete(sql: string, params?: any[]): Promise<any> {
|
protected async safeDelete(sql: string, params?: any[]): Promise<any> {
|
||||||
let connection: PoolConnection | null = null;
|
let connection: PoolConnection | null = null;
|
||||||
|
|
||||||
@@ -209,24 +223,26 @@ export abstract class DatabaseServiceBase {
|
|||||||
connection = await db.getConnection();
|
connection = await db.getConnection();
|
||||||
const [result] = await connection.execute(sql, params);
|
const [result] = await connection.execute(sql, params);
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 實例刪除連線已完全關閉');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉實例刪除連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 事務處理方法
|
// 事務處理方法 - 每次查詢完就完全關閉連線
|
||||||
protected async withTransaction<T>(
|
protected async withTransaction<T>(
|
||||||
callback: (connection: PoolConnection) => Promise<T>
|
callback: (connection: PoolConnection) => Promise<T>
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
@@ -240,10 +256,12 @@ export abstract class DatabaseServiceBase {
|
|||||||
|
|
||||||
await connection.commit();
|
await connection.commit();
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 事務連線已完全關閉');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
@@ -254,16 +272,16 @@ export abstract class DatabaseServiceBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉事務連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批次查詢方法 - 減少連線使用
|
// 批次查詢方法 - 每次查詢完就完全關閉連線
|
||||||
protected async batchQuery<T = any>(
|
protected async batchQuery<T = any>(
|
||||||
queries: Array<{ sql: string; params?: any[] }>
|
queries: Array<{ sql: string; params?: any[] }>
|
||||||
): Promise<T[][]> {
|
): Promise<T[][]> {
|
||||||
@@ -278,17 +296,19 @@ export abstract class DatabaseServiceBase {
|
|||||||
results.push(rows as T[]);
|
results.push(rows as T[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 立即釋放連線
|
// 完全關閉連線
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
connection = null;
|
connection = null;
|
||||||
|
|
||||||
|
console.log('✅ 批次查詢連線已完全關閉');
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
connection.release();
|
await connection.destroy();
|
||||||
} catch (releaseError) {
|
} catch (destroyError) {
|
||||||
console.error('釋放連線時發生錯誤:', releaseError);
|
console.error('關閉批次查詢連線時發生錯誤:', destroyError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
import { db } from './database';
|
import { db } from './database';
|
||||||
import { dbFailover } from './database-failover';
|
import { dbFailover } from './database-failover';
|
||||||
import { dbMonitor } from './database-monitor';
|
import { dbMonitor } from './database-monitor';
|
||||||
|
import { connectionMonitor } from './connection-monitor';
|
||||||
|
|
||||||
export class DatabaseShutdownManager {
|
export class DatabaseShutdownManager {
|
||||||
private static instance: DatabaseShutdownManager;
|
private static instance: DatabaseShutdownManager;
|
||||||
@@ -57,6 +58,17 @@ export class DatabaseShutdownManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 添加連線監控關閉處理器
|
||||||
|
this.addShutdownHandler('connection-monitor', async () => {
|
||||||
|
console.log('🔄 正在停止連線監控服務...');
|
||||||
|
try {
|
||||||
|
connectionMonitor.stopMonitoring();
|
||||||
|
console.log('✅ 連線監控服務已停止');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 停止連線監控服務時發生錯誤:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// 註冊系統信號處理器
|
// 註冊系統信號處理器
|
||||||
this.registerSystemHandlers();
|
this.registerSystemHandlers();
|
||||||
@@ -75,6 +87,21 @@ export class DatabaseShutdownManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化服務
|
||||||
|
public initialize(): void {
|
||||||
|
console.log('🚀 初始化資料庫服務...');
|
||||||
|
|
||||||
|
// 啟動連線監控
|
||||||
|
try {
|
||||||
|
connectionMonitor.startMonitoring(30000); // 30秒檢查一次
|
||||||
|
console.log('✅ 連線監控已啟動');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 啟動連線監控失敗:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ 資料庫服務初始化完成');
|
||||||
|
}
|
||||||
|
|
||||||
// 註冊系統信號處理器
|
// 註冊系統信號處理器
|
||||||
private registerSystemHandlers() {
|
private registerSystemHandlers() {
|
||||||
if (typeof process === 'undefined') return;
|
if (typeof process === 'undefined') return;
|
||||||
|
@@ -6,7 +6,7 @@ import mysql from 'mysql2/promise';
|
|||||||
import { dbFailover } from './database-failover';
|
import { dbFailover } from './database-failover';
|
||||||
import { dbSync } from './database-sync';
|
import { dbSync } from './database-sync';
|
||||||
|
|
||||||
// 資料庫配置
|
// 資料庫配置 - 強制快速釋放連線
|
||||||
const dbConfig = {
|
const dbConfig = {
|
||||||
host: process.env.DB_HOST || '122.100.99.161',
|
host: process.env.DB_HOST || '122.100.99.161',
|
||||||
port: parseInt(process.env.DB_PORT || '43306'),
|
port: parseInt(process.env.DB_PORT || '43306'),
|
||||||
@@ -16,19 +16,22 @@ const dbConfig = {
|
|||||||
charset: 'utf8mb4',
|
charset: 'utf8mb4',
|
||||||
timezone: '+08:00',
|
timezone: '+08:00',
|
||||||
acquireTimeout: 5000, // 5秒獲取連接超時
|
acquireTimeout: 5000, // 5秒獲取連接超時
|
||||||
timeout: 8000, // 8秒查詢超時
|
timeout: 10000, // 10秒查詢超時
|
||||||
reconnect: true,
|
reconnect: true,
|
||||||
connectionLimit: 3, // 進一步減少連接數,避免 Too many connections
|
connectionLimit: 5, // 大幅減少連接數限制
|
||||||
queueLimit: 5, // 減少排隊數量
|
queueLimit: 10, // 減少排隊數量
|
||||||
// 添加連接重試和錯誤處理配置
|
// 添加連接重試和錯誤處理配置
|
||||||
retryDelay: 1000,
|
retryDelay: 1000,
|
||||||
maxRetries: 2,
|
maxRetries: 2,
|
||||||
// 添加連接池配置
|
// 強制快速釋放連線的配置
|
||||||
idleTimeout: 30000, // 30秒空閒超時,快速釋放連接
|
idleTimeout: 5000, // 5秒空閒超時,強制快速釋放
|
||||||
maxIdle: 2, // 最大空閒連接數
|
maxIdle: 1, // 最多只保留1個空閒連接
|
||||||
// 添加連接生命週期管理
|
// 添加連接生命週期管理
|
||||||
maxReconnects: 3,
|
maxReconnects: 3,
|
||||||
reconnectDelay: 2000,
|
reconnectDelay: 1000,
|
||||||
|
// 強制關閉空閒連線
|
||||||
|
keepAliveInitialDelay: 0,
|
||||||
|
enableKeepAlive: false, // 關閉 keepAlive
|
||||||
// 添加 SSL 配置(如果需要)
|
// 添加 SSL 配置(如果需要)
|
||||||
ssl: false as any,
|
ssl: false as any,
|
||||||
};
|
};
|
||||||
|
@@ -40,7 +40,7 @@ import type {
|
|||||||
// =====================================================
|
// =====================================================
|
||||||
export class UserService extends DatabaseServiceBase {
|
export class UserService extends DatabaseServiceBase {
|
||||||
// 創建用戶
|
// 創建用戶
|
||||||
async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
|
async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'> & { id: string }): Promise<User> {
|
||||||
const sql = `
|
const sql = `
|
||||||
INSERT INTO users (id, name, email, password_hash, avatar, department, role, join_date, total_likes, total_views, status, last_login)
|
INSERT INTO users (id, name, email, password_hash, avatar, department, role, join_date, total_likes, total_views, status, last_login)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
@@ -572,9 +572,9 @@ export class UserService extends DatabaseServiceBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通用查詢方法
|
// 通用查詢方法 - 使用安全查詢確保連線釋放
|
||||||
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
|
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
|
||||||
return await db.query<T>(sql, params);
|
return await this.safeQuery<T>(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取儀表板統計數據
|
// 獲取儀表板統計數據
|
||||||
@@ -764,21 +764,25 @@ export class UserService extends DatabaseServiceBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通用單一查詢方法
|
// 通用單一查詢方法 - 使用安全查詢確保連線釋放
|
||||||
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
|
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
|
||||||
return await db.queryOne<T>(sql, params);
|
return await this.safeQueryOne<T>(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取所有用戶
|
// 獲取所有用戶
|
||||||
async getAllUsers(limit = 50, offset = 0): Promise<User[]> {
|
async getAllUsers(limit = 50, offset = 0): Promise<User[]> {
|
||||||
const sql = 'SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
const sql = 'SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
||||||
return await db.query<User>(sql, [limit, offset]);
|
return await this.safeQuery<User>(sql, [limit, offset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 靜態方法保持向後兼容
|
// 靜態方法保持向後兼容
|
||||||
static async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'> & { id?: string }): Promise<User> {
|
static async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'> & { id?: string }): Promise<User> {
|
||||||
const service = new UserService();
|
const service = new UserService();
|
||||||
return await service.create(userData);
|
// 確保 id 存在
|
||||||
|
if (!userData.id) {
|
||||||
|
userData.id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
}
|
||||||
|
return await service.create(userData as Omit<User, 'id' | 'created_at' | 'updated_at'> & { id: string });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUserByEmail(email: string): Promise<User | null> {
|
static async getUserByEmail(email: string): Promise<User | null> {
|
||||||
@@ -2394,14 +2398,14 @@ export class AppService extends DatabaseServiceBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通用查詢方法
|
// 通用查詢方法 - 使用安全查詢確保連線釋放
|
||||||
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
|
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
|
||||||
return await db.query<T>(sql, params);
|
return await this.safeQuery<T>(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通用單一查詢方法
|
// 通用單一查詢方法 - 使用安全查詢確保連線釋放
|
||||||
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
|
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
|
||||||
return await db.queryOne<T>(sql, params);
|
return await this.safeQueryOne<T>(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取應用評分統計
|
// 獲取應用評分統計
|
||||||
@@ -3269,23 +3273,6 @@ export class AppService extends DatabaseServiceBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取應用的收藏數量
|
|
||||||
async getAppFavoritesCount(appId: string): Promise<number> {
|
|
||||||
try {
|
|
||||||
const sql = `
|
|
||||||
SELECT COUNT(*) as count
|
|
||||||
FROM user_favorites
|
|
||||||
WHERE app_id = ?
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = await this.queryOne(sql, [appId]);
|
|
||||||
return result.count || 0;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('獲取應用收藏數量錯誤:', error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 獲取用戶收藏的應用ID列表(不依賴 apps 表狀態)
|
// 獲取用戶收藏的應用ID列表(不依賴 apps 表狀態)
|
||||||
async getUserFavoriteAppIds(userId: string): Promise<string[]> {
|
async getUserFavoriteAppIds(userId: string): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
|
Reference in New Issue
Block a user