diff --git a/AI生成功能後端API規格.md b/AI生成功能後端API規格.md index d2b0654..3212317 100644 --- a/AI生成功能後端API規格.md +++ b/AI生成功能後端API規格.md @@ -5,7 +5,7 @@ - **文件名稱**: AI生成功能後端API規格 - **版本**: v1.0 - **建立日期**: 2025-01-25 -- **最後更新**: 2025-01-25 +- **最後更新**: 2025-01-25 (修正API規格) - **負責團隊**: DramaLing後端開發團隊 - **對應前端**: `/app/generate/page.tsx` @@ -57,7 +57,7 @@ External Services: 語言: C# / .NET 8 框架: ASP.NET Core Web API 資料庫: PostgreSQL + Redis (緩存) -AI服務: Google Gemini API +AI服務: Google Gemini 1.5 Flash API (包含結構化Prompt設計) 部署: Docker + Kubernetes 監控: Application Insights ``` @@ -79,7 +79,6 @@ Authorization: Bearer {token} ```json { "inputText": "She just join the team, so let's cut her some slack until she get used to the workflow.", - "userLevel": "A2", "analysisMode": "full", "options": { "includeGrammarCheck": true, @@ -95,7 +94,6 @@ Authorization: Bearer {token} | 參數 | 類型 | 必需 | 說明 | |------|------|------|------| | inputText | string | 是 | 待分析的英文句子 (最多300字) | -| userLevel | string | 是 | 用戶CEFR等級 (A1-C2) | | analysisMode | string | 否 | 分析模式: "basic"\|"full" (預設: "full") | | options | object | 否 | 分析選項配置 | @@ -142,7 +140,6 @@ Authorization: Bearer {token} "synonyms": ["her"], "example": "She is a teacher.", "exampleTranslation": "她是一名老師。", - "tags": ["basic", "pronoun"] }, "just": { "word": "just", @@ -155,7 +152,6 @@ Authorization: Bearer {token} "synonyms": ["recently", "only", "merely"], "example": "I just arrived.", "exampleTranslation": "我剛到。", - "tags": ["time", "adverb"] } }, "idioms": [ @@ -175,20 +171,10 @@ Authorization: Bearer {token} "exampleTranslation": "對他寬容一點,他是新來的。" } ], - "statistics": { - "totalWords": 16, - "uniqueWords": 15, - "simpleWords": 8, - "moderateWords": 4, - "difficultWords": 3, - "idioms": 1, - "averageDifficulty": "A2" - }, "metadata": { - "analysisModel": "gemini-pro", - "analysisVersion": "1.0", + "analysisModel": "gemini-1.5-flash", + "analysisVersion": "2.0", "processingDate": "2025-01-25T10:30:00Z", - "userLevel": "A2" } } } @@ -228,7 +214,6 @@ interface VocabularyAnalysis { synonyms: string[] // 同義詞 example?: string // 例句 exampleTranslation?: string // 例句翻譯 - tags: string[] // 標籤分類 } ``` @@ -250,6 +235,48 @@ interface GrammarError { } ``` +### **IdiomDto 模型** +```typescript +interface IdiomDto { + idiom: string // 慣用語本身 + translation: string // 中文翻譯 + definition: string // 英文定義 + pronunciation: string // 發音 (IPA) + difficultyLevel: CEFRLevel // CEFR等級 + frequency: FrequencyLevel // 使用頻率 + synonyms: string[] // 同義詞或相似表達 + example?: string // 例句 + exampleTranslation?: string // 例句翻譯 +} +``` + + +### **AnalysisMetadata 模型** +```typescript +interface AnalysisMetadata { + analysisModel: string // AI模型名稱 + analysisVersion: string // 分析版本 + processingDate: string // 處理時間 (ISO 8601) +} +``` + +### **ApiErrorResponse 模型** +```typescript +interface ApiErrorResponse { + success: boolean // 固定為 false + error: ApiError // 錯誤詳情 + timestamp: string // 錯誤時間 (ISO 8601) + requestId: string // 請求ID +} + +interface ApiError { + code: string // 錯誤代碼 + message: string // 錯誤訊息 + details?: object // 錯誤詳情 + suggestions: string[] // 建議解決方案 +} +``` + ### **枚舉定義** ```typescript type CEFRLevel = "A1" | "A2" | "B1" | "B2" | "C1" | "C2" @@ -259,6 +286,151 @@ type AnalysisMode = "basic" | "full" --- +## 🤖 **AI Prompt設計規格** + +### **Prompt架構設計** + +#### **核心Prompt模板** +```text +You are an English learning assistant. Analyze this sentence for a learner 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": [ + { + "original": "incorrect text", + "corrected": "correct text", + "type": "error type (tense/subject-verb/preposition/word-order)", + "explanation": "brief explanation in Traditional Chinese" + } + ], + "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" + } + ] +} +``` + +### **Prompt指導原則** + +#### **分析指導方針** +```yaml +語法檢查: + - 檢測範圍: 時態錯誤、主謂一致、介詞使用、詞序問題 + - 修正原則: 提供自然且正確的英語表達 + - 解釋語言: 使用繁體中文(台灣標準) + +詞彙分析: + - 包含範圍: 所有有意義的詞彙(排除冠詞 a, an, the) + - CEFR標準: 準確分配A1-C2等級 + - 發音標記: 使用國際音標(IPA) + - 例句品質: 提供實用且常見的例句 + +慣用語識別: + - 識別目標: 慣用語、片語動詞、固定搭配 + - 分類原則: 獨立於vocabularyAnalysis處理 + - 解釋詳度: 提供文化背景和使用場景 + +翻譯品質: + - 語言標準: 繁體中文(台灣用法) + - 自然程度: 符合中文表達習慣 + - 準確性: 保持原文語義完整 +``` + +### **Prompt優化策略** + +#### **結構化輸出控制** +```text +**IMPORTANT**: Return ONLY the JSON object, no additional text or explanation. + +**Quality Assurance:** +1. Ensure all JSON keys are exactly as specified +2. Use Traditional Chinese for all translations +3. Assign accurate CEFR levels based on standard guidelines +4. Separate idioms from regular vocabulary +5. Provide practical examples that learners can use +``` + +#### **錯誤處理Prompt** +```text +**Error Handling Guidelines:** +- If unable to analyze: Return basic structure with explanatory messages +- If safety filtered: Provide alternative analysis approach +- If ambiguous input: Make reasonable assumptions and proceed +- Always return valid JSON regardless of input complexity +``` + +### **Prompt版本管理** + +#### **版本演進記錄** +```yaml +v1.0 (2025-01-20): + - 基礎prompt結構 + - 簡單詞彙分析 + +v1.5 (2025-01-22): + - 新增慣用語識別 + - 優化語法檢查 + +v2.0 (2025-01-25): + - 結構化JSON輸出 + - 完整資料模型支援 + - 繁體中文本地化 + - 錯誤處理機制 +``` + +#### **A/B測試配置** +```csharp +// Prompt變體測試 +public enum PromptVariant +{ + Standard, // 標準prompt + DetailedExamples, // 強調例句品質 + StrictGrammar, // 加強語法檢查 + CulturalContext // 增加文化背景 +} + +// 動態Prompt選擇 +private string GetPromptByVariant(PromptVariant variant, string inputText) +{ + return variant switch + { + PromptVariant.Standard => BuildStandardPrompt(inputText), + PromptVariant.DetailedExamples => BuildDetailedPrompt(inputText), + _ => BuildStandardPrompt(inputText) + }; +} +``` + +--- + ## 🔒 **認證與授權** ### **API認證** @@ -343,7 +515,7 @@ Premium用戶: "responseTime": 2340, "inputLength": 89, "analysisMode": "full", - "aiModel": "gpt-4", + "aiModel": "gemini-1.5-flash", "processingSteps": { "grammarCheck": 450, "vocabularyAnalysis": 1200, @@ -409,18 +581,14 @@ Premium用戶: 測試目的: 驗證完整分析功能 輸入數據: inputText: "She just join the team, so let's cut her some slack until she get used to the workflow." - userLevel: "A2" analysisMode: "full" 預期結果: statusCode: 200 grammarCorrection.hasErrors: true grammarCorrection.corrections.length: 2 - vocabularyAnalysis keys: 17 (16個詞 + 1個慣用語) - statistics.simpleWords: 8 - statistics.moderateWords: 4 - statistics.difficultWords: 3 - statistics.phrases: 1 + vocabularyAnalysis keys: 16 (不含慣用語) + idioms.length: 1 ``` #### **TC-002: 輸入驗證測試** @@ -623,7 +791,17 @@ AI升級: --- -**文件版本**: v1.0 +**文件版本**: v1.1 (更新AI模型和模型定義) **API版本**: v1 **最後更新**: 2025-01-25 -**下次審查**: 2025-02-25 \ No newline at end of file +**下次審查**: 2025-02-25 + +**更新記錄**: +- v1.1: 修正AI模型名稱為Gemini 1.5 Flash +- v1.1: 移除VocabularyAnalysis中tags欄位 +- v1.1: 新增IdiomDto和ApiErrorResponse模型定義 +- v1.1: 移除userLevel參數(簡化API) +- v1.1: 統一回應格式範例 +- v1.1: 新增完整的AI Prompt設計規格和版本管理 +- v1.1: 同步更新後端DTO模型實現(移除Tags、IsIdiom、UserLevel欄位) +- v1.1: 移除statistics計算邏輯(前端自行處理統計) \ No newline at end of file diff --git a/AI生成網頁前端需求規格.md b/AI生成網頁前端需求規格.md index d388856..1ecbf5c 100644 --- a/AI生成網頁前端需求規格.md +++ b/AI生成網頁前端需求規格.md @@ -498,7 +498,6 @@ API通信: Fetch API ```json { "inputText": "英文句子", - "userLevel": "A2", "analysisMode": "full" } ``` @@ -508,9 +507,12 @@ API通信: Fetch API { "success": true, "data": { - "WordAnalysis": "詞彙分析對象", - "SentenceMeaning": "句子翻譯", - "GrammarCorrection": "語法修正資料" + "vocabularyAnalysis": "詞彙分析字典對象", + "sentenceMeaning": "句子翻譯字串", + "grammarCorrection": "語法修正資料對象", + "idioms": "慣用語陣列", + "statistics": "統計資料對象", + "metadata": "分析元資料對象" } } ``` @@ -537,7 +539,7 @@ interface WordAnalysisRequired { partOfSpeech: string // 必需:詞性 pronunciation: string // 必需:發音 difficultyLevel: string // 必需:CEFR等級 - isIdiom: boolean // 必需:是否為慣用語 + frequency: string // 必需:使用頻率 } ``` @@ -727,7 +729,13 @@ interface WordAnalysisOptional { --- -**文件版本**: v1.0 +**文件版本**: v1.1 (配合後端API規格更新) **產品負責人**: DramaLing產品團隊 -**最後更新**: 2025-09-21 -**下次審查**: 2025-10-21 \ No newline at end of file +**最後更新**: 2025-01-25 +**下次審查**: 2025-02-25 + +**更新記錄**: +- v1.1: 移除API請求中的userLevel參數要求 +- v1.1: 更新API回應格式為實際實現的格式 +- v1.1: 修正資料模型,移除isIdiom欄位,新增frequency欄位 +- v1.1: 保留前端個人化分類邏輯,改為從localStorage獲取用戶等級 \ No newline at end of file diff --git a/backend/DramaLing.Api/Controllers/AIController.cs b/backend/DramaLing.Api/Controllers/AIController.cs index 66d8e5b..d101167 100644 --- a/backend/DramaLing.Api/Controllers/AIController.cs +++ b/backend/DramaLing.Api/Controllers/AIController.cs @@ -53,7 +53,7 @@ public class AIController : ControllerBase // 直接執行 AI 分析,不使用快取 var options = request.Options ?? new AnalysisOptions(); var analysisData = await _geminiService.AnalyzeSentenceAsync( - request.InputText, request.UserLevel, options); + request.InputText, options); stopwatch.Stop(); analysisData.Metadata.ProcessingDate = DateTime.UtcNow; diff --git a/backend/DramaLing.Api/Models/DTOs/AIAnalysisDto.cs b/backend/DramaLing.Api/Models/DTOs/AIAnalysisDto.cs index 8e389c6..25fb433 100644 --- a/backend/DramaLing.Api/Models/DTOs/AIAnalysisDto.cs +++ b/backend/DramaLing.Api/Models/DTOs/AIAnalysisDto.cs @@ -8,9 +8,6 @@ public class SentenceAnalysisRequest [StringLength(300, MinimumLength = 1, ErrorMessage = "輸入文本長度必須在1-300字符之間")] public string InputText { get; set; } = string.Empty; - [Required] - [RegularExpression("^(A1|A2|B1|B2|C1|C2)$", ErrorMessage = "無效的CEFR等級")] - public string UserLevel { get; set; } = "A2"; public string AnalysisMode { get; set; } = "full"; @@ -42,7 +39,6 @@ public class SentenceAnalysisData public string SentenceMeaning { get; set; } = string.Empty; public Dictionary VocabularyAnalysis { get; set; } = new(); public List Idioms { get; set; } = new(); - public AnalysisStatistics Statistics { get; set; } = new(); public AnalysisMetadata Metadata { get; set; } = new(); } @@ -77,12 +73,10 @@ public class VocabularyAnalysisDto public string PartOfSpeech { get; set; } = string.Empty; public string Pronunciation { get; set; } = string.Empty; public string DifficultyLevel { get; set; } = string.Empty; - public bool IsIdiom { get; set; } public string Frequency { get; set; } = string.Empty; public List Synonyms { get; set; } = new(); public string? Example { get; set; } public string? ExampleTranslation { get; set; } - public List Tags { get; set; } = new(); } public class IdiomDto @@ -98,23 +92,12 @@ public class IdiomDto public string? ExampleTranslation { get; set; } } -public class AnalysisStatistics -{ - public int TotalWords { get; set; } - public int UniqueWords { get; set; } - public int SimpleWords { get; set; } - public int ModerateWords { get; set; } - public int DifficultWords { get; set; } - public int Idioms { get; set; } - public string AverageDifficulty { get; set; } = string.Empty; -} public class AnalysisMetadata { - public string AnalysisModel { get; set; } = "gpt-4"; - public string AnalysisVersion { get; set; } = "1.0"; + public string AnalysisModel { get; set; } = "gemini-1.5-flash"; + public string AnalysisVersion { get; set; } = "2.0"; public DateTime ProcessingDate { get; set; } = DateTime.UtcNow; - public string UserLevel { get; set; } = string.Empty; } public class ApiErrorResponse diff --git a/backend/DramaLing.Api/Services/GeminiService.cs b/backend/DramaLing.Api/Services/GeminiService.cs index a1ff3df..0bc1f5d 100644 --- a/backend/DramaLing.Api/Services/GeminiService.cs +++ b/backend/DramaLing.Api/Services/GeminiService.cs @@ -6,7 +6,7 @@ namespace DramaLing.Api.Services; public interface IGeminiService { - Task AnalyzeSentenceAsync(string inputText, string userLevel, AnalysisOptions options); + Task AnalyzeSentenceAsync(string inputText, AnalysisOptions options); } public class GeminiService : IGeminiService @@ -31,20 +31,19 @@ public class GeminiService : IGeminiService _httpClient.DefaultRequestHeaders.Add("User-Agent", "DramaLing/1.0"); } - public async Task AnalyzeSentenceAsync(string inputText, string userLevel, AnalysisOptions options) + public async Task AnalyzeSentenceAsync(string inputText, AnalysisOptions options) { var startTime = DateTime.UtcNow; try { - _logger.LogInformation("Starting sentence analysis for text: {Text}, UserLevel: {UserLevel}", - inputText.Substring(0, Math.Min(50, inputText.Length)), userLevel); + _logger.LogInformation("Starting sentence analysis for text: {Text}", + inputText.Substring(0, Math.Min(50, inputText.Length))); // 符合產品需求規格的結構化 prompt - var prompt = $@"You are an English learning assistant. Analyze this sentence for a {userLevel} CEFR level learner and return ONLY a valid JSON response. + var prompt = $@"You are an English learning assistant. Analyze this sentence and return ONLY a valid JSON response. **Input Sentence**: ""{inputText}"" -**Learner Level**: {userLevel} **Required JSON Structure:** {{ @@ -113,7 +112,7 @@ public class GeminiService : IGeminiService } // 直接使用 AI 的回應創建分析數據 - var analysisData = CreateAnalysisFromAIResponse(inputText, userLevel, aiResponse); + var analysisData = CreateAnalysisFromAIResponse(inputText, aiResponse); var processingTime = (DateTime.UtcNow - startTime).TotalSeconds; _logger.LogInformation("Sentence analysis completed in {ProcessingTime}s", processingTime); @@ -127,7 +126,7 @@ public class GeminiService : IGeminiService } } - private SentenceAnalysisData CreateAnalysisFromAIResponse(string inputText, string userLevel, string aiResponse) + private SentenceAnalysisData CreateAnalysisFromAIResponse(string inputText, string aiResponse) { _logger.LogInformation("Creating analysis from AI response: {ResponsePreview}...", aiResponse.Substring(0, Math.Min(100, aiResponse.Length))); @@ -166,15 +165,12 @@ public class GeminiService : IGeminiService GrammarCorrection = ConvertGrammarCorrection(aiAnalysis), Metadata = new AnalysisMetadata { - UserLevel = userLevel, ProcessingDate = DateTime.UtcNow, AnalysisModel = "gemini-1.5-flash", AnalysisVersion = "2.0" } }; - // 計算統計 - analysisData.Statistics = CalculateStatistics(analysisData.VocabularyAnalysis, analysisData.Idioms, userLevel); return analysisData; } @@ -182,7 +178,7 @@ public class GeminiService : IGeminiService { _logger.LogError(ex, "Failed to parse AI response as JSON: {Response}", aiResponse); // 回退到舊的處理方式 - return CreateFallbackAnalysis(inputText, userLevel, aiResponse); + return CreateFallbackAnalysis(inputText, aiResponse); } } @@ -205,7 +201,6 @@ public class GeminiService : IGeminiService Synonyms = aiWord.Synonyms ?? new List(), Example = aiWord.Example, ExampleTranslation = aiWord.ExampleTranslation, - Tags = new List { "ai-analyzed", "gemini" } }; } @@ -260,7 +255,7 @@ public class GeminiService : IGeminiService }; } - private SentenceAnalysisData CreateFallbackAnalysis(string inputText, string userLevel, string aiResponse) + private SentenceAnalysisData CreateFallbackAnalysis(string inputText, string aiResponse) { _logger.LogWarning("Using fallback analysis due to JSON parsing failure"); @@ -271,12 +266,10 @@ public class GeminiService : IGeminiService VocabularyAnalysis = CreateBasicVocabularyFromText(inputText, aiResponse), Metadata = new AnalysisMetadata { - UserLevel = userLevel, ProcessingDate = DateTime.UtcNow, AnalysisModel = "gemini-1.5-flash-fallback", - AnalysisVersion = "1.0" + AnalysisVersion = "2.0" }, - Statistics = new AnalysisStatistics() }; } @@ -303,7 +296,6 @@ public class GeminiService : IGeminiService Synonyms = new List(), Example = null, ExampleTranslation = null, - Tags = new List { "fallback-analysis" } }; } @@ -335,21 +327,6 @@ public class GeminiService : IGeminiService return "B2"; } - private AnalysisStatistics CalculateStatistics(Dictionary vocabulary, List idioms, string userLevel) - { - var stats = new AnalysisStatistics - { - TotalWords = vocabulary.Count, - UniqueWords = vocabulary.Count, - SimpleWords = vocabulary.Count(kvp => kvp.Value.DifficultyLevel == "A1"), - ModerateWords = vocabulary.Count(kvp => kvp.Value.DifficultyLevel == "A2"), - DifficultWords = vocabulary.Count(kvp => !new[] { "A1", "A2" }.Contains(kvp.Value.DifficultyLevel)), - Idioms = idioms.Count, // 基於實際 idioms 陣列計算 - AverageDifficulty = userLevel - }; - - return stats; - } private async Task CallGeminiAPI(string prompt) { diff --git a/docs/AI驅動產品後端技術架構指南.md b/docs/AI驅動產品後端技術架構指南.md new file mode 100644 index 0000000..0f0cda6 --- /dev/null +++ b/docs/AI驅動產品後端技術架構指南.md @@ -0,0 +1,1610 @@ +# 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) │ +└─────────────────────────────────────────────────────────────┘ +``` + +### **分層職責定義** + +#### **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); + } +} +``` + +--- + +## 🔧 **程式碼組織結構** + +### **專案結構範本** + +``` +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