diff --git a/NEXT_OPTIMIZATION_PLAN.md b/NEXT_OPTIMIZATION_PLAN.md new file mode 100644 index 0000000..bc56d7d --- /dev/null +++ b/NEXT_OPTIMIZATION_PLAN.md @@ -0,0 +1,261 @@ +# DramaLing 下階段優化計劃 + +## 📋 當前狀況分析 + +### ✅ 已完成的優化 (符合指南) +- Repository Pattern 基礎架構 +- AI 提供商抽象層 +- 智能快取策略架構 +- 安全中間件架構 (未啟用) +- 結構化錯誤處理 +- 前端性能優化工具 + +### ⚠️ 需要修正的問題 +- Repository 實作類型不匹配 (Guid vs string/int) +- 部分中間件編譯錯誤 +- 新功能暫時註解以確保穩定性 + +### ❌ 與指南的主要差異 + +## 🎯 Phase 1: 緊急修正 (1-2 天) + +### 1. **修正編譯錯誤** +**優先級**: 🔴 高 +```bash +# 需要修正的文件: +- /Middleware/AdvancedErrorHandlingMiddleware.cs (switch 表達式重複模式) +- /Controllers/OptimizedAIController.cs (FromCache 屬性) +- /Repositories/*.cs (Guid vs string 類型不匹配) +``` + +**預期效益**: 啟用所有新功能,系統穩定性提升 + +### 2. **業務服務層實作** +**優先級**: 🟡 中 +```csharp +// 需要建立: +public interface IAnalysisService +{ + Task AnalyzeSentenceAsync(string inputText, AnalysisOptions options); + Task GetCachedAnalysisAsync(string inputHash); + Task SaveAnalysisAsync(SentenceAnalysisData analysis); +} + +public interface IFlashcardService +{ + Task CreateFlashcardAsync(CreateFlashcardRequest request); + Task> GetUserFlashcardsAsync(Guid userId); + Task GetStudyRecommendationsAsync(Guid userId); +} +``` + +**預期效益**: 業務邏輯分離,可測試性提升 60% + +### 3. **啟用優化功能** +**優先級**: 🟡 中 +```csharp +// Program.cs 中啟用: +builder.Services.AddScoped(); +builder.Services.AddScoped(); +app.UseMiddleware(); +``` + +**預期效益**: AI API 成本降低 60-80%,響應速度提升 40-60% + +## 🚀 Phase 2: 架構完善 (1-2 週) + +### 1. **測試框架建立** +**目標覆蓋率**: 80%+ +``` +/Tests/ +├── Unit/ # 單元測試 (70%) +│ ├── Services/ +│ ├── Repositories/ +│ └── AI/ +├── Integration/ # 整合測試 (20%) +│ ├── Controllers/ +│ └── Database/ +└── E2E/ # 端到端測試 (10%) + └── AI_Analysis_Flow/ +``` + +### 2. **監控和可觀測性** +```csharp +// 需要實作: +- Metrics 收集器 (Prometheus/OpenTelemetry) +- 結構化日誌 (Serilog with ELK Stack) +- 分散式追蹤 (Jaeger/Zipkin) +- 自動告警系統 +``` + +### 3. **性能監控儀表板** +``` +監控指標: +- API 響應時間分佈 +- AI 提供商性能比較 +- 快取命中率趨勢 +- 錯誤率和類型分析 +- 使用者行為分析 +``` + +## 📈 Phase 3: 進階優化 (1-2 月) + +### 1. **微服務準備** +``` +領域拆分: +├── AI.Service (句子分析、AI 管理) +├── User.Service (用戶管理、認證) +├── Learning.Service (詞卡、學習記錄) +└── Analytics.Service (統計、推薦) +``` + +### 2. **事件驅動架構** +```csharp +// 事件系統: +public interface IEventBus +{ + Task PublishAsync(T eventData) where T : IDomainEvent; + Task SubscribeAsync(Func handler) where T : IDomainEvent; +} + +// 領域事件: +- AnalysisCompletedEvent +- FlashcardCreatedEvent +- UserLevelUpdatedEvent +- StudySessionEndedEvent +``` + +### 3. **AI 能力擴展** +``` +多提供商支援: +├── OpenAI GPT-4 Provider +├── Anthropic Claude Provider +├── 本地模型 Provider (Ollama) +└── 批次處理和請求合併 +``` + +## 🛠️ 具體優化建議 + +### **立即優化 (今天)** + +1. **修正類型問題** +```csharp +// 統一 ID 類型使用 +public interface IFlashcardRepository : IRepository +{ + Task> GetFlashcardsByUserIdAsync(Guid userId); + Task> GetFlashcardsByCardSetIdAsync(Guid cardSetId); + // ... 其他方法使用正確的 Guid 類型 +} +``` + +2. **簡化 Repository 實作** +```csharp +// 暫時使用簡化版本,專注於核心功能 +public class FlashcardRepository : BaseRepository +{ + // 只實作最重要的方法,避免複雜的關聯查詢 + public async Task> GetByUserIdAsync(Guid userId) + { + return await _dbSet.AsNoTracking() + .Where(f => f.UserId == userId && !f.IsArchived) + .ToListAsync(); + } +} +``` + +### **週內優化** + +1. **業務服務層** +```csharp +public class AnalysisService : IAnalysisService +{ + private readonly IAIProviderManager _aiProviderManager; + private readonly ICacheService _cacheService; + private readonly IAnalysisRepository _repository; + + public async Task AnalyzeSentenceAsync( + string inputText, AnalysisOptions options) + { + // 1. 快取檢查 + var cacheKey = GenerateCacheKey(inputText, options); + var cached = await _cacheService.GetAsync(cacheKey); + if (cached != null) return cached; + + // 2. AI 分析 + var result = await _aiProviderManager.AnalyzeSentenceAsync(inputText, options); + + // 3. 快取存儲 + await _cacheService.SetAsync(cacheKey, result); + + // 4. 持久化 (可選) + await _repository.SaveAnalysisAsync(result); + + return result; + } +} +``` + +2. **健康檢查端點** +```csharp +app.MapHealthChecks("/health", new HealthCheckOptions +{ + ResponseWriter = async (context, report) => + { + context.Response.ContentType = "application/json"; + var response = new + { + status = report.Status.ToString(), + checks = report.Entries.Select(x => new + { + name = x.Key, + status = x.Value.Status.ToString(), + exception = x.Value.Exception?.Message, + duration = x.Value.Duration.TotalMilliseconds + }) + }; + await context.Response.WriteAsync(JsonSerializer.Serialize(response)); + } +}); +``` + +## 📊 優化效益預估 + +### **Phase 1 完成後** +- **系統穩定性**: ↑ 30-50% +- **代碼可維護性**: ↑ 40-60% +- **AI API 成本**: ↓ 60-80% +- **響應時間**: ↓ 40-60% + +### **Phase 2 完成後** +- **測試覆蓋率**: 0% → 80%+ +- **問題發現時間**: ↓ 70-80% +- **部署信心**: ↑ 顯著提升 +- **監控可視性**: ↑ 90%+ + +### **Phase 3 完成後** +- **系統擴展性**: ↑ 支援 10x 用戶成長 +- **微服務就緒**: ✅ 完全準備 +- **多 AI 提供商**: ✅ 避免供應商鎖定 +- **事件驅動**: ✅ 高度解耦和彈性 + +## 🎯 實施優先順序 + +### **🔴 立即執行 (今天)** +1. 修正編譯錯誤,啟用新功能 +2. 測試 AI 分析端點正常運作 +3. 驗證快取機制工作正常 + +### **🟡 本週完成** +1. 建立業務服務層 +2. 完善健康檢查系統 +3. 建立基礎單元測試 + +### **🟢 月內完成** +1. 完整測試覆蓋率 +2. 監控儀表板 +3. 性能基準測試 + +--- + +**總結**: 您的系統已經有了堅實的優化基礎,主要需要修正一些技術細節並逐步啟用新功能。按照這個計劃執行,可以將系統提升到企業級標準。 \ No newline at end of file diff --git a/backend/DramaLing.Api/Controllers/AIController.cs b/backend/DramaLing.Api/Controllers/AIController.cs index d101167..578fad9 100644 --- a/backend/DramaLing.Api/Controllers/AIController.cs +++ b/backend/DramaLing.Api/Controllers/AIController.cs @@ -10,14 +10,14 @@ namespace DramaLing.Api.Controllers; [Route("api/ai")] public class AIController : ControllerBase { - private readonly IGeminiService _geminiService; + private readonly IAnalysisService _analysisService; private readonly ILogger _logger; public AIController( - IGeminiService geminiService, + IAnalysisService analysisService, ILogger logger) { - _geminiService = geminiService; + _analysisService = analysisService; _logger = logger; } @@ -50,9 +50,9 @@ public class AIController : ControllerBase requestId)); } - // 直接執行 AI 分析,不使用快取 + // 使用帶快取的分析服務 var options = request.Options ?? new AnalysisOptions(); - var analysisData = await _geminiService.AnalyzeSentenceAsync( + var analysisData = await _analysisService.AnalyzeSentenceAsync( request.InputText, options); stopwatch.Stop(); @@ -101,6 +101,37 @@ public class AIController : ControllerBase }); } + /// + /// 取得分析統計資訊 + /// + [HttpGet("stats")] + [AllowAnonymous] + public async Task GetAnalysisStats() + { + try + { + var stats = await _analysisService.GetAnalysisStatsAsync(); + return Ok(new + { + Success = true, + Data = new + { + TotalAnalyses = stats.TotalAnalyses, + CachedAnalyses = stats.CachedAnalyses, + CacheHitRate = stats.CacheHitRate, + AverageResponseTimeMs = stats.AverageResponseTimeMs, + LastAnalysisAt = stats.LastAnalysisAt, + ProviderUsage = stats.ProviderUsageStats + } + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting analysis stats"); + return StatusCode(500, new { Success = false, Error = "無法取得統計資訊" }); + } + } + private ApiErrorResponse CreateErrorResponse(string code, string message, object? details, string requestId) { var suggestions = GetSuggestionsForError(code); diff --git a/backend/DramaLing.Api/Middleware/SecurityMiddleware.cs b/backend/DramaLing.Api/Middleware/SecurityMiddleware.cs index d1d16ac..6b27361 100644 --- a/backend/DramaLing.Api/Middleware/SecurityMiddleware.cs +++ b/backend/DramaLing.Api/Middleware/SecurityMiddleware.cs @@ -85,7 +85,7 @@ public class SecurityMiddleware #region 速率限制 - private async Task CheckRateLimitAsync(string clientId, string requestId) + private Task CheckRateLimitAsync(string clientId, string requestId) { try { @@ -100,18 +100,18 @@ public class SecurityMiddleware { _logger.LogWarning("Rate limit exceeded for client {ClientId}, request {RequestId}", clientId, requestId); - return false; + return Task.FromResult(false); } // 記錄此次請求 clientLimit.Requests.Add(now); - return true; + return Task.FromResult(true); } catch (Exception ex) { _logger.LogError(ex, "Error checking rate limit for client {ClientId}", clientId); - return true; // 錯誤時允許通過,避免服務中斷 + return Task.FromResult(true); // 錯誤時允許通過,避免服務中斷 } } diff --git a/backend/DramaLing.Api/Program.cs b/backend/DramaLing.Api/Program.cs index e5aaefa..3ddff34 100644 --- a/backend/DramaLing.Api/Program.cs +++ b/backend/DramaLing.Api/Program.cs @@ -66,7 +66,7 @@ else // Caching Services builder.Services.AddMemoryCache(); -// builder.Services.AddScoped(); +builder.Services.AddScoped(); // AI Services // builder.Services.AddHttpClient(); @@ -76,7 +76,8 @@ builder.Services.AddMemoryCache(); // Custom Services builder.Services.AddScoped(); builder.Services.AddHttpClient(); -// 快取系統已移除,每次都直接調用 AI API +// 新增帶快取的分析服務 +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/backend/DramaLing.Api/Repositories/UserRepository.cs b/backend/DramaLing.Api/Repositories/UserRepository.cs index d6831d0..a524c25 100644 --- a/backend/DramaLing.Api/Repositories/UserRepository.cs +++ b/backend/DramaLing.Api/Repositories/UserRepository.cs @@ -112,7 +112,7 @@ public class UserRepository : BaseRepository, IUserRepository #region 學習進度統計 - public async Task> GetUserLearningStatsAsync(Guid userId) + public Task> GetUserLearningStatsAsync(Guid userId) { try { @@ -125,7 +125,7 @@ public class UserRepository : BaseRepository, IUserRepository ["TotalStudyTimeSeconds"] = 0 }; - return stats; + return Task.FromResult(stats); } catch (Exception ex) { @@ -134,11 +134,11 @@ public class UserRepository : BaseRepository, IUserRepository } } - public async Task GetTotalStudyTimeAsync(Guid userId) + public Task GetTotalStudyTimeAsync(Guid userId) { try { - return 0; // 簡化實作 + return Task.FromResult(0); } catch (Exception ex) { @@ -147,11 +147,11 @@ public class UserRepository : BaseRepository, IUserRepository } } - public async Task GetLastActivityDateAsync(Guid userId) + public Task GetLastActivityDateAsync(Guid userId) { try { - return DateTime.UtcNow; // 簡化實作 + return Task.FromResult(DateTime.UtcNow); } catch (Exception ex) { diff --git a/backend/DramaLing.Api/Services/AnalysisService.cs b/backend/DramaLing.Api/Services/AnalysisService.cs new file mode 100644 index 0000000..b49fdf4 --- /dev/null +++ b/backend/DramaLing.Api/Services/AnalysisService.cs @@ -0,0 +1,138 @@ +using DramaLing.Api.Models.DTOs; +using DramaLing.Api.Services.Caching; +using System.Security.Cryptography; +using System.Text; +using System.Diagnostics; + +namespace DramaLing.Api.Services; + +/// +/// 分析服務實作,整合快取和 AI 服務 +/// +public class AnalysisService : IAnalysisService +{ + private readonly IGeminiService _geminiService; + private readonly ICacheService _cacheService; + private readonly ILogger _logger; + private static readonly AnalysisStats _stats = new(); + + public AnalysisService( + IGeminiService geminiService, + ICacheService cacheService, + ILogger logger) + { + _geminiService = geminiService ?? throw new ArgumentNullException(nameof(geminiService)); + _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task AnalyzeSentenceAsync(string inputText, AnalysisOptions options) + { + var stopwatch = Stopwatch.StartNew(); + var cacheKey = GenerateCacheKey(inputText, options); + + try + { + _logger.LogInformation("Starting analysis for text: {Text} (cache key: {CacheKey})", + inputText.Substring(0, Math.Min(50, inputText.Length)), cacheKey); + + // 1. 快取檢查 + var cachedResult = await _cacheService.GetAsync(cacheKey); + if (cachedResult != null) + { + stopwatch.Stop(); + _stats.CachedAnalyses++; + _stats.TotalAnalyses++; + + _logger.LogInformation("Cache hit for analysis in {ElapsedMs}ms", stopwatch.ElapsedMilliseconds); + return cachedResult; + } + + // 2. 快取未命中,執行 AI 分析 + _logger.LogInformation("Cache miss, calling AI service"); + var analysisResult = await _geminiService.AnalyzeSentenceAsync(inputText, options); + + // 3. 存入快取 + await _cacheService.SetAsync(cacheKey, analysisResult, TimeSpan.FromHours(2)); + + // 4. 更新統計 + stopwatch.Stop(); + _stats.TotalAnalyses++; + _stats.LastAnalysisAt = DateTime.UtcNow; + UpdateAverageResponseTime((int)stopwatch.ElapsedMilliseconds); + + _logger.LogInformation("AI analysis completed and cached in {ElapsedMs}ms", stopwatch.ElapsedMilliseconds); + + return analysisResult; + } + catch (Exception ex) + { + stopwatch.Stop(); + _logger.LogError(ex, "Error in analysis service for text: {Text}", inputText); + throw; + } + } + + public async Task HasCachedAnalysisAsync(string inputText, AnalysisOptions options) + { + try + { + var cacheKey = GenerateCacheKey(inputText, options); + return await _cacheService.ExistsAsync(cacheKey); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error checking cache existence"); + return false; + } + } + + public async Task ClearAnalysisCacheAsync(string inputText, AnalysisOptions options) + { + try + { + var cacheKey = GenerateCacheKey(inputText, options); + return await _cacheService.RemoveAsync(cacheKey); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error clearing analysis cache"); + return false; + } + } + + public Task GetAnalysisStatsAsync() + { + _stats.ProviderUsageStats["Gemini"] = _stats.TotalAnalyses - _stats.CachedAnalyses; + return Task.FromResult(_stats); + } + + #region 私有方法 + + private string GenerateCacheKey(string inputText, AnalysisOptions options) + { + // 根據輸入和選項生成穩定的快取鍵 + var optionsString = $"{options.IncludeGrammarCheck}_{options.IncludeVocabularyAnalysis}_{options.IncludeIdiomDetection}"; + var combinedInput = $"{inputText}_{optionsString}"; + + using var sha256 = SHA256.Create(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combinedInput)); + var hash = Convert.ToHexString(hashBytes)[..16]; + + return $"analysis:{hash}"; + } + + private void UpdateAverageResponseTime(int responseTimeMs) + { + if (_stats.AverageResponseTimeMs == 0) + { + _stats.AverageResponseTimeMs = responseTimeMs; + } + else + { + _stats.AverageResponseTimeMs = (_stats.AverageResponseTimeMs + responseTimeMs) / 2; + } + } + + #endregion +} \ No newline at end of file diff --git a/backend/DramaLing.Api/Services/Caching/HybridCacheService.cs b/backend/DramaLing.Api/Services/Caching/HybridCacheService.cs index dcac870..dd12fe6 100644 --- a/backend/DramaLing.Api/Services/Caching/HybridCacheService.cs +++ b/backend/DramaLing.Api/Services/Caching/HybridCacheService.cs @@ -18,8 +18,8 @@ public class HybridCacheService : ICacheService public HybridCacheService( IMemoryCache memoryCache, - IDistributedCache? distributedCache, - ILogger logger) + ILogger logger, + IDistributedCache? distributedCache = null) { _memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache)); _distributedCache = distributedCache; diff --git a/backend/DramaLing.Api/Services/IAnalysisService.cs b/backend/DramaLing.Api/Services/IAnalysisService.cs new file mode 100644 index 0000000..615c300 --- /dev/null +++ b/backend/DramaLing.Api/Services/IAnalysisService.cs @@ -0,0 +1,52 @@ +using DramaLing.Api.Models.DTOs; + +namespace DramaLing.Api.Services; + +/// +/// 分析服務介面,封裝 AI 分析的業務邏輯 +/// +public interface IAnalysisService +{ + /// + /// 智能分析英文句子,包含快取策略 + /// + /// 輸入文本 + /// 分析選項 + /// 分析結果 + Task AnalyzeSentenceAsync(string inputText, AnalysisOptions options); + + /// + /// 檢查快取是否存在 + /// + /// 輸入文本 + /// 分析選項 + /// 是否有快取 + Task HasCachedAnalysisAsync(string inputText, AnalysisOptions options); + + /// + /// 清除特定分析的快取 + /// + /// 輸入文本 + /// 分析選項 + /// 是否成功 + Task ClearAnalysisCacheAsync(string inputText, AnalysisOptions options); + + /// + /// 取得分析統計資訊 + /// + /// 統計資訊 + Task GetAnalysisStatsAsync(); +} + +/// +/// 分析統計資訊 +/// +public class AnalysisStats +{ + public int TotalAnalyses { get; set; } + public int CachedAnalyses { get; set; } + public double CacheHitRate => TotalAnalyses > 0 ? (double)CachedAnalyses / TotalAnalyses : 0; + public int AverageResponseTimeMs { get; set; } + public DateTime LastAnalysisAt { get; set; } + public Dictionary ProviderUsageStats { get; set; } = new(); +} \ No newline at end of file diff --git a/docs/05_deployment/AI驅動產品後端技術架構指南.md b/docs/05_deployment/AI驅動產品後端技術架構指南.md new file mode 100644 index 0000000..35bd175 --- /dev/null +++ b/docs/05_deployment/AI驅動產品後端技術架構指南.md @@ -0,0 +1,2293 @@ +# AI驅動產品後端技術架構指南 + +## 📋 **文件資訊** + +- **文件名稱**: AI驅動產品後端技術架構指南 +- **版本**: v1.0 +- **建立日期**: 2025-01-25 +- **最後更新**: 2025-01-25 +- **負責團隊**: DramaLing技術架構團隊 +- **適用產品**: AI驅動的語言學習、內容分析、智能推薦等產品 + +--- + +## 🎯 **架構設計原則** + +### **核心設計理念** + +#### **高效率 (High Performance)** +```yaml +響應速度: + - API響應時間 < 500ms (不含AI處理) + - AI處理時間 < 5秒 + - 資料庫查詢 < 100ms + - 快取命中率 > 80% + +並發處理: + - 支援1000+併發請求 + - 優雅降級機制 + - 資源池化管理 + - 非同步處理優先 +``` + +#### **好維護 (Maintainability)** +```yaml +程式碼品質: + - 單一職責原則 (SRP) + - 依賴注入 (DI) + - 介面隔離 (ISP) + - 開放封閉原則 (OCP) + +文檔完整性: + - API文檔自動生成 + - 程式碼註釋覆蓋率 > 60% + - 架構決策記錄 (ADR) + - 部署流程文檔化 +``` + +#### **穩定性 (Stability)** +```yaml +錯誤處理: + - 全局異常處理 + - 優雅降級策略 + - 重試機制設計 + - 斷路器模式 + +監控告警: + - 健康檢查端點 + - 關鍵指標監控 + - 自動告警機制 + - 日誌追蹤完整 +``` + +--- + +## 🏗️ **分層架構設計** + +### **整體架構圖** + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 🌐 API Gateway │ +│ (Authentication, Rate Limiting) │ +└─────────────────────┬───────────────────────────────────────┘ + │ +┌─────────────────────▼───────────────────────────────────────┐ +│ 📡 Controllers Layer │ +│ (Request Handling, Validation) │ +└─────────────────────┬───────────────────────────────────────┘ + │ +┌─────────────────────▼───────────────────────────────────────┐ +│ 🔧 Services Layer │ +│ (Business Logic, AI Integration) │ +└─────────────────────┬───────────────────────────────────────┘ + │ +┌─────────────────────▼───────────────────────────────────────┐ +│ 💾 Data Access Layer │ +│ (Repository Pattern, EF Core) │ +└─────────────────────┬───────────────────────────────────────┘ + │ +┌─────────────────────▼───────────────────────────────────────┐ +│ 🗄️ Data Storage Layer │ +│ (Database, Cache, External APIs) │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 🔄 **程式碼運作流程圖** + +### **AI 句子分析完整流程** + +``` +🌐 前端用戶請求 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 📱 Next.js Frontend (http://localhost:3000) │ +│ │ +│ generatePage.tsx: │ +│ └─ handleAnalyzeSentence() │ +│ ├─ 驗證輸入長度 (MAX_MANUAL_INPUT_LENGTH = 300) │ +│ ├─ 構建請求體 { inputText, options } │ +│ └─ fetch('http://localhost:5008/api/ai/analyze-sentence') │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🚀 .NET 8 Backend API (http://localhost:5008) │ +│ │ +│ 1️⃣ SecurityMiddleware (未啟用) │ +│ ├─ 速率限制檢查 │ +│ ├─ 惡意輸入檢測 │ +│ └─ 安全標頭添加 │ +│ │ +│ 2️⃣ ErrorHandlingMiddleware │ +│ ├─ 全局異常捕獲 │ +│ ├─ 結構化錯誤回應 │ +│ └─ 錯誤日誌記錄 │ +│ │ +│ 3️⃣ AIController.AnalyzeSentence() │ +│ ├─ 請求驗證 (ModelState.IsValid) │ +│ ├─ 生成 requestId = Guid.NewGuid() │ +│ ├─ 開始計時 (Stopwatch.StartNew()) │ +│ └─ 調用 _geminiService.AnalyzeSentenceAsync() │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🤖 GeminiService 層 │ +│ │ +│ GeminiService.AnalyzeSentenceAsync(): │ +│ ├─ 1. 構建結構化 Prompt (lines 42-95) │ +│ │ ├─ 包含完整 JSON Schema 定義 │ +│ │ ├─ 指定分析指南 (語法檢查、詞彙分析、CEFR等級) │ +│ │ └─ 明確要求只返回 JSON │ +│ │ │ +│ ├─ 2. CallGeminiAPI() (lines 330-418) │ +│ │ ├─ 構建請求體 (contents, generationConfig) │ +│ │ ├─ HTTP POST 到 Gemini API │ +│ │ ├─ 解析 JSON 回應結構 │ +│ │ └─ 提取 candidates[0].content.parts[0].text │ +│ │ │ +│ └─ 3. CreateAnalysisFromAIResponse() (lines 128-182) │ +│ ├─ 清理 AI 回應 (移除 ```json 標記) │ +│ ├─ JSON 反序列化為 AiAnalysisResponse │ +│ ├─ 轉換為 SentenceAnalysisData DTO │ +│ └─ 錯誤時使用 CreateFallbackAnalysis() │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🌍 Google Gemini API │ +│ │ +│ Endpoint: generativelanguage.googleapis.com │ +│ Model: gemini-1.5-flash │ +│ ├─ 接收結構化 Prompt │ +│ ├─ 執行 AI 推理 (3-5秒) │ +│ ├─ 返回 JSON 格式分析結果 │ +│ └─ 包含: 翻譯、語法檢查、詞彙分析、慣用語 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ (回應路徑) +┌─────────────────────────────────────────────────────────────┐ +│ 📤 回應處理流程 │ +│ │ +│ 1️⃣ GeminiService 回應處理: │ +│ ├─ 解析 AI JSON 回應 │ +│ ├─ 轉換資料格式 (DTO 映射) │ +│ ├─ 設定 Metadata (處理時間、模型版本) │ +│ └─ 返回 SentenceAnalysisData │ +│ │ +│ 2️⃣ AIController 回應包裝: │ +│ ├─ 停止計時器 │ +│ ├─ 記錄性能日誌 │ +│ ├─ 包裝為 SentenceAnalysisResponse │ +│ └─ 返回 HTTP 200 OK │ +│ │ +│ 3️⃣ 前端處理回應: │ +│ ├─ 檢查 response.ok 和 result.success │ +│ ├─ 設定狀態 setSentenceAnalysis(apiData) │ +│ ├─ 處理語法修正 setGrammarCorrection() │ +│ ├─ 渲染 ClickableTextV2 組件 │ +│ └─ 顯示翻譯和慣用語 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### **優化後的流程 (目標架構)** + +``` +🌐 前端用戶請求 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 📱 Next.js Frontend + 性能優化 │ +│ │ +│ 使用 /frontend/lib/performance/: │ +│ ├─ cachedApiCall() - 本地快取檢查 │ +│ ├─ debounce() - 防止重複請求 │ +│ ├─ PerformanceMonitor - 性能追蹤 │ +│ └─ 快取命中 = 直接返回,無需 API 調用 │ +└─────────────────────────┬───────────────────────────────────┘ + │ (快取未命中) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🛡️ 安全與路由層 │ +│ │ +│ 1️⃣ SecurityMiddleware: │ +│ ├─ CheckRateLimit() - 檢查請求頻率 │ +│ ├─ ValidateInputSafety() - 惡意模式檢測 │ +│ ├─ 請求大小驗證 │ +│ └─ 添加安全標頭 │ +│ │ +│ 2️⃣ 路由到 /api/v2/ai/analyze-sentence │ +│ └─ OptimizedAIController.AnalyzeSentenceOptimized() │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🚀 優化的控制器層 │ +│ │ +│ OptimizedAIController: │ +│ ├─ 1. 生成快取鍵 GenerateCacheKey() │ +│ │ └─ SHA256(inputText + options) → "analysis:abc123" │ +│ │ │ +│ ├─ 2. 快取檢查 _cacheService.GetAsync(cacheKey) │ +│ │ ├─ L1: Memory Cache 檢查 (< 1ms) │ +│ │ ├─ L2: Distributed Cache 檢查 (Redis, < 10ms) │ +│ │ └─ 快取命中 → 直接返回結果 + FromCache: true │ +│ │ │ +│ └─ 3. 快取未命中 → 調用 AI 服務 │ +│ └─ _aiProviderManager.AnalyzeSentenceAsync() │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🎯 AI 提供商管理層 │ +│ │ +│ AIProviderManager: │ +│ ├─ 1. GetBestProviderAsync(ProviderSelectionStrategy) │ +│ │ ├─ Performance: 選擇響應時間最快的 │ +│ │ ├─ Cost: 選擇成本最低的 │ +│ │ ├─ Reliability: 選擇成功率最高的 │ +│ │ └─ LoadBalance: 隨機負載均衡 │ +│ │ │ +│ ├─ 2. 健康檢查 CheckHealthAsync() │ +│ │ └─ 過濾不可用的提供商 │ +│ │ │ +│ ├─ 3. 故障轉移機制 │ +│ │ ├─ 主提供商失敗 → 自動切換備用提供商 │ +│ │ ├─ 記錄失敗統計 │ +│ │ └─ 更新提供商健康狀態 │ +│ │ │ +│ └─ 4. 調用選定的 IAIProvider.AnalyzeSentenceAsync() │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🤖 具體 AI 提供商 (GeminiAIProvider) │ +│ │ +│ 實施 IAIProvider 介面: │ +│ ├─ ProviderName: "Google Gemini" │ +│ ├─ IsAvailable: 檢查 API Key 配置 │ +│ ├─ CostPerRequest: 0.001m │ +│ └─ AverageResponseTimeMs: 統計平均響應時間 │ +│ │ +│ AnalyzeSentenceAsync() 執行流程: │ +│ ├─ 1. BuildAnalysisPrompt() - 構建結構化提示詞 │ +│ ├─ 2. CallGeminiAPIAsync() - HTTP 調用 │ +│ │ ├─ 請求統計 _stats.TotalRequests++ │ +│ │ ├─ 計時開始 Stopwatch.StartNew() │ +│ │ ├─ HTTP POST 到 Gemini API │ +│ │ └─ ExtractTextFromResponse() 解析回應 │ +│ │ │ +│ ├─ 3. ParseAIResponse() - 解析和轉換 │ +│ │ ├─ CleanAIResponse() - 清理格式 │ +│ │ ├─ JsonSerializer.Deserialize() │ +│ │ ├─ ConvertVocabularyAnalysis() │ +│ │ ├─ ConvertIdioms() │ +│ │ └─ ConvertGrammarCorrection() │ +│ │ │ +│ └─ 4. 統計記錄 RecordSuccessfulRequest() │ +│ ├─ 更新成功率統計 │ +│ ├─ 計算平均響應時間 │ +│ └─ 記錄成本追蹤 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ (成功回應) +┌─────────────────────────────────────────────────────────────┐ +│ 💾 快取與回應處理 │ +│ │ +│ HybridCacheService.SetAsync(): │ +│ ├─ 1. 計算智能過期時間 │ +│ │ ├─ analysis: → 2小時 │ +│ │ ├─ user: → 30分鐘 │ +│ │ └─ stats: → 5分鐘 │ +│ │ │ +│ ├─ 2. 多層快取存儲 │ +│ │ ├─ L1: MemoryCache.Set() (< 1ms) │ +│ │ └─ L2: DistributedCache.SetAsync() (< 10ms) │ +│ │ │ +│ └─ 3. 控制器回應格式化 │ +│ ├─ 包裝 SentenceAnalysisResponse │ +│ ├─ 添加處理時間和 FromCache 標記 │ +│ └─ HTTP 200 OK 返回 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🎨 前端 UI 渲染 │ +│ │ +│ response 處理流程: │ +│ ├─ 1. 解析 API 回應 │ +│ │ ├─ setSentenceAnalysis(analysisData) │ +│ │ ├─ setSentenceMeaning(翻譯) │ +│ │ └─ setGrammarCorrection(語法修正) │ +│ │ │ +│ ├─ 2. 計算詞彙統計 useMemo() │ +│ │ ├─ simpleCount (使用者程度以下) │ +│ │ ├─ moderateCount (適合學習) │ +│ │ ├─ difficultCount (具挑戰性) │ +│ │ └─ idiomCount (慣用語數量) │ +│ │ │ +│ ├─ 3. 渲染 ClickableTextV2 組件 │ +│ │ ├─ 每個詞彙點擊顯示詳細資訊 │ +│ │ ├─ handleSaveWord() 保存到詞卡 │ +│ │ └─ 顏色編碼顯示難度級別 │ +│ │ │ +│ └─ 4. 顯示統計卡片和翻譯 │ +│ ├─ 四色卡片統計 (簡單/適中/困難/慣用語) │ +│ ├─ 中文翻譯展示 │ +│ └─ 慣用語點擊彈窗 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### **Repository Pattern 數據流程** + +``` +🔍 數據查詢請求 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🎯 業務服務層 (未來實作) │ +│ │ +│ FlashcardService (規劃中): │ +│ ├─ GetUserFlashcards(userId) │ +│ ├─ CreateFlashcard(cardData) │ +│ ├─ UpdateMasteryLevel(flashcardId, level) │ +│ └─ GetStudyRecommendations(userId) │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 📁 Repository 抽象層 │ +│ │ +│ IFlashcardRepository: │ +│ ├─ GetFlashcardsByUserIdAsync(Guid userId) │ +│ ├─ GetDueFlashcardsAsync(userId, dueDate) │ +│ ├─ GetFlashcardsByDifficultyAsync(userId, level) │ +│ ├─ SearchFlashcardsAsync(userId, searchTerm) │ +│ ├─ GetTotalFlashcardsCountAsync(userId) │ +│ └─ BulkUpdateMasteryLevelAsync(flashcardIds, level) │ +│ │ +│ 實作選擇: │ +│ ├─ SimpleFlashcardRepository (目前使用) │ +│ └─ FlashcardRepository (完整功能版本) │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🗃️ BaseRepository 泛型實作 │ +│ │ +│ 查詢優化策略: │ +│ ├─ AsNoTracking() - 只讀查詢,提升 40-60% 性能 │ +│ ├─ 投影查詢 Select() - 只查詢需要的欄位 │ +│ ├─ 分頁查詢 GetPagedAsync() - 大數據集處理 │ +│ ├─ 批次操作 AddRangeAsync() - 減少資料庫往返 │ +│ └─ 異常處理和日誌記錄 │ +│ │ +│ 核心方法: │ +│ ├─ GetByIdAsync(id) │ +│ ├─ FindAsync(Expression>) │ +│ ├─ FirstOrDefaultAsync(predicate) │ +│ ├─ AddAsync(entity) / UpdateAsync(entity) │ +│ └─ SaveChangesAsync() - 工作單元模式 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🗄️ Entity Framework Core + SQLite │ +│ │ +│ DramaLingDbContext: │ +│ ├─ 實體映射 (User, Flashcard, CardSet, StudySession...) │ +│ ├─ 關係配置 (一對多、多對多) │ +│ ├─ 索引優化 (複合索引、唯一索引) │ +│ ├─ 資料庫遷移管理 │ +│ └─ 連接池管理 │ +│ │ +│ 性能優化: │ +│ ├─ 索引策略 HasIndex() │ +│ ├─ 查詢編譯快取 │ +│ ├─ 批次操作支援 │ +│ └─ 連接字串優化 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### **AI 提供商選擇流程** + +``` +📞 AI 分析請求 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🎛️ AIProviderManager.GetBestProviderAsync() │ +│ │ +│ 選擇策略決策樹: │ +│ │ +│ ProviderSelectionStrategy 參數 │ +│ │ │ +│ ├─ Performance ──────┐ │ +│ ├─ Cost ─────────────┼─► 提供商評估邏輯 │ +│ ├─ Reliability ──────┤ │ +│ ├─ LoadBalance ──────┤ │ +│ └─ Primary ──────────┘ │ +│ │ +│ 評估流程: │ +│ ├─ 1. GetAvailableProvidersAsync() │ +│ │ ├─ 檢查 provider.IsAvailable │ +│ │ ├─ 執行 provider.CheckHealthAsync() │ +│ │ └─ 過濾不健康的提供商 │ +│ │ │ +│ ├─ 2. 根據策略排序 │ +│ │ ├─ Performance: OrderBy(ResponseTime) │ +│ │ ├─ Cost: OrderBy(CostPerRequest) │ +│ │ ├─ Reliability: OrderByDescending(SuccessRate) │ +│ │ └─ LoadBalance: Random.Next() │ +│ │ │ +│ └─ 3. 返回最佳提供商 │ +│ └─ 如果主提供商失敗,自動故障轉移到備用提供商 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 🤖 具體 AI 提供商執行 │ +│ │ +│ GeminiAIProvider (目前唯一提供商): │ +│ ├─ 統計追蹤 AIProviderStats │ +│ │ ├─ TotalRequests++ │ +│ │ ├─ 計時統計 │ +│ │ └─ 成本追蹤 │ +│ │ │ +│ ├─ 健康檢查能力 │ +│ │ ├─ CheckHealthAsync() - 測試 API 連通性 │ +│ │ ├─ GetStatsAsync() - 返回使用統計 │ +│ │ └─ 響應時間監控 │ +│ │ │ +│ └─ 實際 AI 調用 │ +│ ├─ BuildAnalysisPrompt() - 同現有邏輯 │ +│ ├─ CallGeminiAPIAsync() - HTTP 調用 │ +│ ├─ ParseAIResponse() - 結果解析 │ +│ └─ 異常處理和降級策略 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ (成功回應) +┌─────────────────────────────────────────────────────────────┐ +│ 💾 快取存儲 + 監控 │ +│ │ +│ 1️⃣ HybridCacheService.SetAsync(): │ +│ ├─ 智能過期時間計算 │ +│ ├─ 並行寫入多層快取 │ +│ └─ 統計更新 (HitCount, TotalKeys) │ +│ │ +│ 2️⃣ PerformanceMonitor 記錄: │ +│ ├─ API 調用時間 │ +│ ├─ 快取命中率 │ +│ ├─ 記憶體使用量 │ +│ └─ 提供商性能統計 │ +│ │ +│ 3️⃣ HealthCheckService 監控: │ +│ ├─ 資料庫連接性 │ +│ ├─ AI 服務可用性 │ +│ ├─ 快取服務狀態 │ +│ └─ 系統資源使用 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### **數據流向圖** + +``` +📊 數據流向和依賴關係 + + 🌐 HTTP Request 📱 Frontend React Components + │ │ + ▼ ▼ + 🛡️ Security Middleware 🧩 ClickableTextV2 + │ │ + ▼ ▼ + 📡 Controller Layer 💾 Local Cache + │ │ + ▼ ▼ + 🤖 AI Provider Manager 📈 Performance Monitor + │ │ + ▼ ▼ + 🔧 Service Layer 🎯 User Interaction + │ │ + ▼ ▼ + 💾 Cache Service 💡 Learning Analytics + │ │ + ▼ ▼ + 📁 Repository Layer 📊 Statistics Display + │ │ + ▼ ▼ + 🗄️ Database (SQLite) 🎨 UI Rendering + +依賴注入流程: +Program.cs + ├─ AddRepositoryServices() → IRepository, IFlashcardRepository + ├─ AddCachingServices() → ICacheService, MemoryCache + ├─ AddAIServices() → IAIProvider, IAIProviderManager + ├─ AddBusinessServices() → IAuthService, IGeminiService + ├─ AddAuthenticationServices() → JWT Bearer Token + └─ AddApiDocumentationServices() → Swagger/OpenAPI +``` + +### **快取策略流程圖** + +``` +🔍 快取查詢流程 (HybridCacheService) + +GetAsync(key) 調用 + │ + ▼ +┌─ L1: Memory Cache Check ────┐ +│ _memoryCache.TryGetValue() │ ✅ Hit (< 1ms) +│ │ │ +│ ❌ Miss │ ▼ +│ │ │ Return cached data +│ ▼ │ + Log cache hit +└─ L2: Distributed Cache ─────┘ + Update stats + │ _distributedCache.GetAsync() + │ ✅ Hit (< 10ms) + ❌ Miss │ + │ ▼ + ▼ Deserialize data + Return null │ + + Log cache miss ▼ + + Update stats Set to L1 cache + │ + ▼ + Return data + +SetAsync(key, value) 調用 + │ + ▼ +┌─ 計算智能過期時間 ────────────────────────────────┐ +│ CalculateSmartExpiry(): │ +│ ├─ "analysis:*" → 2 hours │ +│ ├─ "user:*" → 30 minutes │ +│ ├─ "flashcard:*" → 15 minutes │ +│ ├─ "stats:*" → 5 minutes │ +│ └─ default → 10 minutes │ +└─────────────────┬────────────────────────────────┘ + │ + ▼ +┌─ 並行設定多層快取 ──────────────────────────────────┐ +│ │ +│ Task 1: Memory Cache │ +│ ├─ 限制最大時間 (30分鐘) │ +│ ├─ _memoryCache.Set(key, value, expiry) │ +│ └─ 立即可用 (< 1ms) │ +│ │ +│ Task 2: Distributed Cache (如果可用) │ +│ ├─ SerializeToBytes(value) │ +│ ├─ DistributedCacheEntryOptions 設定 │ +│ ├─ _distributedCache.SetAsync() │ +│ └─ 跨實例共享 (< 10ms) │ +│ │ +│ Task.WhenAll() 等待完成 │ +└─────────────────────────────────────────────────┘ +``` + +### **分層職責定義** + +#### **1. Controllers Layer (控制器層)** +**職責**: 處理HTTP請求、參數驗證、回應格式化 +```csharp +[ApiController] +[Route("api/[controller]")] +public class AIController : ControllerBase +{ + private readonly IAIAnalysisService _aiService; + private readonly ILogger _logger; + + // ✅ 好的實踐:單一職責,只處理HTTP層邏輯 + [HttpPost("analyze")] + public async Task> Analyze( + [FromBody] AnalysisRequest request) + { + // 1. 輸入驗證 + if (!ModelState.IsValid) + return BadRequest(ModelState); + + // 2. 委派給服務層 + var result = await _aiService.AnalyzeAsync(request); + + // 3. 格式化回應 + return Ok(result); + } +} +``` + +#### **2. Services Layer (服務層)** +**職責**: 業務邏輯實現、AI服務整合、快取策略 +```csharp +public interface IAIAnalysisService +{ + Task AnalyzeAsync(AnalysisRequest request); +} + +public class AIAnalysisService : IAIAnalysisService +{ + private readonly IGeminiService _geminiService; + private readonly ICacheService _cacheService; + private readonly IAnalysisRepository _repository; + + // ✅ 好的實踐:依賴注入,介面隔離 + public async Task AnalyzeAsync(AnalysisRequest request) + { + // 1. 快取檢查 + var cached = await _cacheService.GetAsync(request.InputText); + if (cached != null) return cached; + + // 2. AI服務調用 + var aiResult = await _geminiService.AnalyzeAsync(request.InputText); + + // 3. 業務邏輯處理 + var processedResult = ProcessAIResult(aiResult, request); + + // 4. 快取存儲 + await _cacheService.SetAsync(request.InputText, processedResult); + + // 5. 持久化 + await _repository.SaveAnalysisAsync(processedResult); + + return processedResult; + } +} +``` + +#### **3. Data Access Layer (資料存取層)** +**職責**: 資料庫操作、查詢優化、事務管理 +```csharp +public interface IAnalysisRepository +{ + Task GetCachedAnalysisAsync(string inputHash); + Task SaveAnalysisAsync(AnalysisResult result); + Task> GetUsageStatsAsync(DateTime from, DateTime to); +} + +public class AnalysisRepository : IAnalysisRepository +{ + private readonly DramaLingDbContext _context; + + // ✅ 好的實踐:Repository模式,查詢優化 + public async Task GetCachedAnalysisAsync(string inputHash) + { + return await _context.AnalysisCache + .AsNoTracking() // 性能優化:只讀查詢 + .Where(a => a.InputHash == inputHash && a.ExpiresAt > DateTime.UtcNow) + .Select(a => new AnalysisResult // 投影查詢:只選需要的欄位 + { + Data = a.CachedData, + CreatedAt = a.CreatedAt + }) + .FirstOrDefaultAsync(); + } +} +``` + +--- + +## 🤖 **AI整合架構模式** + +### **AI服務抽象層設計** + +#### **多AI提供商支援架構** +```csharp +// ✅ 策略模式:支援多個AI提供商 +public interface IAIProvider +{ + string ProviderName { get; } + Task AnalyzeAsync(AIRequest request); + bool IsAvailable { get; } + decimal CostPerRequest { get; } +} + +public class GeminiProvider : IAIProvider +{ + public string ProviderName => "Google Gemini"; + + public async Task AnalyzeAsync(AIRequest request) + { + // Gemini特定實現 + } +} + +public class OpenAIProvider : IAIProvider +{ + public string ProviderName => "OpenAI GPT"; + + public async Task AnalyzeAsync(AIRequest request) + { + // OpenAI特定實現 + } +} + +// AI提供商選擇器 +public class AIProviderSelector +{ + private readonly IEnumerable _providers; + + public IAIProvider SelectBestProvider(AIRequest request) + { + // 基於成本、可用性、性能選擇最佳提供商 + return _providers + .Where(p => p.IsAvailable) + .OrderBy(p => p.CostPerRequest) + .First(); + } +} +``` + +### **AI請求優化模式** + +#### **智能批次處理** +```csharp +public class BatchAIProcessor +{ + private readonly Queue _requestQueue = new(); + private readonly Timer _batchTimer; + + public async Task ProcessAsync(AIRequest request) + { + // ✅ 批次處理:提高AI API效率 + _requestQueue.Enqueue(request); + + if (_requestQueue.Count >= BatchSize || IsTimeoutReached()) + { + await ProcessBatchAsync(); + } + + return await GetResultAsync(request.Id); + } + + private async Task ProcessBatchAsync() + { + var batch = DrainQueue(); + var batchPrompt = CombineRequests(batch); + var response = await _aiProvider.AnalyzeAsync(batchPrompt); + var results = SplitResponse(response, batch); + + // 分發結果給等待的請求 + foreach (var result in results) + { + _resultStore.SetResult(result.RequestId, result); + } + } +} +``` + +#### **智能快取策略** +```csharp +public class IntelligentCacheService +{ + private readonly IMemoryCache _memoryCache; + private readonly IDistributedCache _distributedCache; + private readonly IDatabase _database; + + // ✅ 多層快取:記憶體 → Redis → 資料庫 + public async Task GetAsync(string key) where T : class + { + // L1: 記憶體快取 (最快) + if (_memoryCache.TryGetValue(key, out T? memoryResult)) + return memoryResult; + + // L2: 分散式快取 (Redis) + var distributedResult = await _distributedCache.GetStringAsync(key); + if (distributedResult != null) + { + var result = JsonSerializer.Deserialize(distributedResult); + _memoryCache.Set(key, result, TimeSpan.FromMinutes(5)); + return result; + } + + // L3: 資料庫快取 (持久化) + var dbResult = await GetFromDatabaseAsync(key); + if (dbResult != null) + { + await SetMultiLevelCacheAsync(key, dbResult); + return dbResult; + } + + return null; + } + + // 智能過期策略 + public async Task SetAsync(string key, T value, TimeSpan? expiry = null) + { + var smartExpiry = CalculateSmartExpiry(key, value); + + // 同時更新多層快取 + _memoryCache.Set(key, value, smartExpiry); + await _distributedCache.SetStringAsync(key, JsonSerializer.Serialize(value), + new DistributedCacheEntryOptions { SlidingExpiration = smartExpiry }); + await SaveToDatabaseAsync(key, value, smartExpiry); + } +} +``` + +--- + +## 🤖 **AI Prompt 工程標準** + +### **Prompt 設計原則** + +#### **核心設計理念** +```yaml +明確性原則: + - 使用清晰、明確的指令語言 + - 避免模糊或可多重解釋的表達 + - 明確定義每個輸出欄位的用途和格式 + +一致性原則: + - 統一的 JSON Schema 定義 + - 標準化的欄位命名規範 + - 一致的資料類型和格式要求 + +穩定性原則: + - 避免讓 AI 自由發揮導致格式不穩定 + - 提供完整的結構範例 + - 明確禁止不期望的行為 + +效率性原則: + - 簡潔的 prompt 減少 token 消耗 + - 平衡詳細程度與處理速度 + - 優先處理核心業務需求 +``` + +### **標準 JSON Schema 定義** + +#### **句子分析回應格式** +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "SentenceAnalysisResponse", + "description": "AI 句子分析的標準回應格式", + "required": ["sentenceTranslation", "hasGrammarErrors", "grammarCorrections", "vocabularyAnalysis", "idioms"], + "properties": { + "sentenceTranslation": { + "type": "string", + "description": "整句的繁體中文翻譯,純文字格式", + "minLength": 1, + "maxLength": 500, + "examples": ["我是一個學生", "你好嗎", "給他一點空間吧"] + }, + "hasGrammarErrors": { + "type": "boolean", + "description": "句子是否包含語法錯誤" + }, + "grammarCorrections": { + "type": "array", + "description": "語法修正建議陣列", + "items": { + "type": "object", + "required": ["original", "corrected", "type", "explanation"], + "properties": { + "original": {"type": "string", "description": "錯誤的原文"}, + "corrected": {"type": "string", "description": "修正後的文字"}, + "type": {"type": "string", "enum": ["tense", "subject-verb", "preposition", "word-order"]}, + "explanation": {"type": "string", "description": "繁體中文解釋"} + } + } + }, + "vocabularyAnalysis": { + "type": "object", + "description": "詞彙分析物件,每個詞彙一個條目", + "patternProperties": { + "^word\\d+$": { + "type": "object", + "required": ["word", "translation", "definition", "partOfSpeech", "pronunciation", "difficultyLevel", "frequency"], + "properties": { + "word": {"type": "string", "description": "詞彙本身"}, + "translation": {"type": "string", "description": "繁體中文翻譯"}, + "definition": {"type": "string", "description": "英文定義"}, + "partOfSpeech": {"type": "string", "enum": ["noun", "verb", "adjective", "adverb", "pronoun", "preposition", "conjunction", "article", "determiner"]}, + "pronunciation": {"type": "string", "pattern": "^/.*/$", "description": "IPA 音標格式"}, + "difficultyLevel": {"type": "string", "enum": ["A1", "A2", "B1", "B2", "C1", "C2"]}, + "frequency": {"type": "string", "enum": ["high", "medium", "low"], "description": "使用頻率"}, + "synonyms": {"type": "array", "items": {"type": "string"}}, + "example": {"type": "string", "description": "例句"}, + "exampleTranslation": {"type": "string", "description": "例句繁體中文翻譯"} + } + } + } + }, + "idioms": { + "type": "array", + "description": "慣用語分析陣列", + "items": { + "type": "object", + "required": ["idiom", "translation", "definition", "pronunciation", "difficultyLevel", "frequency"], + "properties": { + "idiom": {"type": "string", "description": "慣用語表達"}, + "translation": {"type": "string", "description": "繁體中文意思"}, + "definition": {"type": "string", "description": "英文解釋"}, + "pronunciation": {"type": "string", "pattern": "^/.*/$"}, + "difficultyLevel": {"type": "string", "enum": ["A1", "A2", "B1", "B2", "C1", "C2"]}, + "frequency": {"type": "string", "enum": ["high", "medium", "low"]}, + "synonyms": {"type": "array", "items": {"type": "string"}}, + "example": {"type": "string"}, + "exampleTranslation": {"type": "string"} + } + } + } + } +} +``` + +### **標準 Prompt 模板** + +#### **句子分析 Prompt 範例** +``` +You are an English learning assistant. Analyze this sentence and return ONLY a valid JSON response. + +**Input Sentence**: "{inputText}" + +**Required JSON Structure:** +{ + "sentenceTranslation": "Traditional Chinese translation of the entire sentence", + "hasGrammarErrors": true/false, + "grammarCorrections": [], + "vocabularyAnalysis": { + "word1": { + "word": "the word", + "translation": "Traditional Chinese translation", + "definition": "English definition", + "partOfSpeech": "noun/verb/adjective/etc", + "pronunciation": "/phonetic/", + "difficultyLevel": "A1/A2/B1/B2/C1/C2", + "frequency": "high/medium/low", + "synonyms": ["synonym1", "synonym2"], + "example": "example sentence", + "exampleTranslation": "Traditional Chinese example translation" + } + }, + "idioms": [ + { + "idiom": "idiomatic expression", + "translation": "Traditional Chinese meaning", + "definition": "English explanation", + "pronunciation": "/phonetic notation/", + "difficultyLevel": "A1/A2/B1/B2/C1/C2", + "frequency": "high/medium/low", + "synonyms": ["synonym1", "synonym2"], + "example": "usage example", + "exampleTranslation": "Traditional Chinese example" + } + ] +} + +**Analysis Guidelines:** +1. **Grammar Check**: Detect tense errors, subject-verb agreement, preposition usage, word order +2. **Vocabulary Analysis**: Analyze EVERY SINGLE WORD in the sentence including articles (a, an, the), pronouns (I, you, he, she, it), prepositions (in, on, at), conjunctions (and, but, or), and every other word without exception +3. **CEFR Levels**: Assign accurate A1-C2 levels for each word +4. **Idioms**: Identify any idiomatic expressions or phrasal verbs +5. **Translations**: Use Traditional Chinese (Taiwan standard) + +**IMPORTANT**: Return ONLY the JSON object, no additional text or explanation. +``` + +### **Prompt 設計最佳實踐** + +#### **Do's and Don'ts** +```yaml +✅ 應該做的: + 明確性: + - 使用具體的範例而非抽象描述 + - 明確定義每個欄位的預期格式 + - 提供完整的結構範例 + + 穩定性: + - 明確禁止不期望的行為 + - 確保所有欄位格式與範例完全一致 + - 定期測試和驗證 prompt 效果 + +❌ 不應該做的: + 模糊性: + - 避免使用 "適當的"、"合理的" 等模糊詞彙 + - 不要讓 AI 自由決定輸出格式 + - 避免複雜的嵌套指令結構 + + 不一致性: + - 避免在不同 prompt 中使用不同的欄位名稱 + - 不要改變基礎數據結構定義 +``` + +--- + +## 🔧 **程式碼組織結構** + +### **專案結構範本** + +``` +DramaLing.Api/ +├── 📁 Controllers/ # API控制器 +│ ├── AIController.cs # AI分析相關端點 +│ ├── AuthController.cs # 認證相關端點 +│ └── BaseController.cs # 共用控制器基類 +├── 📁 Services/ # 業務服務層 +│ ├── AI/ # AI相關服務 +│ │ ├── IGeminiService.cs +│ │ ├── GeminiService.cs +│ │ ├── IPromptBuilder.cs +│ │ └── PromptBuilder.cs +│ ├── Cache/ # 快取服務 +│ │ ├── ICacheService.cs +│ │ ├── MemoryCacheService.cs +│ │ └── RedisCacheService.cs +│ └── Core/ # 核心業務服務 +│ ├── IAnalysisService.cs +│ └── AnalysisService.cs +├── 📁 Models/ # 資料模型 +│ ├── Entities/ # 資料庫實體 +│ ├── DTOs/ # 資料傳輸對象 +│ └── Requests/ # API請求模型 +├── 📁 Data/ # 資料存取層 +│ ├── DbContext.cs # EF Core上下文 +│ ├── Repositories/ # Repository實現 +│ └── Migrations/ # 資料庫遷移 +├── 📁 Infrastructure/ # 基礎設施 +│ ├── Middleware/ # 中介軟體 +│ ├── Filters/ # 過濾器 +│ ├── Extensions/ # 擴展方法 +│ └── Configuration/ # 配置管理 +├── 📁 Utils/ # 工具類 +│ ├── Helpers/ # 輔助函數 +│ ├── Constants/ # 常數定義 +│ └── Validators/ # 驗證器 +└── Program.cs # 應用程式入口 +``` + +### **依賴注入最佳實踐** + +#### **服務註冊配置** +```csharp +// Program.cs +public static void Main(string[] args) +{ + var builder = WebApplication.CreateBuilder(args); + + // ✅ 分層註冊:按類型組織 + RegisterControllers(builder); + RegisterBusinessServices(builder); + RegisterDataServices(builder); + RegisterInfrastructureServices(builder); + RegisterAIServices(builder); + + var app = builder.Build(); + ConfigureMiddleware(app); + app.Run(); +} + +// 業務服務註冊 +private static void RegisterBusinessServices(WebApplicationBuilder builder) +{ + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); +} + +// AI服務註冊 +private static void RegisterAIServices(WebApplicationBuilder builder) +{ + // ✅ 工廠模式:支援多AI提供商 + builder.Services.AddSingleton(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + // ✅ 配置模式:強型別配置 + builder.Services.Configure( + builder.Configuration.GetSection("Gemini")); + + // ✅ HttpClient工廠:連接池管理 + builder.Services.AddHttpClient(client => + { + client.Timeout = TimeSpan.FromSeconds(30); + client.DefaultRequestHeaders.Add("User-Agent", "DramaLing/1.0"); + }); +} +``` + +--- + +## 🚀 **性能優化策略** + +### **資料庫性能優化** + +#### **Entity Framework最佳實踐** +```csharp +public class OptimizedAnalysisRepository : IAnalysisRepository +{ + private readonly DramaLingDbContext _context; + + // ✅ 查詢優化:AsNoTracking + 投影 + public async Task> GetRecentAnalysesAsync(int userId, int count) + { + return await _context.Analyses + .AsNoTracking() // 關閉變更追蹤:提升查詢性能 + .Where(a => a.UserId == userId) + .OrderByDescending(a => a.CreatedAt) + .Take(count) + .Select(a => new AnalysisDto // 投影查詢:只查詢需要的欄位 + { + Id = a.Id, + InputText = a.InputText, + CreatedAt = a.CreatedAt, + Summary = a.Summary + }) + .ToListAsync(); + } + + // ✅ 批次操作:減少資料庫往返 + public async Task SaveMultipleAnalysesAsync(IEnumerable analyses) + { + _context.Analyses.AddRange(analyses); // 批次新增 + await _context.SaveChangesAsync(); // 單次提交 + } + + // ✅ 連接分離:讀寫分離 + public async Task GetAnalysisStatsAsync(int userId) + { + using var readOnlyContext = CreateReadOnlyContext(); + return await readOnlyContext.Analyses + .Where(a => a.UserId == userId) + .GroupBy(a => a.UserId) + .Select(g => new AnalysisStats + { + TotalCount = g.Count(), + LastAnalysisDate = g.Max(a => a.CreatedAt) + }) + .FirstOrDefaultAsync(); + } +} +``` + +### **AI服務性能優化** + +#### **請求合併與批次處理** +```csharp +public class OptimizedAIService : IAIAnalysisService +{ + private readonly IBatchProcessor _batchProcessor; + private readonly ICircuitBreaker _circuitBreaker; + + // ✅ 請求合併:減少AI API調用次數 + public async Task AnalyzeAsync(AnalysisRequest request) + { + var aiRequest = new AIRequest + { + Id = Guid.NewGuid(), + InputText = request.InputText, + Timestamp = DateTime.UtcNow + }; + + // 批次處理:自動合併相近時間的請求 + var aiResponse = await _batchProcessor.ProcessAsync(aiRequest); + + return TransformToAnalysisResult(aiResponse); + } + + // ✅ 斷路器模式:防止AI服務故障影響整體系統 + public async Task CallAIWithCircuitBreakerAsync(AIRequest request) + { + return await _circuitBreaker.ExecuteAsync(async () => + { + var response = await _aiProvider.CallAsync(request); + + if (!response.IsSuccessful) + throw new AIServiceException(response.ErrorMessage); + + return response; + }); + } +} +``` + +### **快取優化策略** + +#### **多層快取架構** +```csharp +public class HybridCacheService : ICacheService +{ + private readonly IMemoryCache _l1Cache; // L1: 程序內快取 + private readonly IDistributedCache _l2Cache; // L2: Redis分散式快取 + private readonly ICacheRepository _l3Cache; // L3: 資料庫快取 + + // ✅ 智能快取:根據數據特性選擇策略 + public async Task GetAsync(string key) where T : class + { + var cacheStrategy = DetermineCacheStrategy(); + + return cacheStrategy switch + { + CacheStrategy.Fast => await GetFromL1Async(key), + CacheStrategy.Distributed => await GetFromL2Async(key), + CacheStrategy.Persistent => await GetFromL3Async(key), + _ => await GetFromMultiLevelAsync(key) + }; + } + + // 智能過期策略 + private TimeSpan CalculateExpiry(string key, object value) + { + return key switch + { + var k when k.StartsWith("analysis:") => TimeSpan.FromHours(24), + var k when k.StartsWith("user:") => TimeSpan.FromMinutes(30), + var k when k.StartsWith("stats:") => TimeSpan.FromMinutes(5), + _ => TimeSpan.FromMinutes(15) + }; + } + + // ✅ 快取預熱:提前載入熱點資料 + public async Task WarmupCacheAsync() + { + var hotData = await _repository.GetHotDataAsync(); + var tasks = hotData.Select(data => + SetAsync($"warmup:{data.Id}", data, TimeSpan.FromHours(1))); + + await Task.WhenAll(tasks); + } +} +``` + +--- + +## 🛡️ **錯誤處理與穩定性** + +### **全局異常處理中介軟體** + +```csharp +public class GlobalExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex); + } + } + + // ✅ 結構化錯誤處理:不同異常類型對應不同處理策略 + private async Task HandleExceptionAsync(HttpContext context, Exception exception) + { + var response = exception switch + { + ValidationException validationEx => CreateValidationErrorResponse(validationEx), + AIServiceException aiEx => CreateAIServiceErrorResponse(aiEx), + DatabaseException dbEx => CreateDatabaseErrorResponse(dbEx), + UnauthorizedAccessException authEx => CreateUnauthorizedResponse(authEx), + _ => CreateGenericErrorResponse(exception) + }; + + // 記錄錯誤日誌 + _logger.LogError(exception, "Request failed: {Method} {Path}", + context.Request.Method, context.Request.Path); + + // 返回結構化錯誤回應 + context.Response.StatusCode = response.StatusCode; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(JsonSerializer.Serialize(response)); + } +} +``` + +### **重試與斷路器模式** + +```csharp +public class ResilientAIService : IAIAnalysisService +{ + private readonly IAIProvider _primaryProvider; + private readonly IAIProvider _fallbackProvider; + private readonly ICircuitBreaker _circuitBreaker; + + // ✅ 重試機制:指數退避 + public async Task CallWithRetryAsync(AIRequest request) + { + var retryPolicy = Policy + .Handle() + .Or() + .WaitAndRetryAsync( + retryCount: 3, + sleepDurationProvider: retryAttempt => + TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 指數退避:2^n秒 + onRetry: (outcome, timespan, retryCount, context) => + { + _logger.LogWarning("AI service retry {RetryCount} after {Delay}ms", + retryCount, timespan.TotalMilliseconds); + }); + + return await retryPolicy.ExecuteAsync(async () => + { + return await _circuitBreaker.ExecuteAsync(async () => + { + try + { + return await _primaryProvider.AnalyzeAsync(request); + } + catch (AIServiceException) + { + // ✅ 降級策略:使用備用提供商 + _logger.LogWarning("Primary AI provider failed, using fallback"); + return await _fallbackProvider.AnalyzeAsync(request); + } + }); + }); + } +} +``` + +--- + +## 📊 **監控與可觀測性** + +### **結構化日誌設計** + +#### **日誌標準化** +```csharp +public static class LoggerExtensions +{ + // ✅ 結構化日誌:便於查詢和分析 + public static void LogAIRequest(this ILogger logger, string requestId, + string inputText, string provider, double processingTime) + { + logger.LogInformation("AI Request Completed: {RequestId} Provider: {Provider} " + + "InputLength: {InputLength} ProcessingTime: {ProcessingTime}ms", + requestId, provider, inputText.Length, processingTime); + } + + public static void LogBusinessOperation(this ILogger logger, string operation, + string userId, bool success, Dictionary? additionalData = null) + { + using var scope = logger.BeginScope(new Dictionary + { + ["Operation"] = operation, + ["UserId"] = userId, + ["Success"] = success, + ["Timestamp"] = DateTime.UtcNow, + ["AdditionalData"] = additionalData ?? new Dictionary() + }); + + if (success) + logger.LogInformation("Business operation completed successfully"); + else + logger.LogWarning("Business operation failed"); + } +} +``` + +### **健康檢查系統** + +```csharp +public class AIServiceHealthCheck : IHealthCheck +{ + private readonly IGeminiService _geminiService; + private readonly ICacheService _cacheService; + private readonly DramaLingDbContext _dbContext; + + // ✅ 全面健康檢查:AI服務、快取、資料庫 + public async Task CheckHealthAsync( + HealthCheckContext context, CancellationToken cancellationToken = default) + { + var checks = new List<(string Name, Task Check)> + { + ("Gemini API", CheckGeminiHealthAsync()), + ("Cache Service", CheckCacheHealthAsync()), + ("Database", CheckDatabaseHealthAsync()), + ("Memory Usage", CheckMemoryUsageAsync()) + }; + + var results = await Task.WhenAll(checks.Select(async check => + { + try + { + var isHealthy = await check.Check; + return (check.Name, IsHealthy: isHealthy, Error: (string?)null); + } + catch (Exception ex) + { + return (check.Name, IsHealthy: false, Error: ex.Message); + } + })); + + var failedChecks = results.Where(r => !r.IsHealthy).ToList(); + + if (failedChecks.Any()) + { + var errors = string.Join(", ", failedChecks.Select(f => $"{f.Name}: {f.Error}")); + return HealthCheckResult.Unhealthy($"Failed checks: {errors}"); + } + + return HealthCheckResult.Healthy("All systems operational"); + } +} +``` + +--- + +## 🔒 **安全架構設計** + +### **多層安全防護** + +#### **API安全中介軟體** +```csharp +public class SecurityMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public async Task InvokeAsync(HttpContext context) + { + // ✅ 輸入驗證:防止注入攻擊 + if (!await ValidateInputSafetyAsync(context)) + { + context.Response.StatusCode = 400; + await context.Response.WriteAsync("Invalid input detected"); + return; + } + + // ✅ 速率限制:防止濫用 + if (!await CheckRateLimitAsync(context)) + { + context.Response.StatusCode = 429; + await context.Response.WriteAsync("Rate limit exceeded"); + return; + } + + // ✅ 請求追蹤:安全稽核 + var requestId = Guid.NewGuid().ToString(); + context.Items["RequestId"] = requestId; + + using var scope = _logger.BeginScope(new Dictionary + { + ["RequestId"] = requestId, + ["UserId"] = context.User?.Identity?.Name ?? "Anonymous", + ["IPAddress"] = context.Connection.RemoteIpAddress?.ToString(), + ["UserAgent"] = context.Request.Headers["User-Agent"].ToString() + }); + + await _next(context); + } + + private async Task ValidateInputSafetyAsync(HttpContext context) + { + // 檢查惡意模式 + var suspiciousPatterns = new[] + { + @")<[^<]*)*<\/script>", // XSS + @"(\bUNION\b|\bSELECT\b|\bINSERT\b|\bDELETE\b)", // SQL注入 + @"(javascript:|data:|vbscript:)", // 協議注入 + }; + + var body = await ReadRequestBodyAsync(context); + return !suspiciousPatterns.Any(pattern => + Regex.IsMatch(body, pattern, RegexOptions.IgnoreCase)); + } +} +``` + +### **API金鑰管理** + +#### **安全配置管理** +```csharp +public class SecureConfigurationService +{ + private readonly IConfiguration _configuration; + private readonly IKeyVault _keyVault; // Azure Key Vault 或 AWS Secrets Manager + + // ✅ 多層金鑰管理:環境變數 > Key Vault > 配置檔案 + public async Task GetAPIKeyAsync(string serviceName) + { + // 優先級1: 環境變數(容器化部署) + var envKey = Environment.GetEnvironmentVariable($"{serviceName.ToUpper()}_API_KEY"); + if (!string.IsNullOrEmpty(envKey)) + return envKey; + + // 優先級2: Key Vault(生產環境) + if (_keyVault != null) + { + var vaultKey = await _keyVault.GetSecretAsync($"{serviceName}-api-key"); + if (!string.IsNullOrEmpty(vaultKey)) + return vaultKey; + } + + // 優先級3: 配置檔案(開發環境) + var configKey = _configuration[$"ApiKeys:{serviceName}"]; + if (!string.IsNullOrEmpty(configKey)) + return configKey; + + throw new InvalidOperationException($"API key for {serviceName} not found"); + } + + // ✅ 金鑰輪換:定期更新API金鑰 + public async Task RotateAPIKeyAsync(string serviceName) + { + var newKey = await GenerateNewKeyAsync(serviceName); + await _keyVault.SetSecretAsync($"{serviceName}-api-key", newKey); + + // 通知相關服務更新金鑰 + await NotifyKeyRotationAsync(serviceName, newKey); + } +} +``` + +--- + +## 📈 **可擴展性設計** + +### **微服務準備架構** + +#### **服務邊界定義** +```csharp +// ✅ 領域驅動設計:按業務領域劃分服務 +namespace DramaLing.AI.Domain +{ + // AI分析聚合根 + public class AnalysisAggregate + { + public AnalysisId Id { get; private set; } + public string InputText { get; private set; } + public AnalysisResult Result { get; private set; } + public AnalysisStatus Status { get; private set; } + + // 業務邏輯封裝在聚合內 + public void MarkAsCompleted(AnalysisResult result) + { + if (Status != AnalysisStatus.Processing) + throw new InvalidOperationException("Analysis is not in processing state"); + + Result = result; + Status = AnalysisStatus.Completed; + + // 發布領域事件 + AddDomainEvent(new AnalysisCompletedEvent(Id, result)); + } + } +} + +// 服務介面定義 +public interface IAIAnalysisDomainService +{ + Task StartAnalysisAsync(string inputText); + Task CompleteAnalysisAsync(AnalysisId id, AnalysisResult result); +} +``` + +#### **事件驅動架構** +```csharp +public class EventDrivenAIService +{ + private readonly IEventBus _eventBus; + private readonly IAIProvider _aiProvider; + + // ✅ 事件驅動:解耦業務流程 + public async Task ProcessAnalysisAsync(AnalysisRequest request) + { + // 1. 發布分析開始事件 + await _eventBus.PublishAsync(new AnalysisStartedEvent + { + RequestId = request.Id, + InputText = request.InputText, + Timestamp = DateTime.UtcNow + }); + + try + { + // 2. 執行AI分析 + var result = await _aiProvider.AnalyzeAsync(request); + + // 3. 發布分析完成事件 + await _eventBus.PublishAsync(new AnalysisCompletedEvent + { + RequestId = request.Id, + Result = result, + ProcessingTime = result.ProcessingTime + }); + + return result; + } + catch (Exception ex) + { + // 4. 發布分析失敗事件 + await _eventBus.PublishAsync(new AnalysisFailedEvent + { + RequestId = request.Id, + Error = ex.Message, + Timestamp = DateTime.UtcNow + }); + + throw; + } + } +} + +// 事件處理器 +public class AnalysisEventHandler : + IEventHandler, + IEventHandler +{ + public async Task HandleAsync(AnalysisCompletedEvent eventData) + { + // 更新統計資料、發送通知、清理資源等 + await UpdateAnalysisStatsAsync(eventData); + await NotifyUserAsync(eventData.RequestId, "分析完成"); + } + + public async Task HandleAsync(AnalysisFailedEvent eventData) + { + // 錯誤恢復、告警、重試邏輯等 + await LogFailureAsync(eventData); + await TriggerRetryIfNecessaryAsync(eventData); + } +} +``` + +--- + +## 🔧 **配置管理最佳實踐** + +### **強型別配置** + +```csharp +// ✅ 配置類別:型別安全的配置管理 +public class AIServiceOptions +{ + public const string SectionName = "AIService"; + + [Required] + public string GeminiApiKey { get; set; } = string.Empty; + + [Range(1, 300)] + public int MaxInputLength { get; set; } = 300; + + [Range(1, 60)] + public int TimeoutSeconds { get; set; } = 30; + + public RetryOptions Retry { get; set; } = new(); + public CacheOptions Cache { get; set; } = new(); +} + +public class RetryOptions +{ + public int MaxAttempts { get; set; } = 3; + public int BaseDelayMs { get; set; } = 1000; + public bool UseExponentialBackoff { get; set; } = true; +} + +// 配置驗證 +public class AIServiceOptionsValidator : IValidateOptions +{ + public ValidateOptionsResult Validate(string name, AIServiceOptions options) + { + var failures = new List(); + + if (string.IsNullOrWhiteSpace(options.GeminiApiKey)) + failures.Add("Gemini API key is required"); + + if (options.TimeoutSeconds <= 0) + failures.Add("Timeout must be positive"); + + return failures.Any() + ? ValidateOptionsResult.Fail(failures) + : ValidateOptionsResult.Success; + } +} + +// 註冊配置 +builder.Services.Configure( + builder.Configuration.GetSection(AIServiceOptions.SectionName)); +builder.Services.AddSingleton, AIServiceOptionsValidator>(); +``` + +### **環境特定配置** + +```csharp +// appsettings.Development.json +{ + "AIService": { + "GeminiApiKey": "dev-key", + "TimeoutSeconds": 10, + "Cache": { + "EnableDistributed": false, + "DefaultExpiry": "00:05:00" + } + }, + "Logging": { + "LogLevel": { + "DramaLing.AI": "Debug" + } + } +} + +// appsettings.Production.json +{ + "AIService": { + "TimeoutSeconds": 30, + "Cache": { + "EnableDistributed": true, + "DefaultExpiry": "01:00:00" + } + }, + "Logging": { + "LogLevel": { + "DramaLing.AI": "Information" + } + } +} +``` + +--- + +## 🧪 **測試架構設計** + +### **測試金字塔實作** + +#### **單元測試 (70%)** +```csharp +public class AIAnalysisServiceTests +{ + private readonly Mock _mockGeminiService; + private readonly Mock _mockCacheService; + private readonly AIAnalysisService _service; + + // ✅ 純邏輯測試:快速、可靠、獨立 + [Test] + public async Task AnalyzeAsync_WithCachedResult_ReturnsCachedData() + { + // Arrange + var request = new AnalysisRequest { InputText = "test sentence" }; + var cachedResult = new AnalysisResult { /* ... */ }; + + _mockCacheService.Setup(c => c.GetAsync(It.IsAny())) + .ReturnsAsync(cachedResult); + + // Act + var result = await _service.AnalyzeAsync(request); + + // Assert + Assert.That(result, Is.EqualTo(cachedResult)); + _mockGeminiService.Verify(g => g.AnalyzeAsync(It.IsAny()), Times.Never); + } + + // ✅ 錯誤情境測試 + [Test] + public async Task AnalyzeAsync_WhenAIServiceFails_ThrowsAIServiceException() + { + // Arrange + _mockCacheService.Setup(c => c.GetAsync(It.IsAny())) + .ReturnsAsync((AnalysisResult?)null); + _mockGeminiService.Setup(g => g.AnalyzeAsync(It.IsAny())) + .ThrowsAsync(new HttpRequestException("API unavailable")); + + var request = new AnalysisRequest { InputText = "test sentence" }; + + // Act & Assert + Assert.ThrowsAsync(() => _service.AnalyzeAsync(request)); + } +} +``` + +#### **整合測試 (20%)** +```csharp +public class AIControllerIntegrationTests : IClassFixture> +{ + private readonly WebApplicationFactory _factory; + private readonly HttpClient _client; + + // ✅ 真實環境測試:包含所有中介軟體和配置 + [Test] + public async Task AnalyzeSentence_WithValidInput_ReturnsSuccessResponse() + { + // Arrange + var request = new AnalysisRequest + { + InputText = "She just join the team", + AnalysisMode = "full" + }; + + // Act + var response = await _client.PostAsJsonAsync("/api/ai/analyze-sentence", request); + + // Assert + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync(); + + Assert.That(result.Success, Is.True); + Assert.That(result.Data, Is.Not.Null); + Assert.That(result.Data.VocabularyAnalysis, Is.Not.Empty); + } +} +``` + +#### **E2E測試 (10%)** +```csharp +public class AIAnalysisE2ETests +{ + private readonly TestServer _server; + private readonly HttpClient _client; + + // ✅ 端到端測試:包含真實的AI API調用 + [Test] + [Category("E2E")] + public async Task CompleteAnalysisWorkflow_WithRealAI_ProducesValidResults() + { + // 此測試使用真實的AI API,運行時間較長 + // 適合在CI/CD流程中定期執行 + + var testSentences = new[] + { + "She just join the team, so let's cut her some slack.", + "The implementation was challenging but rewarding.", + "Could you please review the documentation?" + }; + + foreach (var sentence in testSentences) + { + var request = new AnalysisRequest { InputText = sentence }; + var response = await _client.PostAsJsonAsync("/api/ai/analyze-sentence", request); + + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync(); + + // 驗證AI分析品質 + Assert.That(result.Data.VocabularyAnalysis.Count, Is.GreaterThan(0)); + Assert.That(result.Data.SentenceMeaning, Is.Not.Empty); + + // 驗證CEFR等級分配合理性 + Assert.That(result.Data.VocabularyAnalysis.Values + .All(v => Enum.IsDefined(typeof(CEFRLevel), v.DifficultyLevel)), Is.True); + } + } +} +``` + +--- + +## 🔄 **部署與DevOps架構** + +### **容器化配置** + +#### **Dockerfile最佳實踐** +```dockerfile +# ✅ 多階段建置:減小鏡像大小 +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +# ✅ 快取優化:先複製專案檔案 +COPY ["DramaLing.Api/DramaLing.Api.csproj", "DramaLing.Api/"] +COPY ["DramaLing.Core/DramaLing.Core.csproj", "DramaLing.Core/"] +RUN dotnet restore "DramaLing.Api/DramaLing.Api.csproj" + +# 複製原始碼並建置 +COPY . . +WORKDIR "/src/DramaLing.Api" +RUN dotnet build "DramaLing.Api.csproj" -c Release -o /app/build + +# 發布應用程式 +FROM build AS publish +RUN dotnet publish "DramaLing.Api.csproj" -c Release -o /app/publish --no-restore + +# ✅ 執行階段:使用最小鏡像 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final +WORKDIR /app + +# ✅ 安全設定:非root用戶 +RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /app +USER appuser + +COPY --from=publish /app/publish . + +# ✅ 健康檢查:容器監控 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:5000/health || exit 1 + +ENTRYPOINT ["dotnet", "DramaLing.Api.dll"] +``` + +#### **Kubernetes部署配置** +```yaml +# deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dramaling-api +spec: + replicas: 3 # ✅ 高可用:多實例部署 + strategy: + type: RollingUpdate # ✅ 零停機部署 + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + spec: + containers: + - name: api + image: dramaling/api:latest + resources: # ✅ 資源限制:防止資源爭用 + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + env: # ✅ 外部化配置 + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + - name: GEMINI_API_KEY + valueFrom: + secretKeyRef: + name: ai-api-keys + key: gemini-key + livenessProbe: # ✅ 自動恢復 + httpGet: + path: /health + port: 5000 + initialDelaySeconds: 30 + periodSeconds: 30 + readinessProbe: # ✅ 流量控制 + httpGet: + path: /health/ready + port: 5000 + initialDelaySeconds: 5 + periodSeconds: 5 +``` + +--- + +## 📊 **監控指標設計** + +### **關鍵性能指標 (KPIs)** + +#### **業務指標監控** +```csharp +public class BusinessMetricsService +{ + private readonly IMetricsCollector _metrics; + + // ✅ 業務指標:追蹤產品健康度 + public void RecordAnalysisMetrics(AnalysisResult result) + { + // AI分析成功率 + _metrics.Increment("ai.analysis.completed", new[] + { + new KeyValuePair("provider", result.Provider), + new KeyValuePair("processing_time", result.ProcessingTime) + }); + + // 詞彙複雜度分布 + _metrics.Histogram("vocabulary.difficulty.distribution", + result.VocabularyAnalysis.Values.Select(v => GetDifficultyScore(v.DifficultyLevel))); + + // 用戶參與度 + _metrics.Increment("user.engagement", new[] + { + new KeyValuePair("feature", "ai_analysis"), + new KeyValuePair("session_id", result.SessionId) + }); + } + + // ✅ 異常指標:快速發現問題 + public void RecordErrorMetrics(Exception exception, string operation) + { + _metrics.Increment("errors.total", new[] + { + new KeyValuePair("operation", operation), + new KeyValuePair("error_type", exception.GetType().Name), + new KeyValuePair("severity", GetErrorSeverity(exception)) + }); + } +} +``` + +#### **技術指標監控** +```csharp +public class TechnicalMetricsMiddleware +{ + private readonly RequestDelegate _next; + private readonly IMetricsCollector _metrics; + + public async Task InvokeAsync(HttpContext context) + { + var stopwatch = Stopwatch.StartNew(); + var path = context.Request.Path.Value; + var method = context.Request.Method; + + try + { + await _next(context); + } + finally + { + stopwatch.Stop(); + + // ✅ 請求指標:API性能監控 + _metrics.Histogram("http.request.duration", stopwatch.ElapsedMilliseconds, new[] + { + new KeyValuePair("method", method), + new KeyValuePair("path", path), + new KeyValuePair("status_code", context.Response.StatusCode) + }); + + // 記憶體使用指標 + var memoryUsage = GC.GetTotalMemory(false); + _metrics.Gauge("system.memory.usage", memoryUsage); + + // 資料庫連接池指標 + _metrics.Gauge("database.connection_pool.active", + GetActiveConnectionCount()); + } + } +} +``` + +--- + +## 📚 **程式碼品質標準** + +### **編碼規範** + +#### **命名規範** +```csharp +// ✅ 清晰的命名:表達意圖,而非實現 +public class SentenceAnalysisService // 而非 AIService +{ + // 方法命名:動詞 + 名詞,表達業務意圖 + public async Task AnalyzeEnglishSentenceAsync(string sentence) + { + // 變數命名:有意義的業務術語 + var grammarCheckResult = await CheckGrammarAsync(sentence); + var vocabularyAnalysis = await AnalyzeVocabularyAsync(sentence); + var idiomDetection = await DetectIdiomsAsync(sentence); + + return BuildAnalysisResult(grammarCheckResult, vocabularyAnalysis, idiomDetection); + } + + // ✅ 私有方法:體現實現細節 + private async Task CheckGrammarAsync(string sentence) + { + var prompt = _promptBuilder.BuildGrammarCheckPrompt(sentence); + var aiResponse = await _aiProvider.CallAsync(prompt); + return _grammarParser.Parse(aiResponse); + } +} +``` + +#### **錯誤處理規範** +```csharp +// ✅ 自訂異常:明確的錯誤分類 +public abstract class DramaLingException : Exception +{ + public string ErrorCode { get; } + public Dictionary Context { get; } + + protected DramaLingException(string errorCode, string message, + Dictionary? context = null) : base(message) + { + ErrorCode = errorCode; + Context = context ?? new Dictionary(); + } +} + +public class AIServiceException : DramaLingException +{ + public AIServiceException(string provider, string aiErrorMessage, + string? originalPrompt = null) + : base("AI_SERVICE_ERROR", $"AI service '{provider}' failed: {aiErrorMessage}") + { + Context["Provider"] = provider; + Context["AIErrorMessage"] = aiErrorMessage; + if (originalPrompt != null) + Context["OriginalPrompt"] = originalPrompt; + } +} + +// 使用範例 +public async Task AnalyzeAsync(string inputText) +{ + try + { + return await _aiProvider.AnalyzeAsync(inputText); + } + catch (HttpRequestException ex) when (ex.Message.Contains("timeout")) + { + throw new AIServiceException(_aiProvider.Name, "Request timeout", inputText); + } + catch (JsonException ex) + { + throw new AIServiceException(_aiProvider.Name, "Invalid response format", inputText); + } +} +``` + +--- + +## 🔮 **未來擴展架構** + +### **AI模型演進支援** + +#### **模型版本管理** +```csharp +public class AIModelManager +{ + private readonly Dictionary _providers; + private readonly IConfiguration _configuration; + + // ✅ 版本控制:支援AI模型A/B測試 + public async Task AnalyzeWithVersionAsync(string inputText, + string? modelVersion = null) + { + var version = modelVersion ?? GetDefaultModelVersion(); + var provider = GetProviderForVersion(version); + + var result = await provider.AnalyzeAsync(inputText); + + // 記錄模型性能 + await RecordModelPerformanceAsync(version, result); + + return result; + } + + // ✅ 藍綠部署:無縫模型升級 + public async Task CanaryDeploymentAsync(string newModelVersion, double trafficPercentage) + { + var canaryProvider = GetProviderForVersion(newModelVersion); + var testResults = await RunCanaryTestsAsync(canaryProvider); + + if (testResults.ErrorRate < 0.01 && testResults.PerformanceIndex > 0.95) + { + await GraduallyMigrateTrafficAsync(newModelVersion, trafficPercentage); + return true; + } + + return false; + } +} +``` + +### **多租戶架構準備** + +#### **租戶隔離設計** +```csharp +public class TenantContext +{ + public string TenantId { get; set; } + public string DatabaseConnection { get; set; } + public AIServiceConfiguration AIConfig { get; set; } + public Dictionary CustomSettings { get; set; } +} + +public class MultiTenantAIService : IAIAnalysisService +{ + private readonly ITenantResolver _tenantResolver; + private readonly IServiceProvider _serviceProvider; + + // ✅ 租戶隔離:每個租戶獨立配置 + public async Task AnalyzeAsync(AnalysisRequest request) + { + var tenant = await _tenantResolver.GetCurrentTenantAsync(); + + // 使用租戶特定的AI配置 + var aiProvider = _serviceProvider.GetKeyedService(tenant.AIConfig.Provider); + + // 使用租戶特定的提示詞範本 + var prompt = BuildTenantSpecificPrompt(request.InputText, tenant); + + return await aiProvider.AnalyzeAsync(prompt); + } +} +``` + +--- + +## 📋 **實施檢查清單** + +### **架構實施階段** + +#### **Phase 1: 基礎架構 (第1-2週)** +- [ ] 建立分層架構基礎專案結構 +- [ ] 實作依賴注入容器配置 +- [ ] 建立基礎API控制器和中介軟體 +- [ ] 設定Entity Framework和資料庫遷移 +- [ ] 實作基本的健康檢查和日誌系統 + +#### **Phase 2: AI整合 (第3-4週)** +- [ ] 實作AI提供商抽象層 +- [ ] 建立智能快取系統 +- [ ] 實作Prompt管理和版本控制 +- [ ] 建立錯誤處理和重試機制 +- [ ] 設定監控指標和告警 + +#### **Phase 3: 優化與安全 (第5-6週)** +- [ ] 實作性能優化策略 +- [ ] 建立全面的安全防護 +- [ ] 完善測試套件 (單元/整合/E2E) +- [ ] 設定CI/CD流程 +- [ ] 建立生產環境監控 + +### **品質檢查標準** + +#### **程式碼品質** +```yaml +覆蓋率要求: + - 單元測試覆蓋率: > 80% + - 業務邏輯覆蓋率: > 90% + - 關鍵路徑覆蓋率: 100% + +性能基準: + - API回應時間: P95 < 500ms + - AI處理時間: P95 < 5s + - 資料庫查詢: P95 < 100ms + - 記憶體使用: < 500MB per instance + +安全檢查: + - 輸入驗證: 100%覆蓋 + - SQL注入防護: 已驗證 + - XSS防護: 已驗證 + - API金鑰安全: 已驗證 +``` + +--- + +## 🎓 **最佳實踐總結** + +### **核心原則** + +1. **單一職責**: 每個類別、方法都有明確單一的職責 +2. **依賴倒置**: 依賴抽象而非具體實現 +3. **開放封閉**: 對擴展開放,對修改封閉 +4. **介面隔離**: 客戶端不應依賴不需要的介面 +5. **最小驚訝**: 程式碼行為符合直覺期望 + +### **AI特定最佳實踐** + +1. **Prompt版本化**: 將Prompt當作程式碼管理 +2. **多提供商支援**: 避免供應商鎖定 +3. **智能快取**: 減少昂貴的AI API調用 +4. **優雅降級**: AI服務故障時的備援策略 +5. **成本監控**: 追蹤和優化AI API使用成本 + +### **維護性保證** + +1. **文檔驅動**: 架構決策和變更都要記錄 +2. **自動化測試**: 確保重構和擴展的安全性 +3. **監控完整**: 從業務指標到技術指標全覆蓋 +4. **配置外部化**: 所有環境特定配置都外部化 +5. **日誌結構化**: 便於查詢、分析和告警 + +--- + +**文件版本**: v1.0 +**技術架構**: .NET 8 + Entity Framework + AI整合 +**最後更新**: 2025-01-25 +**下次審查**: 2025-02-25 + +**參考實現**: DramaLing AI語言學習平台 +**適用範圍**: 中小型AI驅動產品 (< 10萬用戶) \ No newline at end of file