38 KiB
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週)
-
後端服務層重構
- 實現 TestQueueService
- 重構 ReviewController API
- 移除 Session 複雜性
-
前端組件基礎
- 建立 ReviewContext
- 實現核心 Hooks
- 基礎組件框架
階段二:智能適配 (第3-4週)
-
CEFR 適配系統
- ReviewAdaptationService 實現
- 四情境邏輯完整實現
- A1 保護機制
-
測驗類型組件
- 7 種測驗組件實現
- 統一介面標準化
- 答案驗證邏輯
階段三:隊列管理 (第5-6週)
-
智能隊列系統
- 優先級排序算法
- 跳過邏輯實現
- 狀態持久化
-
導航控制系統
- 狀態驅動導航
- 流暢的用戶體驗
- 錯誤恢復機制
階段四:優化與監控 (第7-8週)
-
效能優化
- 快取策略實施
- 查詢優化
- 響應時間優化
-
監控與測試
- 監控指標實施
- 自動化測試
- 效能基準測試
📋 開發檢查清單
前端開發檢查項目
- ReviewContext 實現完成
- 7 個測驗組件實現完成
- 導航控制邏輯實現
- 隊列管理邏輯實現
- 狀態持久化機制
- 錯誤處理和重試機制
- 響應式設計適配
- 無障礙設計支援
後端開發檢查項目
- ReviewController 重構完成
- 智能複習服務實現
- CEFR 適配邏輯實現
- 隊列管理服務實現
- API 錯誤處理統一
- 認證授權機制
- 效能優化實施
- 監控指標收集
整合測試檢查項目
- 前後端 API 契約測試
- 四情境適配邏輯測試
- 隊列管理端到端測試
- 狀態持久化測試
- 效能基準測試
- 用戶體驗流程測試
文檔版本: 1.0 創建日期: 2025-09-29 技術負責: 開發團隊 審核狀態: 待審核 實施優先級: P0 (最高優先級)