# 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<StudyRecord> StudyRecords { get; set; }
    public virtual ICollection<FlashcardTag> FlashcardTags { get; set; }
    public virtual ICollection<ErrorReport> 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<FlashcardsController> _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<ActionResult> 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<IAuthService, AuthService>();

// 使用量追蹤
services.AddScoped<IUsageTrackingService, UsageTrackingService>();

// Azure 語音服務
services.AddScoped<IAzureSpeechService, AzureSpeechService>();

// 音頻快取
services.AddScoped<IAudioCacheService, AudioCacheService>();

// AI 提供商管理
services.AddScoped<IAIProviderManager, AIProviderManager>();
services.AddScoped<IAIProvider, GeminiAIProvider>();
```

## 5. 資料存取層

### 5.1 DbContext 配置

```csharp
public class DramaLingDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Flashcard> Flashcards { get; set; }
    public DbSet<CardSet> CardSets { get; set; }
    public DbSet<StudyRecord> StudyRecords { get; set; }
    public DbSet<ErrorReport> ErrorReports { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 詞卡配置
        modelBuilder.Entity<Flashcard>(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<DramaLingDbContext>(options =>
    options.UseSqlite(connectionString));
```

#### 記憶體資料庫 (測試用)
```csharp
var useInMemoryDb = Environment.GetEnvironmentVariable("USE_INMEMORY_DB") == "true";
if (useInMemoryDb)
{
    services.AddDbContext<DramaLingDbContext>(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<SentenceAnalysisResult> AnalyzeSentenceAsync(string inputText, AnalysisOptions options);
}

// Gemini AI 實作
public class GeminiAIProvider : IAIProvider
{
    private readonly HttpClient _httpClient;
    private readonly GeminiOptions _options;

    public async Task<SentenceAnalysisResult> AnalyzeSentenceAsync(...)
    {
        // 調用 Google Gemini API
        // 處理回應和錯誤
        // 返回標準化結果
    }
}
```

### 8.2 AI 服務配置

```csharp
// 強型別配置
services.Configure<GeminiOptions>(configuration.GetSection(GeminiOptions.SectionName));
services.AddSingleton<IValidateOptions<GeminiOptions>, GeminiOptionsValidator>();

// AI 服務註冊
services.AddHttpClient<GeminiAIProvider>();
services.AddScoped<IAIProvider, GeminiAIProvider>();
services.AddScoped<IAIProviderManager, AIProviderManager>();
```

## 9. 錯誤處理架構

### 9.1 全域異常處理

```csharp
app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
        if (errorFeature != null)
        {
            var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
            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<ICacheService, HybridCacheService>();

// 快取使用範例
var cacheKey = $"flashcards:user:{userId}";
var cachedCards = await _cacheService.GetAsync<List<Flashcard>>(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<DramaLingDbContext>();

app.MapHealthChecks("/health");
```

---

**文檔版本**: v1.0
**建立日期**: 2025-09-24
**維護負責**: 後端開發團隊
**下次審核**: 架構變更時

> 📋 相關文檔：
> - [系統架構總覽](./system-architecture.md)
> - [前端架構詳細說明](./frontend-architecture.md)
> - [詞卡 API 規格](./flashcard-api-specification.md)