dramaling-vocab-learning/frontend/lib/errors/errorHandler.ts

249 lines
6.6 KiB
TypeScript

import { ExtendedFlashcard } from '@/store/useReviewSessionStore'
// 錯誤類型定義
export enum ErrorType {
NETWORK_ERROR = 'NETWORK_ERROR',
API_ERROR = 'API_ERROR',
VALIDATION_ERROR = 'VALIDATION_ERROR',
AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR',
UNKNOWN_ERROR = 'UNKNOWN_ERROR'
}
export interface AppError {
type: ErrorType
message: string
details?: any
timestamp: Date
context?: string
}
// 錯誤處理器
export class ErrorHandler {
private static errorQueue: AppError[] = []
private static maxQueueSize = 50
// 記錄錯誤
static logError(error: AppError) {
console.error(`[${error.type}] ${error.message}`, error.details)
// 添加到錯誤隊列
this.errorQueue.unshift(error)
if (this.errorQueue.length > this.maxQueueSize) {
this.errorQueue.pop()
}
}
// 創建錯誤
static createError(
type: ErrorType,
message: string,
details?: any,
context?: string
): AppError {
const error: AppError = {
type,
message,
details,
context,
timestamp: new Date()
}
this.logError(error)
return error
}
// 處理 API 錯誤
static handleApiError(error: any, context?: string): AppError {
if (error?.response?.status === 401) {
return this.createError(
ErrorType.AUTHENTICATION_ERROR,
'認證失效,請重新登入',
error,
context
)
}
if (error?.response?.status >= 500) {
return this.createError(
ErrorType.API_ERROR,
'伺服器錯誤,請稍後再試',
error,
context
)
}
if (error?.code === 'NETWORK_ERROR' || !error?.response) {
return this.createError(
ErrorType.NETWORK_ERROR,
'網路連線錯誤,請檢查網路狀態',
error,
context
)
}
return this.createError(
ErrorType.API_ERROR,
error?.response?.data?.message || '請求失敗',
error,
context
)
}
// 處理驗證錯誤
static handleValidationError(message: string, details?: any, context?: string): AppError {
return this.createError(ErrorType.VALIDATION_ERROR, message, details, context)
}
// 獲取用戶友好的錯誤訊息
static getUserFriendlyMessage(error: AppError): string {
switch (error.type) {
case ErrorType.NETWORK_ERROR:
return '網路連線有問題,請檢查網路後重試'
case ErrorType.AUTHENTICATION_ERROR:
return '登入狀態已過期,請重新登入'
case ErrorType.API_ERROR:
return error.message || '伺服器暫時無法回應,請稍後再試'
case ErrorType.VALIDATION_ERROR:
return error.message || '輸入資料有誤,請檢查後重試'
default:
return '發生未知錯誤,請聯繫技術支援'
}
}
// 獲取錯誤歷史
static getErrorHistory(): AppError[] {
return [...this.errorQueue]
}
// 清除錯誤歷史
static clearErrorHistory() {
this.errorQueue = []
}
// 判斷是否可以重試
static canRetry(error: AppError): boolean {
return [ErrorType.NETWORK_ERROR, ErrorType.API_ERROR].includes(error.type)
}
// 判斷是否需要重新登入
static needsReauth(error: AppError): boolean {
return error.type === ErrorType.AUTHENTICATION_ERROR
}
}
// 重試邏輯
export class RetryHandler {
private static retryConfig = {
maxRetries: 3,
baseDelay: 1000, // 1秒
maxDelay: 5000 // 5秒
}
// 執行帶重試的操作
static async withRetry<T>(
operation: () => Promise<T>,
context?: string,
maxRetries?: number
): Promise<T> {
const attempts = maxRetries || this.retryConfig.maxRetries
let lastError: any
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
return await operation()
} catch (error) {
lastError = error
console.warn(`[Retry ${attempt}/${attempts}] Operation failed:`, error)
// 如果是最後一次嘗試,拋出錯誤
if (attempt === attempts) {
throw ErrorHandler.handleApiError(error, context)
}
// 計算延遲時間 (指數退避)
const delay = Math.min(
this.retryConfig.baseDelay * Math.pow(2, attempt - 1),
this.retryConfig.maxDelay
)
console.log(`等待 ${delay}ms 後重試...`)
await new Promise(resolve => setTimeout(resolve, delay))
}
}
throw ErrorHandler.handleApiError(lastError, context)
}
// 更新重試配置
static updateConfig(config: Partial<typeof RetryHandler.retryConfig>) {
this.retryConfig = { ...this.retryConfig, ...config }
}
}
// 降級數據服務
export class FallbackService {
// 緊急降級數據
static getEmergencyFlashcards(): ExtendedFlashcard[] {
return [
{
id: 'emergency-1',
word: 'hello',
definition: '你好,哈囉',
example: 'Hello, how are you?',
difficultyLevel: 'A1',
translation: '你好,你還好嗎?'
}
]
}
// 檢查是否需要使用降級模式
static shouldUseFallback(errorCount: number, networkStatus: boolean): boolean {
return errorCount >= 3 || !networkStatus
}
// 本地儲存學習進度
static saveProgressToLocal(progress: {
currentCardId?: string
completedTests: any[]
score: { correct: number; total: number }
}) {
try {
const timestamp = new Date().toISOString()
const progressData = {
...progress,
timestamp,
version: '1.0'
}
localStorage.setItem('learn_progress_backup', JSON.stringify(progressData))
console.log('💾 學習進度已備份到本地')
} catch (error) {
console.error('本地進度備份失敗:', error)
}
}
// 從本地恢復學習進度
static loadProgressFromLocal(): any | null {
try {
const saved = localStorage.getItem('learn_progress_backup')
if (saved) {
const progress = JSON.parse(saved)
console.log('📂 從本地恢復學習進度:', progress)
return progress
}
} catch (error) {
console.error('本地進度恢復失敗:', error)
}
return null
}
// 清除本地進度
static clearLocalProgress() {
try {
localStorage.removeItem('learn_progress_backup')
console.log('🗑️ 本地進度備份已清除')
} catch (error) {
console.error('清除本地進度失敗:', error)
}
}
}