dramaling-vocab-learning/note/智能複習/智能複習系統-技術實作架構規格書.md

38 KiB

智能複習系統 - 技術實作架構規格書 (TAS)

目標讀者: 全端開發工程師、系統架構師、技術主管 版本: 1.0 日期: 2025-09-29 實施狀態: 🎯 架構設計階段


🏗️ 整體系統架構設計

三層架構模式

┌─────────────────────────────────────────────────────────────────┐
│                        🌐 前端層 (React)                        │
├─────────────────┬─────────────────┬─────────────────┬──────────────┤
│   複習流程組件   │   測驗類型組件   │    狀態管理      │   API整合層   │
│                │                │                │             │
│ SmartReview     │ FlipMemoryTest │ ReviewContext   │ ReviewAPI   │
│ Container       │ VocabChoice    │ QueueManager    │ FlashcardAPI│
│ TestQueue       │ SentenceFill   │ StateRecovery   │ ReviewAPI    │
│ Progress        │ SentenceReorder│ Navigation      │             │
│ Tracker         │ ListeningTest  │ Controller      │             │
│                │ SpeakingTest   │                │             │
└─────────────────┴─────────────────┴─────────────────┴──────────────┘
                                    │
┌─────────────────────────────────────────────────────────────────┐
│                        🔗 API 契約層                            │
├─────────────────────────────────────────────────────────────────┤
│  統一API規範 + 錯誤處理 + 認證授權 + 快取策略                    │
└─────────────────────────────────────────────────────────────────┘
                                    │
┌─────────────────────────────────────────────────────────────────┐
│                      🏛️ 後端層 (.NET Core)                      │
├─────────────────┬─────────────────┬─────────────────┬──────────────┤
│   控制器層       │    服務層        │    資料層        │   基礎設施    │
│                │                │                │             │
│ ReviewController │ SpacedRepetition│ ReviewRecord     │ JWTAuth     │
│ FlashcardCtrl   │ ReviewSelector  │ Flashcard       │ Cache       │
│ StatsController │ QuestionGen     │ DailyStats      │ Logging     │
│                │ BlankGeneration │ OptionsVocab    │ Monitoring  │
│                │ CEFRMapping     │                │             │
└─────────────────┴─────────────────┴─────────────────┴──────────────┘
                                    │
┌─────────────────────────────────────────────────────────────────┐
│                     💾 資料庫層 (SQLite/PostgreSQL)              │
│                   智能索引 + 關聯關係 + 效能優化                  │
└─────────────────────────────────────────────────────────────────┘

🎯 前端組件架構設計

核心組件層次結構

// 最外層容器
SmartReviewContainer
├── ReviewContextProvider           // 全域狀態管理
├── TestQueueManager               // 測驗隊列管理
├── ProgressTracker                // 進度追蹤和可視化
├── NavigationController           // 導航邏輯控制
└── TestRenderer                   // 動態測驗渲染器
    ├── FlipMemoryTest             // 翻卡記憶測驗
    ├── VocabChoiceTest            // 詞彙選擇測驗
    ├── SentenceFillTest           // 例句填空測驗
    ├── SentenceReorderTest        // 例句重組測驗
    ├── VocabListeningTest         // 詞彙聽力測驗
    ├── SentenceListeningTest      // 例句聽力測驗
    └── SentenceSpeakingTest       // 例句口說測驗

狀態管理架構

// 🧠 全域複習狀態 Context
interface ReviewContextState {
  // 基礎數據
  dueCards: Flashcard[]
  completedTests: CompletedTest[]

  // 隊列管理
  testQueue: TestItem[]
  currentTestIndex: number
  skippedTests: TestItem[]

  // 用戶狀態
  userCEFRLevel: string
  learningSession: LearningSession

  // UI 狀態
  isAnswered: boolean
  showResult: boolean
  navigationState: 'skip' | 'continue'
}

// 🎯 隊列管理邏輯
class TestQueueManager {
  // 智能排序邏輯
  prioritizeTests(tests: TestItem[]): TestItem[] {
    return tests.sort((a, b) => {
      // 1. 新測驗 (最高優先級)
      if (a.status === 'new' && b.status !== 'new') return -1
      if (b.status === 'new' && a.status !== 'new') return 1

      // 2. 答錯測驗 (中等優先級)
      if (a.status === 'incorrect' && b.status === 'skipped') return -1
      if (b.status === 'incorrect' && a.status === 'skipped') return 1

      // 3. 跳過測驗 (最低優先級)
      return 0
    })
  }

