31 KiB
31 KiB
AI生成功能前後端串接規格
📋 文件資訊
- 文件名稱: AI生成功能前後端串接規格
- 版本: v1.0
- 建立日期: 2025-01-25
- 最後更新: 2025-01-25
- 負責團隊: DramaLing全端開發團隊
🎯 串接架構概述
系統架構圖
┌─────────────────┐ HTTP/JSON ┌──────────────────┐ Gemini API ┌─────────────────┐
│ │ Request │ │ Request │ │
│ Frontend │ ──────────────► │ Backend API │ ──────────────► │ Google Gemini │
│ (Next.js) │ │ (.NET Core) │ │ AI Service │
│ Port 3000 │ ◄────────────── │ Port 5008 │ ◄────────────── │ │
│ │ Response │ │ Response │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ Local Storage │ │ SQLite Database │
│ - auth_token │ │ - Cache │
│ - user_level │ │ - Usage Stats │
└─────────────────┘ └──────────────────┘
🔄 API串接流程
完整用戶操作流程
sequenceDiagram
participant U as 用戶
participant F as 前端(3000)
participant B as 後端(5008)
participant G as Gemini API
participant C as Cache
participant D as Database
U->>F: 1. 輸入英文句子
U->>F: 2. 點擊「分析句子」
F->>F: 3. 驗證輸入(≤300字符)
F->>F: 4. 讀取userLevel
F->>B: 5. POST /api/ai/analyze-sentence
Note over F,B: Content-Type: application/json<br/>Body: {inputText, userLevel, options}
B->>B: 6. 輸入驗證
B->>C: 7. 檢查快取
alt 快取命中
C->>B: 8a. 返回快取結果
B->>F: 9a. 返回分析結果
else 快取未命中
B->>G: 8b. 調用Gemini API
G->>B: 9b. AI分析結果
B->>B: 10. 解析和處理
B->>C: 11. 儲存快取
B->>D: 12. 記錄使用統計
B->>F: 13. 返回分析結果
end
F->>F: 14. 處理API回應
F->>F: 15. 渲染詞彙標記
F->>U: 16. 顯示分析結果
U->>F: 17. 點擊詞彙
F->>F: 18. 顯示詞彙彈窗
U->>F: 19. 保存詞卡
F->>B: 20. POST /api/flashcards
B->>D: 21. 儲存詞卡
B->>F: 22. 返回成功狀態
F->>U: 23. 顯示成功提示
📡 API端點詳細串接
1. 句子分析API
前端請求實現
// 位置: frontend/app/generate/page.tsx:65-87
const handleAnalyzeSentence = async () => {
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2'
const response = await fetch('http://localhost:5008/api/ai/analyze-sentence', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
inputText: textInput,
userLevel: userLevel,
analysisMode: 'full',
options: {
includeGrammarCheck: true,
includeVocabularyAnalysis: true,
includeTranslation: true,
includePhraseDetection: true,
includeExamples: true
}
})
})
const result = await response.json()
// 處理API回應...
}
後端處理實現
// 位置: backend/Controllers/AIController.cs:32-105
[HttpPost("analyze-sentence")]
public async Task<ActionResult<SentenceAnalysisResponse>> AnalyzeSentence(
[FromBody] SentenceAnalysisRequest request)
{
// 1. 輸入驗證
if (!ModelState.IsValid) return BadRequest(...)
// 2. 檢查快取
var cachedResult = await _cacheService.GetCachedAnalysisAsync(request.InputText);
if (cachedResult != null) return Ok(...)
// 3. 調用Gemini AI
var analysisData = await _geminiService.AnalyzeSentenceAsync(
request.InputText, request.UserLevel, options);
// 4. 快取結果
await _cacheService.SetCachedAnalysisAsync(request.InputText, analysisData, TimeSpan.FromHours(24));
// 5. 返回結果
return Ok(new SentenceAnalysisResponse { Success = true, Data = analysisData });
}
💾 數據格式串接
前端到後端請求格式
SentenceAnalysisRequest
{
"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,
"includeVocabularyAnalysis": true,
"includeTranslation": true,
"includePhraseDetection": true,
"includeExamples": true
}
}
後端到前端回應格式
SentenceAnalysisResponse
{
"success": true,
"processingTime": 2.34,
"data": {
"analysisId": "uuid-string",
"originalText": "She just join the team, so let's cut her some slack until she get used to the workflow.",
"grammarCorrection": {
"hasErrors": true,
"correctedText": "She just joined the team, so let's cut her some slack until she gets used to the workflow.",
"corrections": [
{
"error": "join",
"correction": "joined",
"type": "時態錯誤",
"explanation": "第三人稱單數過去式應使用 'joined'"
},
{
"error": "get",
"correction": "gets",
"type": "時態錯誤",
"explanation": "第三人稱單數現在式應使用 'gets'"
}
]
},
"sentenceMeaning": "她剛加入團隊,所以讓我們對她寬容一點,直到她習慣工作流程。",
"vocabularyAnalysis": {
"she": {
"word": "she",
"translation": "她",
"definition": "female person pronoun",
"partOfSpeech": "pronoun",
"pronunciation": "/ʃiː/",
"difficultyLevel": "A1",
"isPhrase": false,
"frequency": "very_high",
"synonyms": ["her"],
"example": "She is a teacher.",
"exampleTranslation": "她是一名老師。",
"tags": ["basic", "pronoun"]
},
"cut someone some slack": {
"word": "cut someone some slack",
"translation": "對某人寬容一點",
"definition": "to be more lenient or forgiving with someone",
"partOfSpeech": "idiom",
"pronunciation": "/kʌt ˈsʌmwʌn sʌm slæk/",
"difficultyLevel": "B2",
"isPhrase": true,
"frequency": "medium",
"synonyms": ["be lenient", "be forgiving", "give leeway"],
"example": "Cut him some slack, he's new here.",
"exampleTranslation": "對他寬容一點,他是新來的。",
"tags": ["idiom", "workplace", "tolerance"]
}
},
"statistics": {
"totalWords": 16,
"uniqueWords": 15,
"simpleWords": 8,
"moderateWords": 4,
"difficultWords": 3,
"phrases": 1,
"averageDifficulty": "A2"
},
"metadata": {
"analysisModel": "gemini-pro",
"analysisVersion": "1.0",
"processingDate": "2025-01-25T10:30:00Z",
"userLevel": "A2"
}
}
}
🔧 前端處理邏輯
API回應處理
數據解析和狀態更新
// 位置: frontend/app/generate/page.tsx:101-124
const apiData = result.data
// 設定分析結果
setSentenceAnalysis(apiData.vocabularyAnalysis || {})
setSentenceMeaning(apiData.sentenceMeaning || '')
// 處理語法修正
if (apiData.grammarCorrection) {
setGrammarCorrection({
hasErrors: apiData.grammarCorrection.hasErrors,
originalText: textInput,
correctedText: apiData.grammarCorrection.correctedText || textInput,
corrections: apiData.grammarCorrection.corrections || []
})
// 使用修正後的文本作為最終文本
setFinalText(apiData.grammarCorrection.correctedText || textInput)
} else {
setFinalText(textInput)
}
setShowAnalysisView(true)
詞彙標記渲染
CEFR等級比較邏輯
// 位置: frontend/components/ClickableTextV2.tsx:107-129
const getWordClass = useCallback((word: string) => {
const wordAnalysis = findWordAnalysis(word)
const baseClass = "cursor-pointer transition-all duration-200 rounded relative mx-0.5 px-1 py-0.5"
if (!wordAnalysis) return ""
const isPhrase = getWordProperty(wordAnalysis, 'isPhrase')
if (isPhrase) return ""
const difficultyLevel = getWordProperty(wordAnalysis, 'difficultyLevel') || 'A1'
const userLevel = typeof window !== 'undefined' ? localStorage.getItem('userEnglishLevel') || 'A2' : 'A2'
const userIndex = getLevelIndex(userLevel)
const wordIndex = getLevelIndex(difficultyLevel)
if (userIndex > wordIndex) {
return `${baseClass} bg-gray-50 border border-dashed border-gray-300 hover:bg-gray-100 hover:border-gray-400 text-gray-600 opacity-80`
} else if (userIndex === wordIndex) {
return `${baseClass} bg-green-50 border border-green-200 hover:bg-green-100 hover:shadow-lg transform hover:-translate-y-0.5 text-green-700 font-medium`
} else {
return `${baseClass} bg-orange-50 border border-orange-200 hover:bg-orange-100 hover:shadow-lg transform hover:-translate-y-0.5 text-orange-700 font-medium`
}
}, [findWordAnalysis, getWordProperty, getLevelIndex])
統計卡片計算
詞彙分類統計
// 位置: frontend/app/generate/page.tsx:353-383
const vocabularyStats = useMemo(() => {
if (!sentenceAnalysis) return null
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2'
let simpleCount = 0, moderateCount = 0, difficultCount = 0, phraseCount = 0
Object.entries(sentenceAnalysis).forEach(([, wordData]: [string, any]) => {
const isPhrase = wordData?.isPhrase || wordData?.IsPhrase
const difficultyLevel = wordData?.difficultyLevel || 'A1'
if (isPhrase) {
phraseCount++
} else {
const userIndex = getLevelIndex(userLevel)
const wordIndex = getLevelIndex(difficultyLevel)
if (userIndex > wordIndex) {
simpleCount++
} else if (userIndex === wordIndex) {
moderateCount++
} else {
difficultCount++
}
}
})
return { simpleCount, moderateCount, difficultCount, phraseCount }
}, [sentenceAnalysis])
🏗️ 後端處理架構
Gemini API整合
AI分析服務實現
// 位置: backend/Services/GeminiService.cs:33-56
public async Task<SentenceAnalysisData> AnalyzeSentenceAsync(string inputText, string userLevel, 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);
var prompt = BuildAnalysisPrompt(inputText, userLevel, options);
var response = await CallGeminiAPI(prompt);
var analysisData = ParseGeminiResponse(response, inputText, userLevel);
var processingTime = (DateTime.UtcNow - startTime).TotalSeconds;
analysisData.Metadata.ProcessingDate = DateTime.UtcNow;
_logger.LogInformation("Sentence analysis completed in {ProcessingTime}s", processingTime);
return analysisData;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error analyzing sentence: {Text}", inputText);
throw;
}
}
智能Prompt構建
// 位置: backend/Services/GeminiService.cs:58-105
private string BuildAnalysisPrompt(string inputText, string userLevel, AnalysisOptions options)
{
var userIndex = Array.IndexOf(_cefrLevels, userLevel);
var targetLevels = GetTargetLevels(userIndex);
return $@"
請分析以下英文句子並以JSON格式回應:
句子: ""{inputText}""
學習者程度: {userLevel}
請提供完整的分析,包含:
1. 語法檢查:檢查是否有語法錯誤,如有則提供修正建議
2. 詞彙分析:分析句子中每個有意義的詞彙
3. 中文翻譯:提供自然流暢的繁體中文翻譯
4. 慣用語識別:識別句子中的慣用語和片語
詞彙分析要求:
- 為每個詞彙標註CEFR等級 (A1-C2)
- 如果是慣用語,設置 isPhrase: true
- 提供IPA發音標記
- 包含同義詞
- 提供適當的例句和翻譯
重要:回應必須是有效的JSON格式,不要包含任何其他文字。";
}
緩存機制整合
分析結果緩存
// 位置: backend/Services/AnalysisCacheService.cs:33-60
public async Task<SentenceAnalysisCache?> GetCachedAnalysisAsync(string inputText)
{
try
{
var textHash = GenerateTextHash(inputText);
var cached = await _context.SentenceAnalysisCache
.FirstOrDefaultAsync(c => c.InputTextHash == textHash && c.ExpiresAt > DateTime.UtcNow);
if (cached != null)
{
// 更新訪問統計
cached.AccessCount++;
cached.LastAccessedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
_logger.LogInformation("Cache hit for text hash: {TextHash}", textHash);
return cached;
}
_logger.LogInformation("Cache miss for text hash: {TextHash}", textHash);
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting cached analysis for text: {InputText}", inputText);
return null;
}
}
🎨 前端UI串接
詞彙標記渲染
ClickableTextV2組件
// 位置: frontend/components/ClickableTextV2.tsx:281-299
return (
<div className="relative">
<div className="text-lg" style={{lineHeight: '2.5'}}>
{words.map((word, index) => {
if (word.trim() === '' || /^[.,!?;:\s]+$/.test(word)) {
return <span key={index}>{word}</span>
}
const className = getWordClass(word)
const icon = getWordIcon(word)
return (
<span
key={index}
className={className}
onClick={(e) => handleWordClick(word, e)}
>
{word}
{icon}
</span>
)
})}
</div>
<VocabPopup />
</div>
)
統計卡片展示
四張統計卡片實現
// 位置: frontend/app/generate/page.tsx:581-605
{vocabularyStats && (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mb-6">
{/* 簡單詞彙卡片 */}
<div className="bg-gray-50 border border-dashed border-gray-300 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-gray-600 mb-1">{vocabularyStats.simpleCount}</div>
<div className="text-gray-600 text-xs sm:text-sm font-medium">太簡單啦</div>
</div>
{/* 適中詞彙卡片 */}
<div className="bg-green-50 border border-green-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-green-700 mb-1">{vocabularyStats.moderateCount}</div>
<div className="text-green-700 text-xs sm:text-sm font-medium">重點學習</div>
</div>
{/* 艱難詞彙卡片 */}
<div className="bg-orange-50 border border-orange-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-orange-700 mb-1">{vocabularyStats.difficultCount}</div>
<div className="text-orange-700 text-xs sm:text-sm font-medium">有點挑戰</div>
</div>
{/* 慣用語卡片 */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-blue-700 mb-1">{vocabularyStats.phraseCount}</div>
<div className="text-blue-700 text-xs sm:text-sm font-medium">慣用語</div>
</div>
</div>
)}
🔒 認證與安全串接
JWT Token處理
前端Token管理
// 位置: frontend/lib/services/auth.ts:77-81
if (response.success && response.data?.token) {
// Store token in localStorage
localStorage.setItem('auth_token', response.data.token);
localStorage.setItem('user_data', JSON.stringify(response.data.user));
}
// 位置: frontend/lib/services/flashcards.ts:72-82
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const token = this.getAuthToken();
const url = `${API_BASE_URL}/api${endpoint}`;
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...(token && { 'Authorization': `Bearer ${token}` }),
...options.headers,
},
...options,
});
}
後端認證驗證
// 位置: backend/Controllers/AIController.cs:123-135
private string GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)
?? User.FindFirst("sub")
?? User.FindFirst("user_id");
if (userIdClaim?.Value == null)
{
throw new UnauthorizedAccessException("用戶ID未找到");
}
return userIdClaim.Value;
}
🔄 錯誤處理串接
前端錯誤處理
API錯誤捕獲
// 位置: frontend/app/generate/page.tsx:89-99
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error?.message || `API請求失敗: ${response.status}`)
}
const result = await response.json()
if (!result.success || !result.data) {
throw new Error('API回應格式錯誤')
}
優雅錯誤回退
// 位置: frontend/app/generate/page.tsx:125-137
} catch (error) {
console.error('Error in sentence analysis:', error)
setGrammarCorrection({
hasErrors: true,
originalText: textInput,
correctedText: textInput,
corrections: []
})
setSentenceMeaning('分析過程中發生錯誤,請稍後再試。')
setFinalText(textInput)
setShowAnalysisView(true)
}
後端錯誤處理
結構化錯誤回應
// 位置: backend/Controllers/AIController.cs:137-155
private ApiErrorResponse CreateErrorResponse(string code, string message, object? details, string requestId)
{
var suggestions = GetSuggestionsForError(code);
return new ApiErrorResponse
{
Success = false,
Error = new ApiError
{
Code = code,
Message = message,
Details = details,
Suggestions = suggestions
},
RequestId = requestId,
Timestamp = DateTime.UtcNow
};
}
🚀 性能優化串接
前端性能優化
React記憶化
// 統計計算優化
const vocabularyStats = useMemo(() => {
// 計算邏輯...
}, [sentenceAnalysis])
// 詞彙分類函數記憶化
const getWordClass = useCallback((word: string) => {
// 分類邏輯...
}, [findWordAnalysis, getWordProperty, getLevelIndex])
// 彈窗定位計算優化
const calculatePopupPosition = useCallback((rect: DOMRect) => {
// 位置計算邏輯...
}, [])
後端性能優化
快取策略
// 快取鍵生成: 文本內容 + 用戶等級
var cacheKey = $"{request.InputText}_{request.UserLevel}";
// 快取過期時間: 24小時
await _cacheService.SetCachedAnalysisAsync(cacheKey, analysisData, TimeSpan.FromHours(24));
// 智能快取清理
[BackgroundService]
public class CacheCleanupService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await _cacheService.CleanExpiredCacheAsync();
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
}
}
🔧 配置管理串接
環境配置
前端環境設定
// API基礎URL配置
const API_BASE_URL = 'http://localhost:5008'; // 開發環境
// const API_BASE_URL = 'https://api.dramaling.com'; // 生產環境
// 字符限制配置
const MAX_MANUAL_INPUT_LENGTH = 300;
// CEFR等級配置
const CEFR_LEVELS = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'] as const;
後端配置管理
// User Secrets配置
dotnet user-secrets set "AI:GeminiApiKey" "your-real-gemini-api-key"
// appsettings.json配置
{
"Gemini": {
"ApiKey": "fallback-key" // 僅作為後備
},
"ConnectionStrings": {
"DefaultConnection": "Data Source=dramaling_test.db"
}
}
// 環境變數配置
Environment.GetEnvironmentVariable("GEMINI_API_KEY") // 優先級最高
📊 數據流向分析
請求數據流
1. 用戶輸入 → 前端驗證 → API請求構建
2. HTTP POST → 後端接收 → 模型驗證
3. 快取檢查 → AI服務調用 → 結果處理
4. 數據序列化 → HTTP回應 → 前端接收
5. JSON解析 → 狀態更新 → UI重新渲染
回應數據流
1. Gemini API → JSON回應 → 後端解析
2. 數據轉換 → DTO映射 → 統計計算
3. 快取儲存 → 結構化回應 → 前端接收
4. 狀態分派 → 組件更新 → 詞彙標記
5. 彈窗渲染 → 統計卡片 → 用戶互動
🧪 測試串接規格
前端測試
單元測試
// 測試API調用
describe('handleAnalyzeSentence', () => {
it('should call API with correct parameters', async () => {
// Mock fetch
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve(mockApiResponse),
})
);
await handleAnalyzeSentence();
expect(fetch).toHaveBeenCalledWith(
'http://localhost:5008/api/ai/analyze-sentence',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
inputText: 'test sentence',
userLevel: 'A2',
analysisMode: 'full'
})
}
);
});
});
整合測試
// 測試完整流程
describe('AI Analysis Integration', () => {
it('should complete full analysis workflow', async () => {
// 1. 輸入文本
fireEvent.change(screen.getByRole('textbox'), {
target: { value: 'She just join the team' }
});
// 2. 點擊分析按鈕
fireEvent.click(screen.getByText('🔍 分析句子'));
// 3. 等待API回應
await waitFor(() => {
expect(screen.getByText('語法修正建議')).toBeInTheDocument();
});
// 4. 驗證詞彙標記
expect(screen.getByText('she')).toHaveClass('bg-gray-50');
expect(screen.getByText('join')).toHaveClass('bg-orange-50');
});
});
後端測試
API端點測試
[Test]
public async Task AnalyzeSentence_WithValidInput_ReturnsSuccessResponse()
{
// Arrange
var request = new SentenceAnalysisRequest
{
InputText = "She just join the team",
UserLevel = "A2",
AnalysisMode = "full"
};
// Act
var response = await _controller.AnalyzeSentence(request);
// Assert
var okResult = Assert.IsType<OkObjectResult>(response.Result);
var analysisResponse = Assert.IsType<SentenceAnalysisResponse>(okResult.Value);
Assert.True(analysisResponse.Success);
Assert.NotNull(analysisResponse.Data);
Assert.NotEmpty(analysisResponse.Data.VocabularyAnalysis);
}
Gemini服務測試
[Test]
public async Task GeminiService_WithValidApiKey_CallsRealAPI()
{
// Arrange
var service = new GeminiService(_httpClient, _configuration, _logger);
// Act
var result = await service.AnalyzeSentenceAsync("test sentence", "A2", new AnalysisOptions());
// Assert
Assert.NotNull(result);
Assert.NotEmpty(result.VocabularyAnalysis);
Assert.Equal("gemini-pro", result.Metadata.AnalysisModel);
}
🔧 開發環境串接
啟動順序
開發環境啟動腳本
# 1. 啟動後端 (Port 5008)
cd backend/DramaLing.Api
dotnet run
# 2. 啟動前端 (Port 3000)
cd frontend
npm run dev
# 3. 設置Gemini API Key
dotnet user-secrets set "AI:GeminiApiKey" "your-real-api-key"
健康檢查
# 檢查後端健康狀態
curl http://localhost:5008/health
curl http://localhost:5008/api/ai/health
# 檢查前端運行狀態
curl http://localhost:3000
調試工具
前端調試
// 在瀏覽器Console中
console.log('API回應:', result.data);
console.log('詞彙分析:', sentenceAnalysis);
console.log('統計資料:', vocabularyStats);
// Network面板查看
// 檢查API請求和回應內容
// 驗證請求頭和載荷格式
後端調試
// 日誌輸出
_logger.LogInformation("Processing sentence analysis request {RequestId} for user {UserId}", requestId, userId);
_logger.LogInformation("Gemini API response: {Response}", response);
_logger.LogInformation("Sentence analysis completed in {ProcessingTime}s", processingTime);
// SQL查詢調試
// 檢查Entity Framework查詢日誌
// 監控數據庫性能指標
📈 監控與分析
性能監控
前端性能指標
// 性能測量
const startTime = performance.now();
await handleAnalyzeSentence();
const endTime = performance.now();
console.log(`分析耗時: ${endTime - startTime}ms`);
// 記憶體使用監控
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log('Performance:', entry.name, entry.duration);
});
});
observer.observe({ entryTypes: ['measure'] });
後端性能指標
// 請求處理時間
var stopwatch = Stopwatch.StartNew();
// ... 處理邏輯
stopwatch.Stop();
_logger.LogInformation("Request processed in {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);
// 資料庫查詢性能
services.AddDbContext<DramaLingDbContext>(options =>
{
options.UseSqlite(connectionString)
.EnableSensitiveDataLogging()
.LogTo(Console.WriteLine, LogLevel.Information);
});
🔮 未來擴展串接
批次分析支援
前端批次請求
interface BatchAnalysisRequest {
sentences: string[];
userLevel: string;
analysisMode: string;
}
const handleBatchAnalysis = async (sentences: string[]) => {
const response = await fetch('/api/ai/batch-analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sentences,
userLevel: getUserLevel(),
analysisMode: 'full'
})
});
};
後端批次處理
[HttpPost("batch-analyze")]
public async Task<ActionResult<BatchAnalysisResponse>> BatchAnalyze(
[FromBody] BatchAnalysisRequest request)
{
var results = new List<SentenceAnalysisData>();
foreach (var sentence in request.Sentences)
{
var analysis = await _geminiService.AnalyzeSentenceAsync(
sentence, request.UserLevel, request.Options);
results.Add(analysis);
}
return Ok(new BatchAnalysisResponse { Results = results });
}
即時分析支援
WebSocket連接
// 前端WebSocket客戶端
const ws = new WebSocket('ws://localhost:5008/ws/analysis');
ws.onmessage = (event) => {
const partialResult = JSON.parse(event.data);
updateAnalysisProgress(partialResult);
};
// 即時分析請求
ws.send(JSON.stringify({
type: 'analyze',
inputText: textInput,
userLevel: userLevel
}));
SignalR整合
// 後端SignalR Hub
public class AnalysisHub : Hub
{
public async Task StartAnalysis(string inputText, string userLevel)
{
await Clients.Caller.SendAsync("AnalysisStarted");
// 分段回傳結果
await Clients.Caller.SendAsync("GrammarCheckComplete", grammarResult);
await Clients.Caller.SendAsync("VocabularyAnalysisComplete", vocabResult);
await Clients.Caller.SendAsync("AnalysisComplete", finalResult);
}
}
📋 串接檢查清單
開發階段檢查
前端檢查項目
- API端點URL正確配置
- 請求格式符合後端期望
- 錯誤處理涵蓋所有情況
- 認證Token正確傳遞
- 響應數據正確解析
- UI狀態正確更新
- 性能優化已實施
後端檢查項目
- API端點路由正確設置
- 輸入驗證完整實現
- Gemini API正確調用
- 數據模型匹配前端需求
- 錯誤處理完善
- 緩存機制有效
- 日誌記錄詳細
生產環境檢查
安全性檢查
- API Key安全存儲
- JWT認證正確實施
- CORS策略適當配置
- 輸入驗證防止注入
- 錯誤訊息不洩露敏感資訊
效能檢查
- API回應時間 < 5秒
- 快取命中率 > 70%
- 記憶體使用穩定
- 資料庫查詢優化
- 並發處理正常
📞 故障排除指南
常見串接問題
CORS錯誤
錯誤: Access to fetch blocked by CORS policy
解決: 檢查後端CORS設定是否包含前端域名
認證失敗
錯誤: 401 Unauthorized
解決: 檢查JWT Token是否有效、格式是否正確
數據格式不匹配
錯誤: Cannot read property 'vocabularyAnalysis' of undefined
解決: 檢查後端回應格式是否符合前端期望
API超時
錯誤: Network timeout
解決: 檢查Gemini API是否可達、增加超時設定
調試步驟
- 檢查網路連通性:
curl http://localhost:5008/health - 驗證API端點:
curl -X POST http://localhost:5008/api/ai/analyze-sentence - 檢查前端日誌: 瀏覽器開發者工具Console
- 檢查後端日誌: dotnet應用程式輸出
- 驗證數據庫: 檢查SQLite文件和表結構
文件版本: v1.0
對應實現: main分支 commit 03c1756
最後驗證: 2025-01-25
下次檢查: 功能更新時