# DramaLing 後端架構詳細說明 ## 1. 技術棧概覽 ### 1.1 核心技術 - **框架**: ASP.NET Core 8.0 - **語言**: C# .NET 8 - **ORM**: Entity Framework Core 8.0 - **資料庫**: SQLite 3.x - **認證**: JWT Bearer Token - **依賴注入**: Microsoft.Extensions.DependencyInjection ### 1.2 專案結構 ``` backend/DramaLing.Api/ ├── Controllers/ # API 控制器 │ ├── FlashcardsController.cs │ ├── AIController.cs │ └── AuthController.cs ├── Models/ │ ├── Entities/ # 資料模型 │ │ ├── Flashcard.cs │ │ ├── User.cs │ │ └── CardSet.cs │ ├── DTOs/ # 資料傳輸物件 │ └── Configuration/ # 配置模型 ├── Data/ # 資料存取層 │ ├── DramaLingDbContext.cs │ └── Migrations/ ├── Services/ # 業務邏輯層 │ ├── AI/ # AI 服務 │ ├── Caching/ # 快取服務 │ └── AuthService.cs ├── Extensions/ # 擴展方法 │ └── ServiceCollectionExtensions.cs └── Program.cs # 應用程式入口 ``` ## 2. 資料模型架構 ### 2.1 詞卡實體模型 (Flashcard) ```csharp public class Flashcard { // 主鍵和關聯 public Guid Id { get; set; } public Guid UserId { get; set; } public Guid? CardSetId { get; set; } // 詞卡內容 [Required, MaxLength(255)] public string Word { get; set; } [Required] public string Translation { get; set; } [Required] public string Definition { get; set; } [MaxLength(50)] public string? PartOfSpeech { get; set; } [MaxLength(255)] public string? Pronunciation { get; set; } public string? Example { get; set; } public string? ExampleTranslation { get; set; } // SM-2 學習算法參數 public float EasinessFactor { get; set; } = 2.5f; public int Repetitions { get; set; } = 0; public int IntervalDays { get; set; } = 1; public DateTime NextReviewDate { get; set; } // 學習統計 [Range(0, 100)] public int MasteryLevel { get; set; } = 0; public int TimesReviewed { get; set; } = 0; public int TimesCorrect { get; set; } = 0; public DateTime? LastReviewedAt { get; set; } // 狀態管理 public bool IsFavorite { get; set; } = false; public bool IsArchived { get; set; } = false; [MaxLength(10)] public string? DifficultyLevel { get; set; } // A1-C2 // 時間戳記 public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } // 導航屬性 public virtual User User { get; set; } public virtual CardSet? CardSet { get; set; } public virtual ICollection StudyRecords { get; set; } public virtual ICollection FlashcardTags { get; set; } public virtual ICollection ErrorReports { get; set; } } ``` ### 2.2 資料庫關聯設計 ``` Users (1) ──────────────── (*) Flashcards │ │ │ │ (*) │ │ └─── (1) CardSets (*) ───────┘ StudyRecords (*) ──── (1) Flashcards ErrorReports (*) ──── (1) Flashcards FlashcardTags (*) ─── (1) Flashcards ``` ## 3. API 架構設計 ### 3.1 控制器架構 #### FlashcardsController.cs ```csharp [ApiController] [Route("api/flashcards")] [AllowAnonymous] // 開發階段暫時移除認證 public class FlashcardsController : ControllerBase { private readonly DramaLingDbContext _context; private readonly ILogger _logger; // 標準 RESTful API 端點 [HttpGet] // GET /api/flashcards [HttpGet("{id}")] // GET /api/flashcards/{id} [HttpPost] // POST /api/flashcards [HttpPut("{id}")] // PUT /api/flashcards/{id} [HttpDelete("{id}")] // DELETE /api/flashcards/{id} [HttpPost("{id}/favorite")] // POST /api/flashcards/{id}/favorite } ``` ### 3.2 API 回應格式標準化 #### 成功回應格式 ```json { "success": true, "data": { "flashcards": [...], "count": 42 }, "message": "操作成功" } ``` #### 錯誤回應格式 ```json { "success": false, "error": "錯誤描述", "details": "詳細錯誤信息", "timestamp": "2025-09-24T10:30:00Z" } ``` ### 3.3 查詢參數支援 #### GET /api/flashcards ```csharp public async Task GetFlashcards( [FromQuery] string? search = null, // 搜尋關鍵字 [FromQuery] bool favoritesOnly = false // 僅收藏詞卡 ) ``` ## 4. 服務層架構 ### 4.1 依賴注入配置 (ServiceCollectionExtensions.cs) ```csharp public static class ServiceCollectionExtensions { // 資料庫服務配置 public static IServiceCollection AddDatabaseServices(...) // Repository 服務配置 public static IServiceCollection AddRepositoryServices(...) // 快取服務配置 public static IServiceCollection AddCachingServices(...) // AI 服務配置 public static IServiceCollection AddAIServices(...) // 業務服務配置 public static IServiceCollection AddBusinessServices(...) // 認證服務配置 public static IServiceCollection AddAuthenticationServices(...) // CORS 政策配置 public static IServiceCollection AddCorsServices(...) } ``` ### 4.2 業務服務層 #### 已實現的服務 ```csharp // 認證服務 services.AddScoped(); // 使用量追蹤 services.AddScoped(); // Azure 語音服務 services.AddScoped(); // 音頻快取 services.AddScoped(); // AI 提供商管理 services.AddScoped(); services.AddScoped(); ``` ## 5. 資料存取層 ### 5.1 DbContext 配置 ```csharp public class DramaLingDbContext : DbContext { public DbSet Users { get; set; } public DbSet Flashcards { get; set; } public DbSet CardSets { get; set; } public DbSet StudyRecords { get; set; } public DbSet ErrorReports { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 詞卡配置 modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.Word).IsRequired().HasMaxLength(255); entity.Property(e => e.Translation).IsRequired(); entity.Property(e => e.Definition).IsRequired(); // 關聯配置 entity.HasOne(f => f.User) .WithMany(u => u.Flashcards) .HasForeignKey(f => f.UserId); entity.HasOne(f => f.CardSet) .WithMany(cs => cs.Flashcards) .HasForeignKey(f => f.CardSetId) .IsRequired(false); // CardSetId 可為空 }); } } ``` ### 5.2 資料庫連接配置 #### 開發環境 ```csharp // 環境變數或配置檔案 var connectionString = Environment.GetEnvironmentVariable("DRAMALING_DB_CONNECTION") ?? configuration.GetConnectionString("DefaultConnection") ?? "Data Source=dramaling_test.db"; services.AddDbContext(options => options.UseSqlite(connectionString)); ``` #### 記憶體資料庫 (測試用) ```csharp var useInMemoryDb = Environment.GetEnvironmentVariable("USE_INMEMORY_DB") == "true"; if (useInMemoryDb) { services.AddDbContext(options => options.UseSqlite("Data Source=:memory:")); } ``` ## 6. 認證與授權 ### 6.1 JWT 配置 ```csharp public static IServiceCollection AddAuthenticationServices(...) { var supabaseUrl = Environment.GetEnvironmentVariable("DRAMALING_SUPABASE_URL") ?? "https://localhost"; var jwtSecret = Environment.GetEnvironmentVariable("DRAMALING_SUPABASE_JWT_SECRET") ?? "dev-secret-minimum-32-characters-long-for-jwt-signing-in-development-mode-only"; 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)) }; }); } ``` ### 6.2 開發階段認證處理 ```csharp // 暫時移除認證要求,使用固定測試用戶 private Guid GetUserId() { return Guid.Parse("00000000-0000-0000-0000-000000000001"); // 生產環境將啟用: // var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; // if (Guid.TryParse(userIdString, out var userId)) // return userId; // throw new UnauthorizedAccessException("Invalid user ID in token"); } ``` ## 7. CORS 設定 ### 7.1 跨域政策配置 ```csharp services.AddCors(options => { options.AddPolicy("AllowFrontend", policy => { policy.WithOrigins("http://localhost:3000", "http://localhost:3001", "http://localhost:3002") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials() .SetPreflightMaxAge(TimeSpan.FromMinutes(5)); }); options.AddPolicy("AllowAll", policy => { policy.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); }); ``` ## 8. AI 服務整合 ### 8.1 AI 提供商架構 ```csharp // AI 提供商介面 public interface IAIProvider { Task AnalyzeSentenceAsync(string inputText, AnalysisOptions options); } // Gemini AI 實作 public class GeminiAIProvider : IAIProvider { private readonly HttpClient _httpClient; private readonly GeminiOptions _options; public async Task AnalyzeSentenceAsync(...) { // 調用 Google Gemini API // 處理回應和錯誤 // 返回標準化結果 } } ``` ### 8.2 AI 服務配置 ```csharp // 強型別配置 services.Configure(configuration.GetSection(GeminiOptions.SectionName)); services.AddSingleton, GeminiOptionsValidator>(); // AI 服務註冊 services.AddHttpClient(); services.AddScoped(); services.AddScoped(); ``` ## 9. 錯誤處理架構 ### 9.1 全域異常處理 ```csharp app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { var errorFeature = context.Features.Get(); if (errorFeature != null) { var logger = context.RequestServices.GetRequiredService>(); logger.LogError(errorFeature.Error, "Unhandled exception occurred"); context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; var response = new { success = false, error = "Internal server error", timestamp = DateTime.UtcNow }; await context.Response.WriteAsync(JsonSerializer.Serialize(response)); } }); }); ``` ### 9.2 控制器級錯誤處理 ```csharp try { var result = await flashcardsService.CreateFlashcard(data); return Ok(new { success = true, data = result }); } catch (ValidationException ex) { return BadRequest(new { success = false, error = ex.Message }); } catch (DbUpdateException ex) { _logger.LogError(ex, "Database error during flashcard creation"); return StatusCode(500, new { success = false, error = "Database operation failed" }); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error during flashcard creation"); return StatusCode(500, new { success = false, error = "Internal server error" }); } ``` ## 10. 開發與部署 ### 10.1 開發環境設定 #### 啟動開發伺服器 ```bash cd backend dotnet run --project DramaLing.Api # 伺服器運行於: http://localhost:5008 # Swagger UI: http://localhost:5008/swagger ``` #### 環境變數設定 ```bash export DRAMALING_DB_CONNECTION="Data Source=dramaling_test.db" export DRAMALING_SUPABASE_URL="https://localhost" export DRAMALING_SUPABASE_JWT_SECRET="dev-secret-minimum-32-characters-long-for-jwt-signing-in-development-mode-only" export USE_INMEMORY_DB="false" ``` ### 10.2 資料庫管理 #### Entity Framework 遷移 ```bash # 新增遷移 dotnet ef migrations add MigrationName # 更新資料庫 dotnet ef database update # 查看遷移狀態 dotnet ef migrations list ``` #### 測試資料初始化 ```csharp // 自動創建測試用戶 var testUser = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId); if (testUser == null) { testUser = new User { Id = userId, Email = "test@dramaling.com", Name = "Test User", CreatedAt = DateTime.UtcNow }; _context.Users.Add(testUser); await _context.SaveChangesAsync(); } ``` ## 11. 效能優化 ### 11.1 查詢優化 ```csharp // 使用 AsNoTracking 提升查詢效能 var flashcards = await _context.Flashcards .AsNoTracking() .Where(f => f.UserId == userId) .OrderByDescending(f => f.CreatedAt) .ToListAsync(); // 避免 N+1 查詢問題 var flashcardsWithDetails = await _context.Flashcards .Include(f => f.StudyRecords) .Include(f => f.CardSet) .Where(f => f.UserId == userId) .ToListAsync(); ``` ### 11.2 快取策略 ```csharp // 記憶體快取服務 services.AddMemoryCache(); services.AddScoped(); // 快取使用範例 var cacheKey = $"flashcards:user:{userId}"; var cachedCards = await _cacheService.GetAsync>(cacheKey); if (cachedCards == null) { cachedCards = await LoadFlashcardsFromDatabase(userId); await _cacheService.SetAsync(cacheKey, cachedCards, TimeSpan.FromMinutes(30)); } ``` ## 12. 安全性措施 ### 12.1 輸入驗證 ```csharp // 模型驗證特性 [Required, MaxLength(255)] public string Word { get; set; } // 控制器層驗證 if (!ModelState.IsValid) { return BadRequest(ModelState); } ``` ### 12.2 SQL 注入防護 ```csharp // Entity Framework 自動參數化查詢 var flashcards = _context.Flashcards .Where(f => f.Word.Contains(searchTerm)) // 自動參數化 .ToList(); ``` ### 12.3 XSS 防護 ```csharp // 自動 HTML 編碼 public string Definition { get; set; } // EF Core 自動處理 ``` ## 13. 監控與日誌 ### 13.1 結構化日誌 ```csharp _logger.LogInformation("Creating flashcard for user {UserId}, word: {Word}", userId, request.Word); _logger.LogError(ex, "Failed to create flashcard for user {UserId}", userId); ``` ### 13.2 健康檢查 ```csharp services.AddHealthChecks() .AddDbContextCheck(); app.MapHealthChecks("/health"); ``` --- **文檔版本**: v1.0 **建立日期**: 2025-09-24 **維護負責**: 後端開發團隊 **下次審核**: 架構變更時 > 📋 相關文檔: > - [系統架構總覽](./system-architecture.md) > - [前端架構詳細說明](./frontend-architecture.md) > - [詞卡 API 規格](./flashcard-api-specification.md)