  // 處理測驗結果
  handleTestResult(testId: string, result: 'correct' | 'incorrect' | 'skipped') {
    switch (result) {
      case 'correct':
        // 從隊列完全移除
        this.removeFromQueue(testId)
        break
      case 'incorrect':
        // 移到隊列最後
        this.moveToEnd(testId, 'incorrect')
        break
      case 'skipped':
        // 移到隊列最後
        this.moveToEnd(testId, 'skipped')
        break
    }
  }
}

測驗組件標準化介面

// 🧩 測驗組件統一介面
interface TestComponentProps {
  flashcard: Flashcard
  testType: TestType
  onSubmit: (result: TestResult) => void
  onSkip: () => void
  isDisabled: boolean
}

// 📝 測驗結果標準格式
interface TestResult {
  flashcardId: string
  testType: TestType
  isCorrect: boolean
  userAnswer: string
  confidenceLevel?: number
  responseTimeMs: number
}

// 🎮 導航狀態管理
type NavigationState = 'pre-answer' | 'post-answer'
type ButtonAction = 'skip' | 'continue'

🏛️ 後端服務架構設計

控制器職責重新劃分

// 📚 FlashcardsController - 純粹詞卡資料管理
[Route("api/flashcards")]
public class FlashcardsController
{
    // 純粹的 CRUD 操作
    GET    /                        // 詞卡列表查詢
    POST   /                        // 創建新詞卡
    GET    /{id}                    // 詞卡詳情
    PUT    /{id}                    // 更新詞卡
    DELETE /{id}                    // 刪除詞卡
    POST   /{id}/favorite           // 收藏切換
}

// 🎯 ReviewController - 完整複習管理
[Route("api/review")]
public class ReviewController
{
    // 複習狀態管理
    GET    /today                   // 今日複習總覽
    GET    /next                    // 下一個測驗
    GET    /progress                // 複習進度

    // 測驗執行
    POST   /{id}/question           // 生成題目選項
    POST   /{id}/submit             // 提交測驗結果
    POST   /{id}/skip               // 跳過測驗

    // 智能適配
    POST   /{id}/optimal-mode       // 智能模式選擇

    // 狀態持久化
    GET    /completed-tests         // 已完成測驗
    POST   /record-test             // 記錄測驗
    GET    /stats                   // 複習統計
}

智能複習服務群架構

// 🧠 核心算法服務
public interface ISpacedRepetitionService
{
    Task<ReviewResult> ProcessReviewAsync(Guid flashcardId, ReviewRequest request);
    Task<List<Flashcard>> GetTodaysDueCardsAsync(Guid userId);
    Task<TestItem?> GetNextTestAsync(Guid userId);
    int CalculateCurrentMasteryLevel(Flashcard flashcard);
}

// 🎯 智能適配服務
public interface IReviewAdaptationService
{
    Task<ReviewModeResult> SelectOptimalModeAsync(Flashcard flashcard, User user);
    string[] GetAvailableTestTypes(string userCEFR, string wordCEFR);
    AdaptationContext GetAdaptationContext(string userCEFR, string wordCEFR);
}

// 🔄 隊列管理服務
public interface ITestQueueService
{
    Task<TestQueue> BuildTodaysQueueAsync(Guid userId);
    Task<TestItem?> GetNextTestItemAsync(Guid userId);
    Task HandleTestResultAsync(Guid userId, TestResult result);
    Task<QueueStatus> GetQueueStatusAsync(Guid userId);
}

// 📝 題目生成服務
public interface IQuestionGeneratorService
{
    Task<QuestionData> GenerateQuestionAsync(Guid flashcardId, TestType testType);
}

// 💾 狀態持久化服務
public interface IReviewStateService
{
    Task<CompletedTest[]> GetCompletedTestsAsync(Guid userId, DateTime? date = null);
    Task<bool> RecordTestCompletionAsync(Guid userId, TestResult result);
    Task<LearningProgress> GetProgressAsync(Guid userId);
}

💾 資料模型設計規範

核心實體關係

// 👤 用戶實體 (擴展 CEFR 支援)
public class User
{
    public Guid Id { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }

    // 🆕 CEFR 智能適配
    public string EnglishLevel { get; set; } = "A2";        // A1-C2
    public DateTime LevelUpdatedAt { get; set; }
    public bool IsLevelVerified { get; set; }

