diff --git a/app/api/admin/analytics/route.ts b/app/api/admin/analytics/route.ts index 0ac6e3c..79ffef2 100644 --- a/app/api/admin/analytics/route.ts +++ b/app/api/admin/analytics/route.ts @@ -4,43 +4,34 @@ import { db } from "@/lib/database" export async function GET(request: NextRequest) { try { - const appService = new AppService() - const userService = new UserService() - // 獲取總用戶數 - const totalUsersResult = await db.queryOne(` - SELECT COUNT(*) as total FROM users - `) - const totalUsers = totalUsersResult?.total || 0 - - // 獲取今日活躍用戶數(今日有登入記錄的用戶) + // 使用批次查詢減少連線使用 const today = new Date().toISOString().split('T')[0] - const todayActiveUsersResult = await db.queryOne(` - SELECT COUNT(DISTINCT user_id) as count - FROM activity_logs - WHERE DATE(created_at) = ? AND action = 'login' - `, [today]) - const todayActiveUsers = todayActiveUsersResult?.count || 0 - - // 獲取昨日活躍用戶數(用於比較) const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0] - const yesterdayActiveUsersResult = await db.queryOne(` - SELECT COUNT(DISTINCT user_id) as count - FROM activity_logs - WHERE DATE(created_at) = ? AND action = 'login' - `, [yesterday]) - const yesterdayActiveUsers = yesterdayActiveUsersResult?.count || 0 + + // 批次查詢基本統計數據 + const basicStats = await db.query(` + SELECT + (SELECT COUNT(*) FROM users) as total_users, + (SELECT COUNT(DISTINCT user_id) FROM activity_logs WHERE DATE(created_at) = ? AND action = 'login') as today_active_users, + (SELECT COUNT(DISTINCT user_id) FROM activity_logs WHERE DATE(created_at) = ? AND action = 'login') as yesterday_active_users, + (SELECT AVG(rating) FROM apps WHERE rating > 0) as avg_rating, + (SELECT COUNT(*) FROM apps) as total_apps, + (SELECT COUNT(*) FROM apps LIMIT 5) as new_this_week + `, [today, yesterday]) + + const stats = basicStats[0] + const totalUsers = stats?.total_users || 0 + const todayActiveUsers = stats?.today_active_users || 0 + const yesterdayActiveUsers = stats?.yesterday_active_users || 0 + const avgRating = stats?.avg_rating || 0 + const totalApps = stats?.total_apps || 0 + const newThisWeek = stats?.new_this_week || 0 // 計算今日活躍用戶增長率 const todayActiveGrowth = yesterdayActiveUsers > 0 ? ((todayActiveUsers - yesterdayActiveUsers) / yesterdayActiveUsers * 100).toFixed(1) : 0 - // 獲取平均評分 - const avgRatingResult = await db.queryOne(` - SELECT AVG(rating) as avg_rating FROM apps WHERE rating > 0 - `) - const avgRating = avgRatingResult?.avg_rating || 0 - // 獲取上週平均評分(簡化版本,使用當前評分減去0.1) const lastWeekRating = Math.max(0, avgRating - 0.1) @@ -49,21 +40,6 @@ export async function GET(request: NextRequest) { ? (avgRating - lastWeekRating).toFixed(1) : 0 - // 獲取應用總數 - const totalAppsResult = await db.queryOne(` - SELECT COUNT(*) as total FROM apps - `) - const totalApps = totalAppsResult?.total || 0 - - // 獲取本週新增應用數(簡化版本,不依賴日期) - const newThisWeekResult = await db.queryOne(` - SELECT COUNT(*) as count - FROM apps - WHERE id LIKE '%' - LIMIT 5 - `) - const newThisWeek = newThisWeekResult?.count || 0 - // 計算用戶增長率(考慮平台剛上線的情況) let userGrowth = 0 let userGrowthText = "較上月" @@ -78,36 +54,56 @@ export async function GET(request: NextRequest) { userGrowthText = "較上月" } - // 獲取近7天的使用趨勢數據(真實數據) + // 批次查詢近7天的使用趨勢數據 + const dateRange = [] + for (let i = 6; i >= 0; i--) { + const date = new Date(Date.now() - i * 24 * 60 * 60 * 1000) + dateRange.push(date.toISOString().split('T')[0]) + } + + const dailyStats = await db.query(` + SELECT + DATE(viewed_at) as date, + COUNT(DISTINCT user_id) as daily_users, + COUNT(*) as daily_sessions + FROM user_views + WHERE DATE(viewed_at) IN (${dateRange.map(() => '?').join(',')}) + GROUP BY DATE(viewed_at) + `, dateRange) + + const dailyActivityStats = await db.query(` + SELECT + DATE(created_at) as date, + COUNT(*) as daily_activity + FROM activity_logs + WHERE DATE(created_at) IN (${dateRange.map(() => '?').join(',')}) + GROUP BY DATE(created_at) + `, dateRange) + + // 建立查詢結果的映射 + const dailyStatsMap = new Map() + dailyStats.forEach(stat => { + dailyStatsMap.set(stat.date, stat) + }) + + const dailyActivityMap = new Map() + dailyActivityStats.forEach(stat => { + dailyActivityMap.set(stat.date, stat) + }) + + // 構建每日使用數據 const dailyUsageData = [] for (let i = 6; i >= 0; i--) { const date = new Date(Date.now() - i * 24 * 60 * 60 * 1000) const dateStr = date.toISOString().split('T')[0] const dayName = ["日", "一", "二", "三", "四", "五", "六"][date.getDay()] - // 查詢當日活躍用戶數(基於瀏覽記錄) - const dailyUsersResult = await db.queryOne(` - SELECT COUNT(DISTINCT user_id) as count - FROM user_views - WHERE DATE(viewed_at) = ? - `, [dateStr]) - const dailyUsers = dailyUsersResult?.count || 0 + const dailyStat = dailyStatsMap.get(dateStr) || { daily_users: 0, daily_sessions: 0 } + const activityStat = dailyActivityMap.get(dateStr) || { daily_activity: 0 } - // 查詢當日總瀏覽次數 - const dailySessionsResult = await db.queryOne(` - SELECT COUNT(*) as count - FROM user_views - WHERE DATE(viewed_at) = ? - `, [dateStr]) - const dailySessions = dailySessionsResult?.count || 0 - - // 查詢當日活動記錄數 - const dailyActivityResult = await db.queryOne(` - SELECT COUNT(*) as count - FROM activity_logs - WHERE DATE(created_at) = ? - `, [dateStr]) - const dailyActivity = dailyActivityResult?.count || 0 + const dailyUsers = dailyStat.daily_users || 0 + const dailySessions = dailyStat.daily_sessions || 0 + const dailyActivity = activityStat.daily_activity || 0 // 基於真實數據計算系統負載 const cpuPeak = Math.min(90, 20 + dailyUsers * 0.8 + dailySessions * 0.05) @@ -170,26 +166,32 @@ export async function GET(request: NextRequest) { category: app.category })) - // 獲取24小時使用數據 + // 批次查詢24小時使用數據 + const hourlyStats = await db.query(` + SELECT + HOUR(created_at) as hour, + COUNT(DISTINCT CASE WHEN action IN ('login', 'view') THEN user_id END) as hourly_users, + COUNT(*) as hourly_activity + FROM activity_logs + WHERE DATE(created_at) = CURDATE() + GROUP BY HOUR(created_at) + ORDER BY hour + `) + + // 建立小時統計的映射 + const hourlyStatsMap = new Map() + hourlyStats.forEach(stat => { + hourlyStatsMap.set(stat.hour, stat) + }) + + // 構建24小時數據 const hourlyData = [] for (let hour = 0; hour < 24; hour++) { const hourStr = hour.toString().padStart(2, '0') - // 查詢該小時的活躍用戶數 - const hourlyUsersResult = await db.queryOne(` - SELECT COUNT(DISTINCT user_id) as count - FROM activity_logs - WHERE HOUR(created_at) = ? AND action IN ('login', 'view') - `, [hour]) - const hourlyUsers = hourlyUsersResult?.count || 0 - - // 查詢該小時的總活動數 - const hourlyActivityResult = await db.queryOne(` - SELECT COUNT(*) as count - FROM activity_logs - WHERE HOUR(created_at) = ? - `, [hour]) - const hourlyActivity = hourlyActivityResult?.count || 0 + const hourlyStat = hourlyStatsMap.get(hour) || { hourly_users: 0, hourly_activity: 0 } + const hourlyUsers = hourlyStat.hourly_users || 0 + const hourlyActivity = hourlyStat.hourly_activity || 0 // 根據時間段和用戶數確定強度等級 let intensity = "low" diff --git a/app/api/admin/judges/[id]/route.ts b/app/api/admin/judges/[id]/route.ts index fec75a3..db07d4f 100644 --- a/app/api/admin/judges/[id]/route.ts +++ b/app/api/admin/judges/[id]/route.ts @@ -5,6 +5,8 @@ import { NextRequest, NextResponse } from 'next/server'; import { JudgeService } from '@/lib/services/database-service'; +const judgeService = new JudgeService(); + // 獲取單一評審 export async function GET(request: NextRequest, { params }: { params: { id: string } }) { try { @@ -105,7 +107,7 @@ export async function PUT(request: NextRequest, { params }: { params: { id: stri if (body.is_active !== undefined) updateData.is_active = body.is_active; // 執行更新 - const success = await JudgeService.updateJudge(id, updateData); + const success = await judgeservice.updateJudge(id, updateData); if (!success) { return NextResponse.json({ @@ -155,10 +157,10 @@ export async function DELETE(request: NextRequest, { params }: { params: { id: s if (hardDelete) { // 硬刪除:從資料庫中完全移除 - success = await JudgeService.deleteJudge(id); + success = await judgeservice.deleteJudge(id); } else { // 軟刪除:將 is_active 設為 false - success = await JudgeService.updateJudge(id, { is_active: false }); + success = await judgeservice.updateJudge(id, { is_active: false }); } if (!success) { diff --git a/app/api/admin/judges/route.ts b/app/api/admin/judges/route.ts index 5bc32eb..9ac721b 100644 --- a/app/api/admin/judges/route.ts +++ b/app/api/admin/judges/route.ts @@ -118,7 +118,8 @@ export async function POST(request: NextRequest) { is_active: isActive }; - const newJudge = await JudgeService.createJudge(judgeData); + const judgeService = new JudgeService(); + const newJudge = await judgeService.createJudge(judgeData); return NextResponse.json({ success: true, diff --git a/app/api/admin/users/route.ts b/app/api/admin/users/route.ts index 4434a3d..610e195 100644 --- a/app/api/admin/users/route.ts +++ b/app/api/admin/users/route.ts @@ -74,7 +74,7 @@ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) const page = parseInt(searchParams.get('page') || '1') - const limit = parseInt(searchParams.get('limit') || '10') + const limit = parseInt(searchParams.get('limit') || '5') const search = searchParams.get('search') || '' const department = searchParams.get('department') || '' const role = searchParams.get('role') || '' diff --git a/app/page.tsx b/app/page.tsx index a73724c..9b2f42c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -973,16 +973,11 @@ export default function AIShowcasePlatform() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 diff --git a/app/register/page.tsx b/app/register/page.tsx index 13862fc..8e95113 100644 --- a/app/register/page.tsx +++ b/app/register/page.tsx @@ -320,16 +320,11 @@ export default function RegisterPage() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 diff --git a/components/admin/app-management.tsx b/components/admin/app-management.tsx index 673c684..80a9874 100644 --- a/components/admin/app-management.tsx +++ b/components/admin/app-management.tsx @@ -985,16 +985,11 @@ export function AppManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -1156,16 +1151,11 @@ export function AppManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 diff --git a/components/admin/competition-management.tsx b/components/admin/competition-management.tsx index a5291a2..e30e061 100644 --- a/components/admin/competition-management.tsx +++ b/components/admin/competition-management.tsx @@ -2659,16 +2659,11 @@ export function CompetitionManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -2944,16 +2939,11 @@ export function CompetitionManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -5140,16 +5130,11 @@ export function CompetitionManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -5236,16 +5221,11 @@ export function CompetitionManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -5363,16 +5343,11 @@ export function CompetitionManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -6281,16 +6256,11 @@ export function CompetitionManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -6385,16 +6355,11 @@ export function CompetitionManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -6809,7 +6774,7 @@ export function CompetitionManagement() { 部門 * {/* 自定義部門輸入框 */} - {(newJudge.department === "" || !["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ICDU", "ICBU", "ITBU", "MBU1", "MBU5", "PJA", "PBU", "SBG", "SBU", "董事會", "法務室", "關係企業發展", "稽核室", "總經理室"].includes(newJudge.department)) && ( + {(newJudge.department === "" || !["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ITBU", "MBU1", "PBU", "SBG", "SBU", "法務室", "關係企業發展", "稽核室", "總經理室"].includes(newJudge.department)) && ( setNewJudge({ ...newJudge, department: e.target.value })} diff --git a/components/admin/team-management.tsx b/components/admin/team-management.tsx index b554396..bab8be6 100644 --- a/components/admin/team-management.tsx +++ b/components/admin/team-management.tsx @@ -315,16 +315,11 @@ export function TeamManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 diff --git a/components/admin/user-management.tsx b/components/admin/user-management.tsx index 6943e0a..7b94ad2 100644 --- a/components/admin/user-management.tsx +++ b/components/admin/user-management.tsx @@ -88,7 +88,7 @@ export function UserManagement() { }) const [pagination, setPagination] = useState({ page: 1, - limit: 10, + limit: 5, total: 0, totalPages: 0 }) @@ -813,16 +813,11 @@ export function UserManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 @@ -963,6 +958,36 @@ export function UserManagement() { ))} + + {/* 用戶列表分頁控件 */} + {pagination.totalPages > 1 && ( +
+
+ 顯示第 {((pagination.page - 1) * pagination.limit) + 1} - {Math.min(pagination.page * pagination.limit, pagination.total)} 項,共 {pagination.total} 項 +
+
+ + + 第 {pagination.page} 頁,共 {pagination.totalPages} 頁 + + +
+
+ )} @@ -1207,16 +1232,11 @@ export function UserManagement() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 diff --git a/components/auth/register-dialog.tsx b/components/auth/register-dialog.tsx index 02de2b9..115eb96 100644 --- a/components/auth/register-dialog.tsx +++ b/components/auth/register-dialog.tsx @@ -32,7 +32,7 @@ export function RegisterDialog({ open, onOpenChange }: RegisterDialogProps) { const [showConfirmPassword, setShowConfirmPassword] = useState(false) const [success, setSuccess] = useState(false) - const departments = ["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ICDU", "ICBU", "ITBU", "MBU1", "MBU5", "PJA", "PBU", "SBG", "SBU", "董事會", "法務室", "關係企業發展", "稽核室", "總經理室"] + const departments = ["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ITBU", "MBU1", "PBU", "SBG", "SBU", "法務室", "關係企業發展", "稽核室", "總經理室"] const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() diff --git a/components/competition/popularity-rankings.tsx b/components/competition/popularity-rankings.tsx index 03e3851..594639d 100644 --- a/components/competition/popularity-rankings.tsx +++ b/components/competition/popularity-rankings.tsx @@ -755,16 +755,11 @@ export function PopularityRankings() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 diff --git a/components/competition/registration-dialog.tsx b/components/competition/registration-dialog.tsx index 93b0a2c..e54929f 100644 --- a/components/competition/registration-dialog.tsx +++ b/components/competition/registration-dialog.tsx @@ -326,16 +326,11 @@ export function RegistrationDialog({ open, onOpenChange }: RegistrationDialogPro HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 diff --git a/components/favorites-page.tsx b/components/favorites-page.tsx index b3151ae..19cc42f 100644 --- a/components/favorites-page.tsx +++ b/components/favorites-page.tsx @@ -202,16 +202,11 @@ export function FavoritesPage() { HQBU HRBU IBU - ICDU - ICBU ITBU MBU1 - MBU5 - PJA PBU SBG SBU - 董事會 法務室 關係企業發展 稽核室 diff --git a/contexts/competition-context.tsx b/contexts/competition-context.tsx index f6c8e7e..0b79f4c 100644 --- a/contexts/competition-context.tsx +++ b/contexts/competition-context.tsx @@ -148,9 +148,9 @@ export function CompetitionProvider({ children }: { children: ReactNode }) { console.log('ℹ️ 暫無競賽數據') } } else { - console.error('❌ 競賽數據載入失敗:', competitionsData.message) - // 設置空數組以避免undefined錯誤 + // 沒有競賽數據是正常情況,不報錯 setCompetitions([]) + console.log('ℹ️ 暫無競賽數據') } // 載入當前競賽 @@ -173,7 +173,9 @@ export function CompetitionProvider({ children }: { children: ReactNode }) { console.log('ℹ️ 暫無當前競賽') } } else { - console.error('❌ 當前競賽載入失敗:', currentData.message) + // 沒有當前競賽是正常情況,不報錯 + setCurrentCompetition(null) + console.log('ℹ️ 暫無當前競賽') } // 載入評審數據 @@ -191,8 +193,9 @@ export function CompetitionProvider({ children }: { children: ReactNode }) { console.log('ℹ️ 暫無評審數據') } } else { - console.error('❌ 評審數據載入失敗:', judgesData.message) + // 沒有評審數據是正常情況,不報錯 setJudges([]) + console.log('ℹ️ 暫無評審數據') } } catch (error) { console.error('❌ 載入競賽數據失敗:', error) diff --git a/lib/database-middleware.ts b/lib/database-middleware.ts new file mode 100644 index 0000000..9a53733 --- /dev/null +++ b/lib/database-middleware.ts @@ -0,0 +1,156 @@ +// ===================================================== +// 資料庫連線管理中間件 +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { db } from './database'; +import { dbMonitor } from './database-monitor'; + +// 連線池狀態追蹤 +let connectionCount = 0; +const maxConnections = 3; // 與資料庫配置保持一致 + +export function withDatabaseConnection( + handler: (...args: T) => Promise +) { + return async (...args: T): Promise => { + // 檢查連線池狀態 + if (connectionCount >= maxConnections) { + console.warn(`⚠️ 連線池已滿 (${connectionCount}/${maxConnections}),等待可用連線...`); + + // 等待一段時間後重試 + await new Promise(resolve => setTimeout(resolve, 100)); + + if (connectionCount >= maxConnections) { + return NextResponse.json( + { + success: false, + error: '資料庫連線池已滿,請稍後再試', + retryAfter: 5 // 建議5秒後重試 + }, + { status: 503 } + ); + } + } + + connectionCount++; + + try { + const result = await handler(...args); + return result; + } catch (error) { + console.error('資料庫操作錯誤:', error); + + // 根據錯誤類型返回適當的響應 + if (error instanceof Error) { + if (error.message.includes('ER_CON_COUNT_ERROR')) { + return NextResponse.json( + { + success: false, + error: '資料庫連線數已達上限,請稍後再試', + retryAfter: 10 + }, + { status: 503 } + ); + } else if (error.message.includes('ECONNRESET') || error.message.includes('PROTOCOL_CONNECTION_LOST')) { + return NextResponse.json( + { + success: false, + error: '資料庫連線中斷,請重試', + retryAfter: 3 + }, + { status: 503 } + ); + } + } + + return NextResponse.json( + { + success: false, + error: '資料庫操作失敗', + details: error instanceof Error ? error.message : '未知錯誤' + }, + { status: 500 } + ); + } finally { + connectionCount--; + } + }; +} + +// 連線池健康檢查 +export async function checkConnectionHealth(): Promise<{ + isHealthy: boolean; + connectionCount: number; + maxConnections: number; + usagePercentage: number; +}> { + try { + const stats = dbMonitor.getConnectionStats(); + + return { + isHealthy: stats.usagePercentage < 80, + connectionCount: stats.currentConnections, + maxConnections: stats.maxConnections, + usagePercentage: stats.usagePercentage + }; + } catch (error) { + console.error('連線健康檢查失敗:', error); + return { + isHealthy: false, + connectionCount: 0, + maxConnections: 0, + usagePercentage: 100 + }; + } +} + +// 自動釋放空閒連線 +export async function autoReleaseIdleConnections(): Promise { + try { + const health = await checkConnectionHealth(); + + if (health.usagePercentage > 70) { + console.log('🔄 連線使用率過高,嘗試釋放空閒連線...'); + return await dbMonitor.releaseIdleConnections(); + } + + return true; + } catch (error) { + console.error('自動釋放連線失敗:', error); + return false; + } +} + +// 連線池監控 +export function startConnectionMonitoring() { + // 每30秒檢查一次連線狀態 + dbMonitor.startMonitoring(30000); + + // 每5分鐘嘗試釋放空閒連線 + setInterval(async () => { + await autoReleaseIdleConnections(); + }, 5 * 60 * 1000); + + console.log('🔍 資料庫連線監控已啟動'); +} + +// 優雅的關閉 +export async function gracefulShutdown() { + console.log('🔄 正在優雅關閉資料庫連線...'); + + try { + dbMonitor.stopMonitoring(); + await db.close(); + console.log('✅ 資料庫連線已關閉'); + } catch (error) { + console.error('❌ 關閉資料庫連線時發生錯誤:', error); + } +} + +// 處理程序退出事件 +if (typeof process !== 'undefined') { + process.on('SIGINT', gracefulShutdown); + process.on('SIGTERM', gracefulShutdown); + process.on('exit', gracefulShutdown); +} diff --git a/lib/database-monitor.ts b/lib/database-monitor.ts new file mode 100644 index 0000000..6d6c6de --- /dev/null +++ b/lib/database-monitor.ts @@ -0,0 +1,166 @@ +// ===================================================== +// 資料庫連線監控工具 +// ===================================================== + +import { db } from './database'; + +export class DatabaseMonitor { + private static instance: DatabaseMonitor; + private connectionCount = 0; + private maxConnections = 0; + private isMonitoring = false; + private monitorInterval: NodeJS.Timeout | null = null; + + private constructor() {} + + public static getInstance(): DatabaseMonitor { + if (!DatabaseMonitor.instance) { + DatabaseMonitor.instance = new DatabaseMonitor(); + } + return DatabaseMonitor.instance; + } + + // 開始監控 + public startMonitoring(intervalMs: number = 30000) { + if (this.isMonitoring) { + console.log('⚠️ 資料庫監控已在運行中'); + return; + } + + this.isMonitoring = true; + console.log('🔍 開始監控資料庫連線狀態...'); + + this.monitorInterval = setInterval(async () => { + await this.checkConnectionStatus(); + }, intervalMs); + + // 立即執行一次檢查 + this.checkConnectionStatus(); + } + + // 停止監控 + public stopMonitoring() { + if (this.monitorInterval) { + clearInterval(this.monitorInterval); + this.monitorInterval = null; + } + this.isMonitoring = false; + console.log('⏹️ 資料庫監控已停止'); + } + + // 檢查連線狀態 + private async checkConnectionStatus() { + try { + // 檢查當前連線數 + const statusResult = await db.queryOne(` + SHOW STATUS LIKE 'Threads_connected' + `); + + const currentConnections = statusResult?.Value || 0; + this.connectionCount = parseInt(currentConnections); + + // 檢查最大連線數 + const maxConnResult = await db.queryOne(` + SHOW VARIABLES LIKE 'max_connections' + `); + + this.maxConnections = parseInt(maxConnResult?.Value || '100'); + + // 檢查連線使用率 + const usagePercentage = (this.connectionCount / this.maxConnections) * 100; + + // 記錄狀態 + console.log(`📊 資料庫連線狀態: ${this.connectionCount}/${this.maxConnections} (${usagePercentage.toFixed(1)}%)`); + + // 警告檢查 + if (usagePercentage > 80) { + console.warn(`⚠️ 資料庫連線使用率過高: ${usagePercentage.toFixed(1)}%`); + } else if (usagePercentage > 60) { + console.log(`⚠️ 資料庫連線使用率較高: ${usagePercentage.toFixed(1)}%`); + } + + // 檢查長時間運行的查詢 + await this.checkLongRunningQueries(); + + } catch (error) { + console.error('❌ 資料庫監控錯誤:', error); + } + } + + // 檢查長時間運行的查詢 + private async checkLongRunningQueries() { + try { + const longQueries = await db.query(` + SELECT + ID, + USER, + HOST, + DB, + COMMAND, + TIME, + STATE, + INFO + FROM information_schema.PROCESSLIST + WHERE TIME > 30 + AND COMMAND != 'Sleep' + ORDER BY TIME DESC + LIMIT 5 + `); + + if (longQueries.length > 0) { + console.warn(`⚠️ 發現 ${longQueries.length} 個長時間運行的查詢:`); + longQueries.forEach((query, index) => { + console.warn(` ${index + 1}. 用戶: ${query.USER}, 時間: ${query.TIME}s, 狀態: ${query.STATE}`); + if (query.INFO && query.INFO.length > 100) { + console.warn(` 查詢: ${query.INFO.substring(0, 100)}...`); + } else if (query.INFO) { + console.warn(` 查詢: ${query.INFO}`); + } + }); + } + } catch (error) { + console.error('❌ 檢查長時間查詢時發生錯誤:', error); + } + } + + // 獲取連線統計 + public getConnectionStats() { + return { + currentConnections: this.connectionCount, + maxConnections: this.maxConnections, + usagePercentage: (this.connectionCount / this.maxConnections) * 100, + isMonitoring: this.isMonitoring + }; + } + + // 強制釋放空閒連線 + public async releaseIdleConnections() { + try { + const result = await db.query(` + KILL + (SELECT GROUP_CONCAT(ID) + FROM information_schema.PROCESSLIST + WHERE COMMAND = 'Sleep' + AND TIME > 300) + `); + + console.log('🔄 已嘗試釋放空閒連線'); + return true; + } catch (error) { + console.error('❌ 釋放空閒連線失敗:', error); + return false; + } + } + + // 獲取連線池狀態 + public getPoolStatus() { + // 這裡可以添加更多連線池狀態信息 + return { + isMonitoring: this.isMonitoring, + lastCheck: new Date().toISOString() + }; + } +} + +// 導出單例實例 +export const dbMonitor = DatabaseMonitor.getInstance(); diff --git a/lib/database-service-base.ts b/lib/database-service-base.ts new file mode 100644 index 0000000..9671d6c --- /dev/null +++ b/lib/database-service-base.ts @@ -0,0 +1,308 @@ +// ===================================================== +// 資料庫服務基類 - 確保連線正確關閉 +// ===================================================== + +import { db } from './database'; +import { PoolConnection } from 'mysql2/promise'; + +export abstract class DatabaseServiceBase { + // 靜態安全查詢方法 - 確保連線關閉 + static async safeQuery(sql: string, params?: any[]): Promise { + let connection: PoolConnection | null = null; + + try { + // 獲取連線 + connection = await db.getConnection(); + + // 執行查詢 + const [rows] = await connection.execute(sql, params); + + // 立即釋放連線 + connection.release(); + connection = null; + + return rows as T[]; + } catch (error) { + // 確保連線被釋放 + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 實例安全查詢方法 - 確保連線關閉 + protected async safeQuery(sql: string, params?: any[]): Promise { + let connection: PoolConnection | null = null; + + try { + // 獲取連線 + connection = await db.getConnection(); + + // 執行查詢 + const [rows] = await connection.execute(sql, params); + + // 立即釋放連線 + connection.release(); + connection = null; + + return rows as T[]; + } catch (error) { + // 確保連線被釋放 + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 靜態安全單一查詢方法 + static async safeQueryOne(sql: string, params?: any[]): Promise { + const results = await DatabaseServiceBase.safeQuery(sql, params); + return results.length > 0 ? results[0] : null; + } + + // 實例安全單一查詢方法 + protected async safeQueryOne(sql: string, params?: any[]): Promise { + const results = await this.safeQuery(sql, params); + return results.length > 0 ? results[0] : null; + } + + // 靜態安全插入方法 + static async safeInsert(sql: string, params?: any[]): Promise { + let connection: PoolConnection | null = null; + + try { + connection = await db.getConnection(); + const [result] = await connection.execute(sql, params); + + // 立即釋放連線 + connection.release(); + connection = null; + + return result; + } catch (error) { + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 實例安全插入方法 + protected async safeInsert(sql: string, params?: any[]): Promise { + let connection: PoolConnection | null = null; + + try { + connection = await db.getConnection(); + const [result] = await connection.execute(sql, params); + + // 立即釋放連線 + connection.release(); + connection = null; + + return result; + } catch (error) { + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 靜態安全更新方法 + static async safeUpdate(sql: string, params?: any[]): Promise { + let connection: PoolConnection | null = null; + + try { + connection = await db.getConnection(); + const [result] = await connection.execute(sql, params); + + // 立即釋放連線 + connection.release(); + connection = null; + + return result; + } catch (error) { + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 實例安全更新方法 + protected async safeUpdate(sql: string, params?: any[]): Promise { + let connection: PoolConnection | null = null; + + try { + connection = await db.getConnection(); + const [result] = await connection.execute(sql, params); + + // 立即釋放連線 + connection.release(); + connection = null; + + return result; + } catch (error) { + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 靜態安全刪除方法 + static async safeDelete(sql: string, params?: any[]): Promise { + let connection: PoolConnection | null = null; + + try { + connection = await db.getConnection(); + const [result] = await connection.execute(sql, params); + + // 立即釋放連線 + connection.release(); + connection = null; + + return result; + } catch (error) { + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 實例安全刪除方法 + protected async safeDelete(sql: string, params?: any[]): Promise { + let connection: PoolConnection | null = null; + + try { + connection = await db.getConnection(); + const [result] = await connection.execute(sql, params); + + // 立即釋放連線 + connection.release(); + connection = null; + + return result; + } catch (error) { + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 事務處理方法 + protected async withTransaction( + callback: (connection: PoolConnection) => Promise + ): Promise { + let connection: PoolConnection | null = null; + + try { + connection = await db.getConnection(); + await connection.beginTransaction(); + + const result = await callback(connection); + + await connection.commit(); + + // 立即釋放連線 + connection.release(); + connection = null; + + return result; + } catch (error) { + if (connection) { + try { + await connection.rollback(); + } catch (rollbackError) { + console.error('回滾事務時發生錯誤:', rollbackError); + } + + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 批次查詢方法 - 減少連線使用 + protected async batchQuery( + queries: Array<{ sql: string; params?: any[] }> + ): Promise { + let connection: PoolConnection | null = null; + + try { + connection = await db.getConnection(); + const results: T[][] = []; + + for (const query of queries) { + const [rows] = await connection.execute(query.sql, query.params); + results.push(rows as T[]); + } + + // 立即釋放連線 + connection.release(); + connection = null; + + return results; + } catch (error) { + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } + throw error; + } + } + + // 連線健康檢查 + protected async checkConnectionHealth(): Promise { + try { + await this.safeQueryOne('SELECT 1 as health_check'); + return true; + } catch (error) { + console.error('資料庫連線健康檢查失敗:', error); + return false; + } + } +} diff --git a/lib/database.ts b/lib/database.ts index 898c89c..942d731 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -15,17 +15,20 @@ const dbConfig = { database: process.env.DB_NAME || 'db_AI_Platform', charset: 'utf8mb4', timezone: '+08:00', - acquireTimeout: 10000, // 10秒獲取連接超時 - timeout: 10000, // 10秒查詢超時 + acquireTimeout: 5000, // 5秒獲取連接超時 + timeout: 8000, // 8秒查詢超時 reconnect: true, - connectionLimit: 5, // 減少連接數,避免 Too many connections - queueLimit: 10, // 允許排隊,避免立即失敗 + connectionLimit: 3, // 進一步減少連接數,避免 Too many connections + queueLimit: 5, // 減少排隊數量 // 添加連接重試和錯誤處理配置 - retryDelay: 2000, - maxRetries: 3, + retryDelay: 1000, + maxRetries: 2, // 添加連接池配置 - idleTimeout: 60000, // 1分鐘空閒超時,快速釋放連接 - maxIdle: 5, // 最大空閒連接數 + idleTimeout: 30000, // 30秒空閒超時,快速釋放連接 + maxIdle: 2, // 最大空閒連接數 + // 添加連接生命週期管理 + maxReconnects: 3, + reconnectDelay: 2000, // 添加 SSL 配置(如果需要) ssl: false as any, }; @@ -67,9 +70,9 @@ export class Database { return await dbFailover.query(sql, params); } - let connection; + let connection: mysql.PoolConnection | null = null; let retries = 0; - const maxRetries = 3; + const maxRetries = 2; while (retries < maxRetries) { try { @@ -80,14 +83,19 @@ export class Database { console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message); if (connection) { - connection.release(); + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + connection = null; } - if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST') { + if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST' || error.code === 'ER_CON_COUNT_ERROR') { retries++; if (retries < maxRetries) { - console.log(`等待 ${2000 * retries}ms 後重試...`); - await new Promise(resolve => setTimeout(resolve, 2000 * retries)); + console.log(`等待 ${1000 * retries}ms 後重試...`); + await new Promise(resolve => setTimeout(resolve, 1000 * retries)); continue; } } @@ -95,7 +103,11 @@ export class Database { throw error; } finally { if (connection) { - connection.release(); + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } } } } @@ -138,12 +150,22 @@ export class Database { } } - const connection = await this.getConnection(); + let connection: mysql.PoolConnection | null = null; try { + connection = await this.getConnection(); const [result] = await connection.execute(sql, params); return result as mysql.ResultSetHeader; + } catch (error) { + console.error('資料庫插入錯誤:', error); + throw error; } finally { - connection.release(); + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } } } @@ -167,12 +189,22 @@ export class Database { } } - const connection = await this.getConnection(); + let connection: mysql.PoolConnection | null = null; try { + connection = await this.getConnection(); const [result] = await connection.execute(sql, params); return result as mysql.ResultSetHeader; + } catch (error) { + console.error('資料庫更新錯誤:', error); + throw error; } finally { - connection.release(); + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } } } @@ -196,12 +228,22 @@ export class Database { } } - const connection = await this.getConnection(); + let connection: mysql.PoolConnection | null = null; try { + connection = await this.getConnection(); const [result] = await connection.execute(sql, params); return result as mysql.ResultSetHeader; + } catch (error) { + console.error('資料庫刪除錯誤:', error); + throw error; } finally { - connection.release(); + if (connection) { + try { + connection.release(); + } catch (releaseError) { + console.error('釋放連線時發生錯誤:', releaseError); + } + } } } diff --git a/lib/services/database-service.ts b/lib/services/database-service.ts index c0c26c8..8976629 100644 --- a/lib/services/database-service.ts +++ b/lib/services/database-service.ts @@ -4,6 +4,7 @@ import { db } from '../database'; import { dbSync } from '../database-sync'; +import { DatabaseServiceBase } from '../database-service-base'; const { DatabaseSyncFixed } = require('../database-sync-fixed.js'); import bcrypt from 'bcryptjs'; import crypto from 'crypto'; @@ -37,7 +38,7 @@ import type { // ===================================================== // 用戶服務 // ===================================================== -export class UserService { +export class UserService extends DatabaseServiceBase { // 創建用戶 async create(userData: Omit): Promise { const sql = ` @@ -59,20 +60,20 @@ export class UserService { userData.last_login || null ]; - await db.insert(sql, params); + await DatabaseServiceBase.safeInsert(sql, params); return await this.findByEmail(userData.email) as User; } // 根據郵箱獲取用戶 async findByEmail(email: string): Promise { const sql = 'SELECT * FROM users WHERE email = ? AND status = "active"'; - return await db.queryOne(sql, [email]); + return await this.safeQueryOne(sql, [email]); } // 根據ID獲取用戶 async findById(id: string): Promise { const sql = 'SELECT * FROM users WHERE id = ? AND status = "active"'; - return await db.queryOne(sql, [id]); + return await this.safeQueryOne(sql, [id]); } // 更新用戶 @@ -82,7 +83,7 @@ export class UserService { const values = fields.map(field => (updates as any)[field]); const sql = `UPDATE users SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; - const result = await db.update(sql, [...values, id]); + const result = await DatabaseServiceBase.safeUpdate(sql, [...values, id]); if (result.affectedRows > 0) { return await this.findById(id); @@ -93,7 +94,7 @@ export class UserService { // 更新最後登入時間 async updateLastLogin(id: string): Promise { const sql = 'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?'; - const result = await db.update(sql, [id]); + const result = await DatabaseServiceBase.safeUpdate(sql, [id]); return result.affectedRows > 0; } @@ -112,7 +113,7 @@ export class UserService { page?: number; limit?: number; } = {}): Promise<{ users: User[]; total: number }> { - const { search, department, role, status, page = 1, limit = 10 } = filters; + const { search, department, role, status, page = 1, limit = 5 } = filters; // 構建查詢條件 let whereConditions: string[] = []; @@ -150,7 +151,7 @@ export class UserService { // 獲取總數 const countSql = `SELECT COUNT(*) as total FROM users ${whereClause}`; - const countResult = await this.query(countSql, params); + const countResult = await DatabaseServiceBase.safeQuery(countSql, params); const total = countResult[0]?.total || 0; // 獲取用戶列表 @@ -164,7 +165,7 @@ export class UserService { ORDER BY created_at DESC LIMIT ${offset}, ${limit} `; - const users = await this.query(usersSql, params); + const users = await this.safeQuery(usersSql, params); return { users, total }; } @@ -190,7 +191,7 @@ export class UserService { COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_this_month FROM users `; - const result = await this.query(sql); + const result = await DatabaseServiceBase.safeQuery(sql); const stats = result[0] || {}; return { @@ -823,7 +824,7 @@ export class UserService { // ===================================================== // 評審服務 // ===================================================== -export class JudgeService { +export class JudgeService extends DatabaseServiceBase { // 安全解析 expertise 字段 private static parseExpertise(expertise: any): string[] { if (!expertise) return []; @@ -847,7 +848,7 @@ export class JudgeService { } // 創建評審 - static async createJudge(judgeData: Omit): Promise { + async createJudge(judgeData: Omit): Promise { const sql = ` INSERT INTO judges (id, name, title, department, expertise, avatar, is_active) VALUES (UUID(), ?, ?, ?, ?, ?, ?) @@ -861,8 +862,8 @@ export class JudgeService { judgeData.is_active ]; - await db.insert(sql, params); - return await this.getJudgeByName(judgeData.name) as Judge; + await DatabaseServiceBase.safeInsert(sql, params); + return await JudgeService.getJudgeByName(judgeData.name) as Judge; } // 根據姓名獲取評審 @@ -916,7 +917,7 @@ export class JudgeService { console.log('執行 SQL:', sql); console.log('參數:', [...values, id]); - const result = await db.update(sql, [...values, id]); + const result = await DatabaseServiceBase.safeUpdate(sql, [...values, id]); console.log('更新結果:', result); return result.affectedRows > 0; } @@ -925,7 +926,7 @@ export class JudgeService { static async deleteJudge(id: string): Promise { try { const sql = 'DELETE FROM judges WHERE id = ?'; - const result = await db.delete(sql, [id]); + const result = await DatabaseServiceBase.safeDelete(sql, [id]); return result.affectedRows > 0; } catch (error) { console.error('刪除評審錯誤:', error); @@ -967,7 +968,7 @@ export class JudgeService { sql += ' ORDER BY a.name'; - const results = await db.query(sql, params); + const results = await DatabaseServiceBase.safeQuery(sql, params); return results; } } @@ -975,7 +976,7 @@ export class JudgeService { // ===================================================== // 團隊服務 // ===================================================== -export class TeamService { +export class TeamService extends DatabaseServiceBase { // 創建團隊 static async createTeam(teamData: { name: string; @@ -998,7 +999,7 @@ export class TeamService { teamData.description || null ]; - const result = await db.insert(sql, params); + const result = await DatabaseServiceBase.safeInsert(sql, params); console.log('團隊創建結果:', result); return id; } @@ -1017,7 +1018,7 @@ export class TeamService { GROUP BY t.id ORDER BY t.created_at DESC `; - const results = await db.query(sql); + const results = await DatabaseServiceBase.safeQuery(sql); return results; } @@ -1031,7 +1032,7 @@ export class TeamService { LEFT JOIN users u ON t.leader_id = u.id WHERE t.id = ? AND t.is_active = TRUE `; - const results = await db.query(sql, [id]); + const results = await DatabaseServiceBase.safeQuery(sql, [id]); return results.length > 0 ? results[0] : null; } @@ -1045,7 +1046,7 @@ export class TeamService { LEFT JOIN users u ON t.leader_id = u.id WHERE t.name = ? AND t.is_active = TRUE `; - const results = await db.query(sql, [name]); + const results = await DatabaseServiceBase.safeQuery(sql, [name]); return results.length > 0 ? results[0] : null; } @@ -1072,7 +1073,7 @@ export class TeamService { const sql = `UPDATE teams SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; try { - const result = await db.update(sql, values); + const result = await DatabaseServiceBase.safeUpdate(sql, values); console.log('團隊更新結果:', result); return result.affectedRows > 0; } catch (error) { @@ -1085,7 +1086,7 @@ export class TeamService { static async deleteTeam(id: string): Promise { try { const sql = 'UPDATE teams SET is_active = FALSE, updated_at = CURRENT_TIMESTAMP WHERE id = ?'; - const result = await db.update(sql, [id]); + const result = await DatabaseServiceBase.safeUpdate(sql, [id]); return result.affectedRows > 0; } catch (error) { console.error('刪除團隊錯誤:', error); @@ -1097,7 +1098,7 @@ export class TeamService { static async hardDeleteTeam(id: string): Promise { try { const sql = 'DELETE FROM teams WHERE id = ?'; - const result = await db.delete(sql, [id]); + const result = await DatabaseServiceBase.safeDelete(sql, [id]); return result.affectedRows > 0; } catch (error) { console.error('硬刪除團隊錯誤:', error); @@ -1115,7 +1116,7 @@ export class TeamService { const params = [id, teamId, userId, role]; try { - const result = await db.insert(sql, params); + const result = await DatabaseServiceBase.safeInsert(sql, params); console.log('團隊成員添加結果:', result); return result.affectedRows > 0; } catch (error) { @@ -1133,7 +1134,7 @@ export class TeamService { WHERE tm.team_id = ? AND u.status = 'active' ORDER BY tm.joined_at ASC `; - const results = await db.query(sql, [teamId]); + const results = await DatabaseServiceBase.safeQuery(sql, [teamId]); return results; } @@ -1141,7 +1142,7 @@ export class TeamService { static async removeTeamMember(teamId: string, userId: string): Promise { try { const sql = 'DELETE FROM team_members WHERE team_id = ? AND user_id = ?'; - const result = await db.delete(sql, [teamId, userId]); + const result = await DatabaseServiceBase.safeDelete(sql, [teamId, userId]); return result.affectedRows > 0; } catch (error) { console.error('移除團隊成員錯誤:', error); @@ -1153,7 +1154,7 @@ export class TeamService { static async updateTeamMemberRole(teamId: string, userId: string, role: string): Promise { try { const sql = 'UPDATE team_members SET role = ? WHERE team_id = ? AND user_id = ?'; - const result = await db.update(sql, [role, teamId, userId]); + const result = await DatabaseServiceBase.safeUpdate(sql, [role, teamId, userId]); return result.affectedRows > 0; } catch (error) { console.error('更新團隊成員角色錯誤:', error); @@ -1165,7 +1166,7 @@ export class TeamService { static async bindAppToTeam(teamId: string, appId: string): Promise { try { const sql = 'UPDATE apps SET team_id = ? WHERE id = ? AND is_active = TRUE'; - const result = await db.update(sql, [teamId, appId]); + const result = await DatabaseServiceBase.safeUpdate(sql, [teamId, appId]); return result.affectedRows > 0; } catch (error) { console.error('綁定應用到團隊錯誤:', error); @@ -1177,7 +1178,7 @@ export class TeamService { static async unbindAppFromTeam(appId: string): Promise { try { const sql = 'UPDATE apps SET team_id = NULL WHERE id = ?'; - const result = await db.update(sql, [appId]); + const result = await DatabaseServiceBase.safeUpdate(sql, [appId]); return result.affectedRows > 0; } catch (error) { console.error('解除應用與團隊綁定錯誤:', error); @@ -1200,7 +1201,7 @@ export class TeamService { `; console.log('📝 getTeamApps SQL:', sql); console.log('📝 getTeamApps 參數:', [teamId]); - const results = await db.query(sql, [teamId]); + const results = await DatabaseServiceBase.safeQuery(sql, [teamId]); console.log('📊 getTeamApps 結果:', results.length, '個應用'); return results; } @@ -1220,7 +1221,7 @@ export class TeamService { GROUP BY t.id ) as team_stats `; - const results = await db.query(sql); + const results = await DatabaseServiceBase.safeQuery(sql); return results[0] || { totalTeams: 0, activeTeams: 0, inactiveTeams: 0, avgMembersPerTeam: 0 }; } } @@ -1228,7 +1229,7 @@ export class TeamService { // ===================================================== // 競賽服務 // ===================================================== -export class CompetitionService { +export class CompetitionService extends DatabaseServiceBase { // 創建競賽 static async createCompetition(competitionData: Omit): Promise { // 使用智能雙寫,每個資料庫生成自己的 ID @@ -1409,7 +1410,7 @@ export class CompetitionService { // 清除當前競賽 static async clearCurrentCompetition(): Promise { try { - await db.update('UPDATE competitions SET is_current = FALSE'); + await DatabaseServiceBase.safeUpdate('UPDATE competitions SET is_current = FALSE'); return true; } catch (error) { @@ -1487,7 +1488,7 @@ export class CompetitionService { WHERE cj.competition_id = ? AND j.is_active = TRUE ORDER BY cj.assigned_at ASC `; - return await db.query(sql, [competitionId]); + return await DatabaseServiceBase.safeQuery(sql, [competitionId]); } // 為競賽添加評審 @@ -1496,7 +1497,7 @@ export class CompetitionService { const dbSyncFixed = new DatabaseSyncFixed(); // 先刪除現有的評審關聯 - await db.delete('DELETE FROM competition_judges WHERE competition_id = ?', [competitionId]); + await DatabaseServiceBase.safeDelete('DELETE FROM competition_judges WHERE competition_id = ?', [competitionId]); // 添加新的評審關聯 if (judgeIds.length > 0) { @@ -1534,7 +1535,7 @@ export class CompetitionService { // 從競賽中移除評審 static async removeCompetitionJudge(competitionId: string, judgeId: string): Promise { const sql = 'DELETE FROM competition_judges WHERE competition_id = ? AND judge_id = ?'; - const result = await db.delete(sql, [competitionId, judgeId]); + const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, judgeId]); return result.affectedRows > 0; } @@ -1548,7 +1549,7 @@ export class CompetitionService { WHERE ct.competition_id = ? AND t.is_active = TRUE ORDER BY ct.registered_at ASC `; - return await db.query(sql, [competitionId]); + return await DatabaseServiceBase.safeQuery(sql, [competitionId]); } // 獲取競賽的應用列表 @@ -1579,7 +1580,7 @@ export class CompetitionService { WHERE a.team_id IN (${placeholders}) AND a.is_active = TRUE ORDER BY a.created_at ASC `; - apps = await db.query(sql, teamIds); + apps = await DatabaseServiceBase.safeQuery(sql, teamIds); } } } else { @@ -1592,7 +1593,7 @@ export class CompetitionService { WHERE ca.competition_id = ? AND a.is_active = TRUE ORDER BY ca.submitted_at ASC `; - apps = await db.query(sql, [competitionId]); + apps = await DatabaseServiceBase.safeQuery(sql, [competitionId]); } return apps; @@ -1604,7 +1605,7 @@ export class CompetitionService { const dbSyncFixed = new DatabaseSyncFixed(); // 先刪除現有的團隊關聯 - await db.delete('DELETE FROM competition_teams WHERE competition_id = ?', [competitionId]); + await DatabaseServiceBase.safeDelete('DELETE FROM competition_teams WHERE competition_id = ?', [competitionId]); // 添加新的團隊關聯 if (teamIds.length > 0) { @@ -1642,7 +1643,7 @@ export class CompetitionService { // 從競賽中移除團隊 static async removeCompetitionTeam(competitionId: string, teamId: string): Promise { const sql = 'DELETE FROM competition_teams WHERE competition_id = ? AND team_id = ?'; - const result = await db.delete(sql, [competitionId, teamId]); + const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, teamId]); return result.affectedRows > 0; } @@ -1652,7 +1653,7 @@ export class CompetitionService { const dbSyncFixed = new DatabaseSyncFixed(); // 先刪除現有的應用關聯 - await db.delete('DELETE FROM competition_apps WHERE competition_id = ?', [competitionId]); + await DatabaseServiceBase.safeDelete('DELETE FROM competition_apps WHERE competition_id = ?', [competitionId]); // 添加新的應用關聯 if (appIds.length > 0) { @@ -1690,7 +1691,7 @@ export class CompetitionService { // 從競賽中移除應用 static async removeCompetitionApp(competitionId: string, appId: string): Promise { const sql = 'DELETE FROM competition_apps WHERE competition_id = ? AND app_id = ?'; - const result = await db.delete(sql, [competitionId, appId]); + const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, appId]); return result.affectedRows > 0; } @@ -1702,7 +1703,7 @@ export class CompetitionService { WHERE cat.competition_id = ? ORDER BY cat.order_index ASC, cat.created_at ASC `; - return await db.query(sql, [competitionId]); + return await DatabaseServiceBase.safeQuery(sql, [competitionId]); } // 為競賽添加獎項類型 @@ -1711,7 +1712,7 @@ export class CompetitionService { const dbSyncFixed = new DatabaseSyncFixed(); // 先刪除現有的獎項類型 - await db.delete('DELETE FROM competition_award_types WHERE competition_id = ?', [competitionId]); + await DatabaseServiceBase.safeDelete('DELETE FROM competition_award_types WHERE competition_id = ?', [competitionId]); // 添加新的獎項類型 if (awardTypes.length > 0) { @@ -1756,7 +1757,7 @@ export class CompetitionService { // 從競賽中移除獎項類型 static async removeCompetitionAwardType(competitionId: string, awardTypeId: string): Promise { const sql = 'DELETE FROM competition_award_types WHERE competition_id = ? AND id = ?'; - const result = await db.delete(sql, [competitionId, awardTypeId]); + const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, awardTypeId]); return result.affectedRows > 0; } @@ -1767,7 +1768,7 @@ export class CompetitionService { const dbSyncFixed = new DatabaseSyncFixed(); // 先刪除現有的評分規則 - await db.delete('DELETE FROM competition_rules WHERE competition_id = ?', [competitionId]); + await DatabaseServiceBase.safeDelete('DELETE FROM competition_rules WHERE competition_id = ?', [competitionId]); // 添加新的評分規則 if (rules.length > 0) { @@ -1811,7 +1812,7 @@ export class CompetitionService { // 從競賽中移除評分規則 static async removeCompetitionRule(competitionId: string, ruleId: string): Promise { const sql = 'DELETE FROM competition_rules WHERE competition_id = ? AND id = ?'; - const result = await db.delete(sql, [competitionId, ruleId]); + const result = await DatabaseServiceBase.safeDelete(sql, [competitionId, ruleId]); return result.affectedRows > 0; } @@ -1883,7 +1884,7 @@ export class CompetitionService { // ===================================================== // 應用服務 // ===================================================== -export class AppService { +export class AppService extends DatabaseServiceBase { // 創建應用 async createApp(appData: { name: string; @@ -3449,7 +3450,7 @@ export class AppService { // ===================================================== // 評分服務 // ===================================================== -export class ScoringService { +export class ScoringService extends DatabaseServiceBase { // 獲取競賽規則 static async getCompetitionRules(competitionId: string): Promise { console.log('🔍 獲取競賽規則,competitionId:', competitionId); @@ -3462,7 +3463,7 @@ export class ScoringService { `; try { - const result = await db.query(sql, [competitionId]); + const result = await DatabaseServiceBase.safeQuery(sql, [competitionId]); console.log('🔍 競賽規則查詢結果:', result); return result; } catch (error) { @@ -3478,7 +3479,7 @@ export class ScoringService { FROM competition_apps ca WHERE ca.app_id = ? `; - const result = await db.query(sql, [appId]); + const result = await DatabaseServiceBase.safeQuery(sql, [appId]); return result.length > 0 ? result[0].competition_id : null; } @@ -3513,7 +3514,7 @@ export class ScoringService { console.log('🔍 編輯模式,使用記錄ID:', finalJudgeScoreId); } else { // 新增模式:檢查是否已存在評分記錄 - const existingScore = await db.query( + const existingScore = await DatabaseServiceBase.safeQuery( 'SELECT id FROM judge_scores WHERE judge_id = ? AND app_id = ?', [judge_id, app_id] ); @@ -3530,14 +3531,14 @@ export class ScoringService { } // 檢查記錄是否存在,決定是更新還是插入 - const existingRecord = await db.query( + const existingRecord = await DatabaseServiceBase.safeQuery( 'SELECT id FROM judge_scores WHERE id = ?', [finalJudgeScoreId] ); if (existingRecord.length > 0) { // 更新現有記錄 - await db.update(` + await DatabaseServiceBase.safeUpdate(` UPDATE judge_scores SET total_score = ?, comments = ?, submitted_at = CURRENT_TIMESTAMP WHERE id = ? @@ -3545,7 +3546,7 @@ export class ScoringService { console.log('🔍 更新現有評分記錄'); } else { // 檢查是否已有相同 judge_id + app_id + competition_id 的記錄 - const duplicateRecord = await db.query( + const duplicateRecord = await DatabaseServiceBase.safeQuery( 'SELECT id FROM judge_scores WHERE judge_id = ? AND app_id = ? AND competition_id = ?', [judge_id, app_id, competition_id] ); @@ -3553,7 +3554,7 @@ export class ScoringService { if (duplicateRecord.length > 0) { // 更新現有記錄 finalJudgeScoreId = duplicateRecord[0].id; - await db.update(` + await DatabaseServiceBase.safeUpdate(` UPDATE judge_scores SET total_score = ?, comments = ?, submitted_at = CURRENT_TIMESTAMP WHERE id = ? @@ -3561,7 +3562,7 @@ export class ScoringService { console.log('🔍 更新重複的評分記錄'); } else { // 創建新記錄 - await db.insert(` + await DatabaseServiceBase.safeInsert(` INSERT INTO judge_scores (id, judge_id, app_id, competition_id, total_score, comments) VALUES (?, ?, ?, ?, ?, ?) `, [ @@ -3581,7 +3582,7 @@ export class ScoringService { console.log('🔍 競賽規則:', rules); // 3. 刪除現有的評分詳情 - await db.delete( + await DatabaseServiceBase.safeDelete( 'DELETE FROM judge_score_details WHERE judge_score_id = ?', [finalJudgeScoreId] ); @@ -3605,7 +3606,7 @@ export class ScoringService { weight: rule.weight }); - await db.insert(` + await DatabaseServiceBase.safeInsert(` INSERT INTO judge_score_details (id, judge_score_id, rule_id, rule_name, score, weight) VALUES (?, ?, ?, ?, ?, ?) `, [ @@ -3646,7 +3647,7 @@ export class ScoringService { GROUP BY js.id `; - const result = await db.query(sql, [judgeScoreId]); + const result = await DatabaseServiceBase.safeQuery(sql, [judgeScoreId]); return result.length > 0 ? result[0] : null; } @@ -3660,7 +3661,7 @@ export class ScoringService { console.log('🔍 獲取競賽評分進度,competitionId:', competitionId); // 獲取競賽的評審數量 - const judgesResult = await db.query(` + const judgesResult = await DatabaseServiceBase.safeQuery(` SELECT COUNT(DISTINCT cj.judge_id) as judge_count FROM competition_judges cj WHERE cj.competition_id = ? @@ -3670,7 +3671,7 @@ export class ScoringService { console.log('🔍 評審數量:', judgeCount); // 獲取競賽的參賽APP數量 - const appsResult = await db.query(` + const appsResult = await DatabaseServiceBase.safeQuery(` SELECT COUNT(DISTINCT ca.app_id) as app_count FROM competition_apps ca WHERE ca.competition_id = ? @@ -3685,20 +3686,20 @@ export class ScoringService { if (judgeCount === 0) { // 嘗試從 judges 表獲取所有評審 - const allJudgesResult = await db.query('SELECT COUNT(*) as judge_count FROM judges'); + const allJudgesResult = await DatabaseServiceBase.safeQuery('SELECT COUNT(*) as judge_count FROM judges'); finalJudgeCount = allJudgesResult[0]?.judge_count || 0; console.log('🔍 使用所有評審數量:', finalJudgeCount); } if (appCount === 0) { // 嘗試從 apps 表獲取所有APP - const allAppsResult = await db.query('SELECT COUNT(*) as app_count FROM apps'); + const allAppsResult = await DatabaseServiceBase.safeQuery('SELECT COUNT(*) as app_count FROM apps'); finalAppCount = allAppsResult[0]?.app_count || 0; console.log('🔍 使用所有APP數量:', finalAppCount); } // 獲取已完成的評分數量 - const completedResult = await db.query(` + const completedResult = await DatabaseServiceBase.safeQuery(` SELECT COUNT(*) as completed_count FROM judge_scores js WHERE js.competition_id = ? @@ -3755,7 +3756,7 @@ export class ScoringService { console.log('🔍 獲取評分完成度匯總,competitionId:', competitionId); // 獲取競賽的評審列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有評審 - let judgesResult = await db.query(` + let judgesResult = await DatabaseServiceBase.safeQuery(` SELECT j.id, j.name FROM judges j LEFT JOIN competition_judges cj ON j.id = cj.judge_id @@ -3765,13 +3766,13 @@ export class ScoringService { // 如果沒有關聯的評審,獲取所有評審 if (judgesResult.length === 0) { - judgesResult = await db.query(` + judgesResult = await DatabaseServiceBase.safeQuery(` SELECT id, name FROM judges ORDER BY name `); } // 獲取競賽的APP列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有APP - let appsResult = await db.query(` + let appsResult = await DatabaseServiceBase.safeQuery(` SELECT a.id, a.name, t.name as team_name FROM apps a LEFT JOIN competition_apps ca ON a.id = ca.app_id @@ -3782,7 +3783,7 @@ export class ScoringService { // 如果沒有關聯的APP,獲取所有APP if (appsResult.length === 0) { - appsResult = await db.query(` + appsResult = await DatabaseServiceBase.safeQuery(` SELECT a.id, a.name, t.name as team_name FROM apps a LEFT JOIN teams t ON a.team_id = t.id @@ -3791,7 +3792,7 @@ export class ScoringService { } // 獲取已完成的評分記錄 - const scoresResult = await db.query(` + const scoresResult = await DatabaseServiceBase.safeQuery(` SELECT js.judge_id, js.app_id, js.total_score, js.submitted_at, j.name as judge_name, a.name as app_name FROM judge_scores js @@ -3928,7 +3929,7 @@ export class ScoringService { scoreData.comments || null ]; - await db.insert(sql, params); + await DatabaseServiceBase.safeInsert(sql, params); return await this.getProposalScore(scoreData.judge_id, scoreData.proposal_id) as ProposalJudgeScore; } @@ -4028,7 +4029,7 @@ export class ScoringService { ORDER BY submitted_at DESC `; - return await db.query(sql, [competitionId, competitionId, competitionId]); + return await DatabaseServiceBase.safeQuery(sql, [competitionId, competitionId, competitionId]); } // 提交團隊評分(使用應用評分表,但標記為團隊類型) @@ -4062,7 +4063,7 @@ export class ScoringService { scoreData.comments || null ]; - await db.insert(sql, params); + await DatabaseServiceBase.safeInsert(sql, params); return await this.getAppScore(scoreData.judge_id, virtualAppId) as AppJudgeScore; } @@ -4180,14 +4181,14 @@ export class ScoringService { sql += ' ORDER BY submitted_at DESC'; - return await db.query(sql, params); + return await DatabaseServiceBase.safeQuery(sql, params); } // 刪除評分記錄 static async deleteScore(scoreId: string, scoreType: 'app' | 'proposal'): Promise { const tableName = scoreType === 'app' ? 'app_judge_scores' : 'proposal_judge_scores'; const sql = `DELETE FROM ${tableName} WHERE id = ?`; - const result = await db.delete(sql, [scoreId]); + const result = await DatabaseServiceBase.safeDelete(sql, [scoreId]); return result.affectedRows > 0; } @@ -4206,7 +4207,7 @@ export class ScoringService { const values = fields.map(field => (updates as any)[field]); const sql = `UPDATE app_judge_scores SET ${setClause}, submitted_at = CURRENT_TIMESTAMP WHERE id = ?`; - const result = await db.update(sql, [...values, scoreId]); + const result = await DatabaseServiceBase.safeUpdate(sql, [...values, scoreId]); return result.affectedRows > 0; } @@ -4225,7 +4226,7 @@ export class ScoringService { const values = fields.map(field => (updates as any)[field]); const sql = `UPDATE proposal_judge_scores SET ${setClause}, submitted_at = CURRENT_TIMESTAMP WHERE id = ?`; - const result = await db.update(sql, [...values, scoreId]); + const result = await DatabaseServiceBase.safeUpdate(sql, [...values, scoreId]); return result.affectedRows > 0; } } @@ -4233,7 +4234,7 @@ export class ScoringService { // ===================================================== // 獎項服務 // ===================================================== -export class AwardService { +export class AwardService extends DatabaseServiceBase { // 創建獎項 static async createAward(awardData: Omit): Promise { const sql = ` @@ -4261,7 +4262,7 @@ export class AwardService { awardData.category ]; - await db.insert(sql, params); + await DatabaseServiceBase.safeInsert(sql, params); return await this.getAwardByCompetitionAndCreator(awardData.competition_id, awardData.creator) as Award; } @@ -4287,7 +4288,7 @@ export class AwardService { // ===================================================== // 系統設定服務 // ===================================================== -export class SystemSettingService { +export class SystemSettingService extends DatabaseServiceBase { // 獲取設定值 static async getSetting(key: string): Promise { const sql = 'SELECT value FROM system_settings WHERE `key` = ?'; @@ -4309,7 +4310,7 @@ export class SystemSettingService { `; const params = [key, value, description || null, category, isPublic]; - const result = await db.insert(sql, params); + const result = await DatabaseServiceBase.safeInsert(sql, params); return result.affectedRows > 0; }