dramaling-vocab-learning/docs/04_technical/backend-architecture.md

590 lines
16 KiB
Markdown

# 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)