    // Navigation Properties
    public ICollection<Flashcard> Flashcards { get; set; }
    public ICollection<ReviewRecord> ReviewRecords { get; set; }
    public ICollection<DailyStats> DailyStats { get; set; }
}

// 📚 詞卡實體 (智能複習增強)
public class Flashcard
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }

    // 詞卡內容
    public string Word { get; set; }
    public string Translation { get; set; }
    public string Definition { get; set; }
    public string? PartOfSpeech { get; set; }
    public string? Pronunciation { get; set; }
    public string? Example { get; set; }
    public string? ExampleTranslation { get; set; }

    // 🆕 智能複習參數
    public string? DifficultyLevel { get; set; }        // A1-C2
    public float EasinessFactor { get; set; } = 2.5f;   // SM-2 算法
    public int Repetitions { get; set; } = 0;
    public int IntervalDays { get; set; } = 1;
    public DateTime NextReviewDate { get; set; }
    public int MasteryLevel { get; set; } = 0;           // 0-100
    public int TimesReviewed { get; set; } = 0;
    public int TimesCorrect { get; set; } = 0;
    public DateTime? LastReviewedAt { get; set; }

    // 🆕 測驗歷史追蹤
    public string? ReviewHistory { get; set; }           // JSON
    public string? LastQuestionType { get; set; }

    // Navigation Properties
    public User User { get; set; }
    public ICollection<ReviewRecord> ReviewRecords { get; set; }
}

// 📊 複習記錄實體 (簡化無 Session)
public class ReviewRecord
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }
    public Guid FlashcardId { get; set; }

    // 測驗資訊
    public string ReviewMode { get; set; }               // 測驗類型
    public int QualityRating { get; set; }              // 1-5 (SM-2)
    public bool IsCorrect { get; set; }
    public string? UserAnswer { get; set; }
    public int? ResponseTimeMs { get; set; }
    public DateTime StudiedAt { get; set; }

    // SM-2 追蹤參數
    public double? PreviousEasinessFactor { get; set; }
    public double? NewEasinessFactor { get; set; }
    public int? PreviousIntervalDays { get; set; }
    public int? NewIntervalDays { get; set; }
    public DateTime? NextReviewDate { get; set; }

    // Navigation Properties
    public User User { get; set; }
    public Flashcard Flashcard { get; set; }
}

// 📈 每日統計實體
public class DailyStats
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }
    public DateOnly Date { get; set; }

    // 複習統計
    public int WordsStudied { get; set; } = 0;
    public int WordsCorrect { get; set; } = 0;
    public int ReviewTimeSeconds { get; set; } = 0;
    public int SessionCount { get; set; } = 0;

    // Navigation Properties
    public User User { get; set; }
}

資料庫索引策略

-- 🎯 智能複習核心索引
CREATE INDEX IX_Flashcards_UserDue
ON flashcards(user_id, next_review_date)
WHERE is_archived = 0;

-- 📊 複習記錄查詢優化
CREATE UNIQUE INDEX IX_ReviewRecord_UserCardTest
ON review_records(user_id, flashcard_id, review_mode);

-- 📈 統計查詢優化
CREATE UNIQUE INDEX IX_DailyStats_UserDate
ON daily_stats(user_id, date);

-- 🔍 詞卡搜尋優化
CREATE INDEX IX_Flashcards_Search
ON flashcards(user_id, word, translation)
WHERE is_archived = 0;

-- ⚡ CEFR 等級查詢優化
CREATE INDEX IX_Flashcards_CEFR
ON flashcards(user_id, difficulty_level, mastery_level);

🔗 API 設計規範

統一 API 契約

// 🌐 統一響應格式
interface ApiResponse<T> {
  success: boolean
  data?: T
  error?: string
  message?: string
  timestamp: string
  requestId?: string
}

// ⚠️ 統一錯誤格式
interface ApiError {
  code: string
  message: string
  details?: any
  suggestions?: string[]
}

智能複習 API 設計

// 📋 今日複習總覽 API
GET /api/review/today
Response: {
  success: true,
  data: {
    dueCards: Flashcard[],
    totalTests: number,
    completedTests: number,
    estimatedTimeMinutes: number,
    adaptationSummary: {
      a1Protection: boolean,
      primaryAdaptation: "簡單詞彙" | "適中詞彙" | "困難詞彙",
      recommendedTestTypes: string[]
    }
  }
}

