# DramaLing API 開發指南 **版本**: 1.0 **最後更新**: 2025-09-30 **適用對象**: 後端開發者、新團隊成員 ## 🚀 快速開始 ### 開發環境要求 - **.NET 8 SDK** (最新 LTS 版本) - **Visual Studio Code** 或 **Visual Studio 2022** - **Git** 版本控制 - **SQLite** (開發環境) / **SQL Server** (生產環境) ### 環境變數配置 建立 `.env` 檔案或設定系統環境變數: ```bash # Gemini AI 配置 DRAMALING_GEMINI_API_KEY=your-gemini-api-key # Supabase 認證配置 DRAMALING_SUPABASE_URL=your-supabase-url DRAMALING_SUPABASE_JWT_SECRET=your-jwt-secret # Replicate AI 配置 DRAMALING_REPLICATE_API_TOKEN=your-replicate-token # Azure Speech 配置 DRAMALING_AZURE_SPEECH_KEY=your-azure-speech-key DRAMALING_AZURE_SPEECH_REGION=your-region # 資料庫連接 (可選,預設使用 SQLite) DRAMALING_DB_CONNECTION=your-connection-string # 測試環境 USE_INMEMORY_DB=true # 測試時使用記憶體資料庫 ``` --- ## 🏗️ 開發工作流程 ### 1. 專案設定 ```bash # Clone 專案 git clone cd dramaling-vocab-learning/backend/DramaLing.Api # 安裝相依套件 dotnet restore # 執行資料庫遷移 dotnet ef database update # 啟動開發伺服器 dotnet run # 訪問 Swagger UI open https://localhost:7001/swagger ``` ### 2. 開發分支策略 ```bash # 主要分支 main # 生產環境代碼 develop # 開發整合分支 # 功能分支命名規則 feature/user-auth # 新功能開發 bugfix/cache-issue # Bug 修復 hotfix/security-patch # 緊急修復 refactor/clean-arch # 重構改善 ``` --- ## 📝 編碼規範 ### 1. C# 編碼標準 **命名規則**: ```csharp // 類別和介面 - PascalCase public class FlashcardService { } public interface IFlashcardRepository { } // 方法和屬性 - PascalCase public async Task GetByIdAsync(Guid id) { } public string UserName { get; set; } // 私有欄位 - camelCase with underscore private readonly ILogger _logger; // 參數和區域變數 - camelCase public void ProcessData(string inputData) { var processedResult = Transform(inputData); } // 常數 - PascalCase public const int MaxRetryCount = 3; ``` **異步方法規範**: ```csharp // ✅ 正確:異步方法使用 Async 後綴 public async Task GetUserAsync(Guid userId) { } // ✅ 正確:使用 ConfigureAwait(false) 在類別庫中 var result = await httpClient.GetAsync(url).ConfigureAwait(false); // ❌ 錯誤:同步調用異步方法 var user = GetUserAsync(id).Result; // 可能導致死鎖 ``` ### 2. 資料夾和檔案組織 ``` Services/Domain/Feature/ ├── IFeatureService.cs # 介面定義 ├── FeatureService.cs # 實現 ├── FeatureModels.cs # 相關模型 (如果簡單) └── README.md # 服務說明 (複雜功能) Tests/Unit/Services/Domain/ └── FeatureServiceTests.cs # 對應測試 ``` ### 3. 文檔註解標準 ```csharp /// /// 根據使用者 ID 獲取單字卡列表 /// /// 使用者唯一識別碼 /// 搜尋關鍵字,可為空 /// 是否只顯示收藏的單字卡 /// 符合條件的單字卡列表 /// 當 userId 為空時拋出 public async Task> GetByUserIdAsync( Guid userId, string? search = null, bool favoritesOnly = false) { // 實作邏輯... } ``` --- ## 🧩 功能開發指南 ### 1. 新增 API 端點 **步驟 1: 定義 DTO** ```csharp // Models/DTOs/Feature/ public class CreateFeatureRequest { [Required] public string Name { get; set; } = string.Empty; [StringLength(500)] public string? Description { get; set; } } public class FeatureResponse { public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } } ``` **步驟 2: 建立 Repository (如需要)** ```csharp // Repositories/IFeatureRepository.cs public interface IFeatureRepository : IRepository { Task> GetByUserIdAsync(Guid userId); Task GetByNameAsync(string name); } // Repositories/FeatureRepository.cs public class FeatureRepository : BaseRepository, IFeatureRepository { public FeatureRepository(DramaLingDbContext context, ILogger> logger) : base(context, logger) { } public async Task> GetByUserIdAsync(Guid userId) { return await DbSet.Where(f => f.UserId == userId).ToListAsync(); } } ``` **步驟 3: 實作 Service** ```csharp // Services/Domain/IFeatureService.cs public interface IFeatureService { Task CreateAsync(CreateFeatureRequest request); Task> GetByUserIdAsync(Guid userId); } // Services/Domain/FeatureService.cs public class FeatureService : IFeatureService { private readonly IFeatureRepository _repository; private readonly ILogger _logger; public FeatureService(IFeatureRepository repository, ILogger logger) { _repository = repository; _logger = logger; } public async Task CreateAsync(CreateFeatureRequest request) { var feature = new Feature { Name = request.Name, Description = request.Description, CreatedAt = DateTime.UtcNow }; await _repository.AddAsync(feature); return new FeatureResponse { Id = feature.Id, Name = feature.Name, CreatedAt = feature.CreatedAt }; } } ``` **步驟 4: 建立 Controller** ```csharp [ApiController] [Route("api/[controller]")] [Authorize] public class FeatureController : ControllerBase { private readonly IFeatureService _featureService; public FeatureController(IFeatureService featureService) { _featureService = featureService; } /// /// 建立新功能 /// [HttpPost] public async Task> CreateFeature(CreateFeatureRequest request) { var result = await _featureService.CreateAsync(request); return CreatedAtAction(nameof(GetFeature), new { id = result.Id }, result); } } ``` **步驟 5: 註冊服務** ```csharp // Extensions/ServiceCollectionExtensions.cs public static IServiceCollection AddBusinessServices(this IServiceCollection services) { // ... 其他服務 services.AddScoped(); services.AddScoped(); return services; } ``` ### 2. 撰寫單元測試 ```csharp // Tests/Unit/Services/FeatureServiceTests.cs public class FeatureServiceTests : TestBase { private readonly IFeatureService _service; private readonly Mock _mockRepository; public FeatureServiceTests() { _mockRepository = new Mock(); _service = new FeatureService(_mockRepository.Object, Mock.Of>()); } [Fact] public async Task CreateAsync_ValidRequest_ShouldReturnFeatureResponse() { // Arrange var request = new CreateFeatureRequest { Name = "Test Feature" }; var expectedFeature = new Feature { Id = Guid.NewGuid(), Name = "Test Feature" }; _mockRepository.Setup(r => r.AddAsync(It.IsAny())) .Returns(Task.CompletedTask) .Callback(f => f.Id = expectedFeature.Id); // Act var result = await _service.CreateAsync(request); // Assert result.Should().NotBeNull(); result.Name.Should().Be("Test Feature"); _mockRepository.Verify(r => r.AddAsync(It.IsAny()), Times.Once); } } ``` --- ## 🧪 測試策略 ### 1. 測試分類 **單元測試** - 隔離測試個別類別 ```csharp [Fact] [Trait("Category", "Unit")] public async Task ServiceMethod_ValidInput_ReturnsExpectedResult() { // AAA 模式測試 } ``` **整合測試** - 測試多個組件協作 ```csharp [Fact] [Trait("Category", "Integration")] public async Task ApiEndpoint_ValidRequest_ReturnsCorrectResponse() { // 使用 TestServer 測試整個請求流程 } ``` **端到端測試** - 完整使用者場景 ```csharp [Fact] [Trait("Category", "E2E")] public async Task UserWorkflow_CompleteScenario_WorksCorrectly() { // 模擬真實使用者操作流程 } ``` ### 2. 測試執行 ```bash # 執行所有測試 dotnet test # 執行特定分類測試 dotnet test --filter "Category=Unit" dotnet test --filter "Category=Integration" # 產生覆蓋率報告 dotnet test --collect:"XPlat Code Coverage" # 執行特定測試類別 dotnet test --filter "ClassName=FeatureServiceTests" ``` --- ## 🐛 除錯與診斷 ### 1. 日誌記錄最佳實務 ```csharp public class FeatureService : IFeatureService { private readonly ILogger _logger; public async Task ProcessFeatureAsync(Guid featureId) { _logger.LogInformation("開始處理功能 {FeatureId}", featureId); try { var feature = await _repository.GetByIdAsync(featureId); if (feature == null) { _logger.LogWarning("功能不存在 {FeatureId}", featureId); throw new NotFoundException($"Feature {featureId} not found"); } // 處理邏輯... _logger.LogInformation("功能處理完成 {FeatureId}", featureId); return feature; } catch (Exception ex) { _logger.LogError(ex, "處理功能時發生錯誤 {FeatureId}", featureId); throw; } } } ``` ### 2. 效能監控 ```csharp // 使用 Stopwatch 監控關鍵操作 using var activity = Activity.StartActivity("ProcessFeature"); var stopwatch = Stopwatch.StartNew(); try { // 業務邏輯執行 var result = await ProcessComplexOperation(); stopwatch.Stop(); _logger.LogInformation("操作完成,耗時 {ElapsedMs}ms", stopwatch.ElapsedMilliseconds); return result; } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "操作失敗,耗時 {ElapsedMs}ms", stopwatch.ElapsedMilliseconds); throw; } ``` ### 3. 常見問題診斷 **問題**: 資料庫連接失敗 ```bash # 檢查連接字串 dotnet user-secrets list # 測試資料庫連接 dotnet ef database update --dry-run ``` **問題**: JWT 驗證失敗 ```csharp // 在 Startup/Program.cs 中啟用詳細日誌 builder.Logging.AddFilter("Microsoft.AspNetCore.Authentication", LogLevel.Debug); ``` **問題**: 快取不工作 ```csharp // 檢查快取配置和依賴注入 services.AddMemoryCache(); services.AddScoped(); ``` --- ## 🔧 工具和擴展 ### 1. 推薦 VS Code 擴展 ```json // .vscode/extensions.json { "recommendations": [ "ms-dotnettools.csharp", "ms-dotnettools.vscode-dotnet-runtime", "bradlc.vscode-tailwindcss", "esbenp.prettier-vscode", "ms-vscode.vscode-json", "humao.rest-client" ] } ``` ### 2. EditorConfig 設定 ```ini # .editorconfig root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.cs] indent_style = space indent_size = 4 [*.{json,yml,yaml}] indent_style = space indent_size = 2 ``` ### 3. Git 鉤子設定 ```bash # .githooks/pre-commit #!/bin/sh # 執行程式碼格式化 dotnet format --verify-no-changes # 執行測試 dotnet test --no-build --verbosity quiet # 執行靜態分析 dotnet build --verbosity quiet ``` --- ## 📚 學習資源 ### 1. 核心概念學習 - **Clean Architecture**: Robert C. Martin 的 Clean Architecture 書籍 - **Domain-Driven Design**: Eric Evans 的 DDD 經典著作 - **ASP.NET Core**: Microsoft 官方文檔 - **Entity Framework Core**: EF Core 官方指南 ### 2. 最佳實務參考 - **Microsoft .NET Application Architecture Guides** - **Clean Code**: Robert C. Martin - **Refactoring**: Martin Fowler - **Design Patterns**: Gang of Four ### 3. 社群資源 - **Stack Overflow**: 問題解決 - **GitHub**: 開源專案參考 - **Medium/Dev.to**: 技術部落格 - **YouTube**: 技術教學影片 --- ## ❓ 常見問題 FAQ ### Q: 如何新增一個新的 AI 服務? A: 1. 在 `Services/AI/` 下建立新的服務目錄 2. 實作服務介面和具體類別 3. 在 `ServiceCollectionExtensions.cs` 註冊服務 4. 撰寫單元測試 5. 更新 `Services/README.md` 文檔 ### Q: 資料庫遷移失敗怎麼辦? A: ```bash # 檢查遷移狀態 dotnet ef migrations list # 回滾到特定遷移 dotnet ef database update PreviousMigrationName # 重新產生遷移 dotnet ef migrations add NewMigrationName ``` ### Q: 如何優化 API 效能? A: 1. 使用異步方法 (`async/await`) 2. 實作適當的快取策略 3. 最佳化資料庫查詢 (避免 N+1) 4. 使用分頁載入大數據集 5. 啟用 HTTP 壓縮和快取標頭 ### Q: 如何處理敏感資訊? A: ```bash # 使用 User Secrets (開發環境) dotnet user-secrets set "ApiKey" "your-secret-key" # 使用環境變數 (生產環境) export DRAMALING_API_KEY="your-secret-key" # 絕不在程式碼中硬編碼敏感資訊 ``` --- ## 🤝 貢獻指南 ### 提交 Pull Request 前檢查清單 - [ ] 程式碼遵循編碼規範 - [ ] 所有測試通過 - [ ] 新功能有對應的測試 - [ ] 文檔已更新 - [ ] Commit 訊息清楚描述變更 - [ ] 沒有合併衝突 ### Commit 訊息格式 ``` 類型(範圍): 簡短描述 詳細描述(如果需要) - 變更項目 1 - 變更項目 2 Closes #123 ``` **類型標籤**: - `feat`: 新功能 - `fix`: Bug 修復 - `docs`: 文檔更新 - `style`: 程式碼格式化 - `refactor`: 重構 - `test`: 測試相關 - `chore`: 構建工具或輔助工具 --- **文檔版本**: 1.0 **維護者**: DramaLing 開發團隊 **最後更新**: 2025-09-30