249 lines
6.6 KiB
TypeScript
249 lines
6.6 KiB
TypeScript
// 錯誤類型定義
|
|
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',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
translation: '你好,你還好嗎?'
|
|
}
|
|
] as ExtendedFlashcard[]
|
|
}
|
|
|
|
// 檢查是否需要使用降級模式
|
|
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)
|
|
}
|
|
}
|
|
} |