// 🎯 下一個測驗 API
GET /api/review/next
Response: {
  success: true,
  data: {
    testItem: {
      flashcardId: string,
      testType: string,
      flashcard: Flashcard,
      questionData: QuestionData
    },
    queueStatus: {
      remaining: number,
      completed: number,
      skipped: number
    },
    navigationState: "skip" | "continue"
  }
}

// 📝 提交測驗 API
POST /api/review/{flashcardId}/submit
Request: {
  testType: string,
  isCorrect: boolean,
  userAnswer: string,
  confidenceLevel?: number,
  responseTimeMs: number
}
Response: {
  success: true,
  data: {
    reviewResult: ReviewResult,
    nextAction: "continue" | "session_complete",
    masteryUpdate: {
      previousLevel: number,
      newLevel: number,
      nextReviewDate: string
    }
  }
}

// ⏭️ 跳過測驗 API
POST /api/review/{flashcardId}/skip
Request: {
  testType: string,
  reason?: string
}
Response: {
  success: true,
  data: {
    skippedTestId: string,
    nextAction: "continue",
    queueStatus: QueueStatus
  }
}

🧠 智能適配算法設計

CEFR 四情境適配邏輯

// 🎯 CEFRMappingService - 等級轉換服務
public static class CEFRMappingService
{
    private static readonly Dictionary<string, int> CEFRToLevel = new()
    {
        { "A1", 20 }, { "A2", 35 }, { "B1", 50 },
        { "B2", 65 }, { "C1", 80 }, { "C2", 95 }
    };

    public static int GetNumericLevel(string cefrLevel)
        => CEFRToLevel.GetValueOrDefault(cefrLevel, 50);

    public static string GetCEFRLevel(int numericLevel)
        => CEFRToLevel.FirstOrDefault(kvp => kvp.Value == numericLevel).Key ?? "B1";
}

// 🛡️ 智能適配服務實現
public class ReviewAdaptationService : IReviewAdaptationService
{
    public async Task<ReviewModeResult> SelectOptimalModeAsync(Flashcard flashcard, User user)
    {
        var userLevel = CEFRMappingService.GetNumericLevel(user.EnglishLevel);
        var wordLevel = CEFRMappingService.GetNumericLevel(flashcard.DifficultyLevel ?? "B1");

        // 四情境判斷邏輯
        var adaptationContext = GetAdaptationContext(userLevel, wordLevel);
        var availableTestTypes = GetAvailableTestTypes(adaptationContext);
        var selectedMode = await SelectModeWithHistory(flashcard.Id, availableTestTypes);

        return new ReviewModeResult
        {
            SelectedMode = selectedMode,
            AdaptationContext = adaptationContext,
            AvailableTestTypes = availableTestTypes,
            Reason = GetSelectionReason(adaptationContext, selectedMode)
        };
    }

    private AdaptationContext GetAdaptationContext(int userLevel, int wordLevel)
    {
        var difficulty = wordLevel - userLevel;

        if (userLevel <= 20)          // A1 保護
            return AdaptationContext.A1Protection;

        if (difficulty < -10)         // 簡單詞彙
            return AdaptationContext.EasyVocabulary;

        if (difficulty >= -10 && difficulty <= 10)  // 適中詞彙
            return AdaptationContext.ModerateVocabulary;

        return AdaptationContext.DifficultVocabulary;  // 困難詞彙
    }

    private string[] GetAvailableTestTypes(AdaptationContext context)
    {
        return context switch
        {
            AdaptationContext.A1Protection => new[] { "flip-memory", "vocab-choice", "vocab-listening" },
            AdaptationContext.EasyVocabulary => new[] { "sentence-reorder", "sentence-fill" },
            AdaptationContext.ModerateVocabulary => new[] { "sentence-fill", "sentence-reorder", "sentence-speaking" },
            AdaptationContext.DifficultVocabulary => new[] { "flip-memory", "vocab-choice" },
            _ => new[] { "flip-memory", "vocab-choice" }
        };
    }
}

隊列管理算法

// 🔄 測驗隊列服務
public class TestQueueService : ITestQueueService
{
    public async Task<TestQueue> BuildTodaysQueueAsync(Guid userId)
    {
        // 1. 獲取今日到期詞卡
        var dueCards = await GetTodaysDueCardsAsync(userId);

        // 2. 獲取已完成測驗
        var completedTests = await GetCompletedTestsAsync(userId, DateTime.Today);

        // 3. 計算剩餘測驗
        var allTests = GenerateAllPossibleTests(dueCards);
        var remainingTests = FilterCompletedTests(allTests, completedTests);

        // 4. 智能排序
        var prioritizedTests = PrioritizeTests(remainingTests);

        return new TestQueue
        {
            Tests = prioritizedTests,
            TotalCount = allTests.Count,
            CompletedCount = completedTests.Count,
            RemainingCount = remainingTests.Count
        };
    }

    private List<TestItem> PrioritizeTests(List<TestItem> tests)
    {
        return tests
            .OrderBy(t => t.Status switch {
                TestStatus.New => 0,          // 優先處理新測驗
                TestStatus.Incorrect => 1,    // 然後是答錯的
                TestStatus.Skipped => 2,      // 最後是跳過的
                _ => 3
            })
            .ThenBy(t => t.LastAttemptAt ?? DateTime.MinValue)  // 按最後嘗試時間排序
            .ToList();
    }
}

🎮 前端狀態管理架構

Context + Hooks 架構

// 🧠 Review Context Provider
export const ReviewContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [state, dispatch] = useReducer(reviewReducer, initialState)

  // 核心方法
  const contextValue = {
    // 狀態
    ...state,

    // 操作方法
    loadTodaysReview: () => dispatch({ type: 'LOAD_TODAYS_REVIEW' }),
    submitTest: (result: TestResult) => dispatch({ type: 'SUBMIT_TEST', payload: result }),
    skipTest: (testId: string) => dispatch({ type: 'SKIP_TEST', payload: testId }),
    proceedToNext: () => dispatch({ type: 'PROCEED_TO_NEXT' }),

    // 狀態查詢
    getCurrentTest: () => state.testQueue[state.currentTestIndex],
    getNavigationState: () => state.isAnswered ? 'continue' : 'skip',
    isSessionComplete: () => state.testQueue.every(t => t.status === 'completed')
  }

  return (
    <ReviewContext.Provider value={contextValue}>
      {children}
    </ReviewContext.Provider>
  )
}

// 🎯 自定義 Hooks
export const useReviewFlow = () => {
  const context = useContext(ReviewContext)

  return {
    // 當前測驗
    currentTest: context.getCurrentTest(),

    // 導航控制
    navigationState: context.getNavigationState(),
    canSkip: !context.isAnswered,
    canContinue: context.isAnswered,

    // 操作方法
    submitAnswer: context.submitTest,
    skipCurrent: context.skipTest,
    proceedNext: context.proceedToNext,

    // 進度資訊
    progress: {
      completed: context.testQueue.filter(t => t.status === 'completed').length,
      total: context.testQueue.length,
      isComplete: context.isSessionComplete()
    }
  }
}

組件化架構實現

// 🎮 智能複習主容器
export const SmartReviewContainer: React.FC = () => {
  return (
    <ReviewContextProvider>
      <div className="smart-review-container">
        <ProgressTracker />
        <TestRenderer />
        <NavigationController />
      </div>
    </ReviewContextProvider>
  )
}

// 📊 進度追蹤組件
export const ProgressTracker: React.FC = () => {
  const { progress } = useReviewFlow()

  return (
    <div className="progress-tracker">
      {/* 雙層進度條實現 */}
      <div className="card-progress">
        <div className="progress-bar">
          <div
            className="progress-fill"
            style={{ width: `${(progress.completed / progress.total) * 100}%` }}
          />
        </div>
        <span>{progress.completed}/{progress.total} 已完成</span>
      </div>
    </div>
  )
}

// 🎯 動態測驗渲染器
export const TestRenderer: React.FC = () => {
  const { currentTest } = useReviewFlow()

  if (!currentTest) {
    return <SessionCompleteScreen />
  }

  // 動態載入對應的測驗組件
  const TestComponent = getTestComponent(currentTest.testType)

  return (
    <div className="test-renderer">
      <TestComponent
        flashcard={currentTest.flashcard}
        questionData={currentTest.questionData}
        onSubmit={handleTestSubmit}
        onSkip={handleTestSkip}
      />
    </div>
  )
}

// 🧭 導航控制器
export const NavigationController: React.FC = () => {
  const { navigationState, canSkip, canContinue, skipCurrent, proceedNext } = useReviewFlow()

  return (
    <div className="navigation-controller">
      {navigationState === 'skip' && canSkip && (
        <button onClick={skipCurrent} className="skip-button">
          ⏭️ 跳過這題
        </button>
      )}

      {navigationState === 'continue' && canContinue && (
        <button onClick={proceedNext} className="continue-button">
          ➡️ 繼續下一題
        </button>
      )}
    </div>
  )
}

🔧 服務層實現規範

依賴注入配置

// 📦 服務註冊 (Program.cs)
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddSmartReviewServices(this IServiceCollection services)
    {
        // 核心智能複習服務
        services.AddScoped<ISpacedRepetitionService, SpacedRepetitionService>();
        services.AddScoped<IReviewAdaptationService, ReviewAdaptationService>();
        services.AddScoped<ITestQueueService, TestQueueService>();
        services.AddScoped<IQuestionGeneratorService, QuestionGeneratorService>();
        services.AddScoped<IReviewStateService, ReviewStateService>();

        // 輔助服務
        services.AddSingleton<CEFRMappingService>();
        services.AddScoped<IOptionsVocabularyService, OptionsVocabularyService>();
        services.AddScoped<IBlankGenerationService, BlankGenerationService>();

        // 配置選項
        services.Configure<SpacedRepetitionOptions>(config.GetSection("SpacedRepetition"));

        return services;
    }
}

錯誤處理統一標準

// 🛡️ 全域錯誤處理中間件
public class SmartReviewErrorHandlingMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception ex)
    {
        var response = ex switch
        {
            ArgumentException => CreateErrorResponse("INVALID_INPUT", ex.Message, 400),
            UnauthorizedAccessException => CreateErrorResponse("UNAUTHORIZED", "認證失敗", 401),
            InvalidOperationException => CreateErrorResponse("INVALID_OPERATION", ex.Message, 400),
            _ => CreateErrorResponse("INTERNAL_ERROR", "系統內部錯誤", 500)
        };

        context.Response.StatusCode = response.StatusCode;
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(JsonSerializer.Serialize(response.Content));
    }
}

📊 效能優化架構

快取策略設計

// 🚀 多層快取架構
public class SmartReviewCacheService
{
    // L1: Memory Cache (最快)
    private readonly IMemoryCache _memoryCache;

    // L2: Distributed Cache (Redis/SQL)
    private readonly IDistributedCache _distributedCache;

    // L3: Database (最持久)
    private readonly DramaLingDbContext _context;

    public async Task<T?> GetAsync<T>(string key) where T : class
    {
        // 1. 嘗試記憶體快取
        if (_memoryCache.TryGetValue(key, out T? cachedValue))
            return cachedValue;

        // 2. 嘗試分散式快取
        var distributedValue = await _distributedCache.GetStringAsync(key);
        if (distributedValue != null)
        {
            var deserializedValue = JsonSerializer.Deserialize<T>(distributedValue);
            _memoryCache.Set(key, deserializedValue, TimeSpan.FromMinutes(5));
            return deserializedValue;
        }

        // 3. 資料庫查詢 (最後選擇)
        return null;
    }
}

// 📈 關鍵快取策略
var cacheStrategies = new Dictionary<string, CacheStrategy>
{
    ["today_due_cards"] = new(TimeSpan.FromMinutes(15), CacheLevel.Memory),
    ["completed_tests"] = new(TimeSpan.FromMinutes(30), CacheLevel.Distributed),
    ["user_cefr_mapping"] = new(TimeSpan.FromHours(1), CacheLevel.Memory),
    ["question_options"] = new(TimeSpan.FromMinutes(10), CacheLevel.Memory)
};

API 效能優化

// ⚡ 查詢優化策略
public class OptimizedReviewService
{
    // 批量預載入相關資料
    public async Task<List<Flashcard>> GetDueCardsWithOptimizationAsync(Guid userId)
    {
        return await _context.Flashcards
            .Where(f => f.UserId == userId && f.NextReviewDate <= DateTime.Today)
            .Include(f => f.ReviewRecords.Where(sr => sr.StudiedAt.Date == DateTime.Today))
            .AsNoTracking()  // 只讀查詢優化
            .AsSplitQuery()  // 分割查詢避免笛卡爾積
            .ToListAsync();
    }

    // 智能預測下一個測驗
    public async Task<TestItem?> PredictNextTestAsync(Guid userId)
    {
        // 使用演算法預測,減少即時計算
        var cachedPrediction = await _cache.GetAsync($"next_test:{userId}");
        if (cachedPrediction != null) return cachedPrediction;

        // 重新計算並快取
        var nextTest = await ComputeNextTestAsync(userId);
        await _cache.SetAsync($"next_test:{userId}", nextTest, TimeSpan.FromMinutes(5));

        return nextTest;
    }
}

🔐 安全與認證架構

JWT 認證策略

// 🔑 JWT 配置 (Program.cs)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = supabaseUrl,
            ValidAudience = "authenticated",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret))
        };

        // 智能複習特殊處理:允許過期 token 緩衝期
        options.Events = new JwtBearerEvents
        {
            OnTokenValidated = context =>
            {
                // 檢查複習狀態,允許正在複習的用戶有 5 分鐘緩衝
                return Task.CompletedTask;
            }
        };
    });

權限控制策略

// 🛡️ 智能複習權限檢查
[AttributeUsage(AttributeTargets.Method)]
public class RequireFlashcardOwnershipAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        // 檢查詞卡所有權
        var flashcardId = GetFlashcardIdFromRoute(context);
        var userId = GetUserIdFromToken(context);

        if (!ValidateOwnership(flashcardId, userId))
        {
            context.Result = new UnauthorizedResult();
        }
    }
}

// 使用範例
[HttpPost("{id}/submit")]
[RequireFlashcardOwnership]
public async Task<ActionResult> SubmitTest(Guid id, [FromBody] TestResult result)
{
    // 已通過權限檢查,直接處理業務邏輯
}

📈 監控與可觀測性

關鍵指標監控

// 📊 智能複習指標收集
public class SmartReviewMetrics
{
    // 業務指標
    [Counter("smart_review_tests_completed_total")]
    public static readonly Counter TestsCompleted;

    [Counter("smart_review_tests_skipped_total")]
    public static readonly Counter TestsSkipped;

    [Histogram("smart_review_response_time_ms")]
    public static readonly Histogram ResponseTime;

    [Gauge("smart_review_active_sessions")]
    public static readonly Gauge ActiveSessions;

    // CEFR 適配指標
    [Counter("cefr_adaptation_selections_total")]
    public static readonly Counter CEFRAdaptations;

    [Histogram("cefr_adaptation_accuracy")]
    public static readonly Histogram AdaptationAccuracy;
}

健康檢查端點

// 🩺 智能複習系統健康檢查
[HttpGet("/health/smart-review")]
public async Task<ActionResult> GetSmartReviewHealth()
{
    var healthChecks = new Dictionary<string, object>
    {
        ["database"] = await CheckDatabaseConnectionAsync(),
        ["cefr_mapping"] = CheckCEFRMappingService(),
        ["spaced_repetition"] = CheckSpacedRepetitionAlgorithm(),
        ["test_queue"] = await CheckTestQueueServiceAsync(),
        ["api_response_time"] = await MeasureAPIResponseTimeAsync()
    };

    var isHealthy = healthChecks.Values.All(v => v.ToString() == "Healthy");

    return Ok(new
    {
        Status = isHealthy ? "Healthy" : "Degraded",
        Checks = healthChecks,
        Timestamp = DateTime.UtcNow,
        SystemInfo = new
        {
            ActiveUsers = await GetActiveUserCountAsync(),
            TestsCompletedToday = await GetTodaysTestCountAsync(),
            AverageResponseTimeMs = GetAverageResponseTime()
        }
    });
}

🚀 部署架構規範

環境配置標準

// 🔧 appsettings.json (生產環境)
{
  "SmartReview": {
    "EnableAdaptiveAlgorithm": true,
    "A1ProtectionLevel": 20,
    "CEFRMappingCache": {
      "ExpirationMinutes": 60,
      "MaxEntries": 1000
    },
    "TestQueue": {
      "MaxQueueSize": 100,
      "PriorityRecalculationMinutes": 15,
      "SkipLimitPerSession": 50
    },
    "Performance": {
      "MaxConcurrentSessions": 1000,
      "ResponseTimeThresholdMs": 100,
      "CacheHitRateThreshold": 0.8
    }
  },
  "SpacedRepetition": {
    "Algorithm": "SM2Enhanced",
    "GrowthFactors": {
      "ShortTerm": 1.8,
      "MediumTerm": 1.4,
      "LongTerm": 1.2,
      "VeryLongTerm": 1.1
    },
    "OverduePenalties": {
      "Light": 0.9,
      "Medium": 0.75,
      "Heavy": 0.5,
      "Extreme": 0.3
    }
  }
}

容器化部署配置

# 🐳 Dockerfile (生產環境)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["DramaLing.Api.csproj", "."]
RUN dotnet restore

COPY . .
RUN dotnet build -c Release -o /app/build

FROM build AS publish
RUN dotnet publish -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

# 智能複習系統特殊配置
ENV ASPNETCORE_ENVIRONMENT=Production
ENV SMART_REVIEW_ENABLED=true
ENV CEFR_CACHE_SIZE=1000

ENTRYPOINT ["dotnet", "DramaLing.Api.dll"]

🧪 測試架構設計

測試策略規範

// 🧪 智能複習服務測試
[TestFixture]
public class SmartReviewServiceTests
{
    private ITestQueueService _queueService;
    private ISpacedRepetitionService _spacedRepService;
    private Mock<DramaLingDbContext> _mockContext;

    [Test]
    public async Task BuildTodaysQueue_ShouldPrioritizeNewTests()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var dueCards = CreateTestDueCards();
        var completedTests = CreateTestCompletedTests();

        // Act
        var queue = await _queueService.BuildTodaysQueueAsync(userId);

        // Assert
        Assert.That(queue.Tests.First().Status, Is.EqualTo(TestStatus.New));
        Assert.That(queue.Tests.Where(t => t.Status == TestStatus.New).Count(), Is.GreaterThan(0));
    }

    [Test]
    public async Task CEFRAdaptation_A1User_ShouldLimitToBasicTests()
    {
        // Arrange
        var userLevel = 20; // A1
        var wordLevel = 50; // B1

        // Act
        var availableTests = _adaptationService.GetAvailableTestTypes(userLevel, wordLevel);

        // Assert
        Assert.That(availableTests, Is.EquivalentTo(new[] { "flip-memory", "vocab-choice", "vocab-listening" }));
    }
}

前端組件測試

// 🧪 React 組件測試
describe('SmartReviewContainer', () => {
  test('should load todays review on mount', async () => {
    render(
      <ReviewContextProvider>
        <SmartReviewContainer />
      </ReviewContextProvider>
    )

    await waitFor(() => {
      expect(screen.getByText(/今日複習/)).toBeInTheDocument()
    })
  })

  test('should show skip button before answering', () => {
    const { getByText } = renderWithContext(<NavigationController />, {
      isAnswered: false,
      navigationState: 'skip'
    })

    expect(getByText('跳過這題')).toBeInTheDocument()
  })

  test('should show continue button after answering', () => {
    const { getByText } = renderWithContext(<NavigationController />, {
      isAnswered: true,
      navigationState: 'continue'
    })

    expect(getByText('繼續下一題')).toBeInTheDocument()
  })
})

🎯 實施路線圖

階段一:核心架構 (第1-2週)

  1. 後端服務層重構

    • 實現 TestQueueService
    • 重構 ReviewController API
    • 移除 Session 複雜性
  2. 前端組件基礎

    • 建立 ReviewContext
    • 實現核心 Hooks
    • 基礎組件框架

階段二:智能適配 (第3-4週)

  1. CEFR 適配系統

    • ReviewAdaptationService 實現
    • 四情境邏輯完整實現
    • A1 保護機制
  2. 測驗類型組件

    • 7 種測驗組件實現
    • 統一介面標準化
    • 答案驗證邏輯

階段三:隊列管理 (第5-6週)

  1. 智能隊列系統

    • 優先級排序算法
    • 跳過邏輯實現
    • 狀態持久化
  2. 導航控制系統

    • 狀態驅動導航
    • 流暢的用戶體驗
    • 錯誤恢復機制

階段四:優化與監控 (第7-8週)

  1. 效能優化

    • 快取策略實施
    • 查詢優化
    • 響應時間優化
  2. 監控與測試

    • 監控指標實施
    • 自動化測試
    • 效能基準測試

📋 開發檢查清單

前端開發檢查項目

  • ReviewContext 實現完成
  • 7 個測驗組件實現完成
  • 導航控制邏輯實現
  • 隊列管理邏輯實現
  • 狀態持久化機制
  • 錯誤處理和重試機制
  • 響應式設計適配
  • 無障礙設計支援

後端開發檢查項目

  • ReviewController 重構完成
  • 智能複習服務實現
  • CEFR 適配邏輯實現
  • 隊列管理服務實現
  • API 錯誤處理統一
  • 認證授權機制
  • 效能優化實施
  • 監控指標收集

整合測試檢查項目

  • 前後端 API 契約測試
  • 四情境適配邏輯測試
  • 隊列管理端到端測試
  • 狀態持久化測試
  • 效能基準測試
  • 用戶體驗流程測試

文檔版本: 1.0 創建日期: 2025-09-29 技術負責: 開發團隊 審核狀態: 待審核 實施優先級: P0 (最高優先級)