docs: 重新組織架構文檔體系並建立完整的詞卡管理 API 規格

- 重新組織文檔結構,整合到既有的 docs 目錄架構
- 移動架構文檔到 docs/04_technical/ 避免與現有結構衝突
- 移動需求規格文檔到 docs/01_requirement/ 保持分類邏輯
- 重新整理 AI 分析規格文檔到對應目錄
- 建立完整的 DramaLing 架構文檔體系:
  * 系統架構總覽:整體架構圖和技術棧說明
  * 後端架構詳細說明:ASP.NET Core 8.0 實現細節
  * 前端架構詳細說明:Next.js 15 組件設計模式
  * 詞卡管理 API 規格:基於實際程式碼的完整 API 文檔
- 更新所有跨文檔引用路徑和依賴關係
- 為所有文檔引用添加清楚的用途標注說明

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-24 03:56:26 +08:00
parent 7f85c79bc3
commit 0e2931ffe6
11 changed files with 3203 additions and 10 deletions

View File

@ -13,11 +13,191 @@
- 📊 學習進度追蹤
- 🔄 批量操作功能
## 2. 功能需求分析
## 2. 用戶需求分析
### 2.1 核心功能模組
### 2.1 用戶角色定義
#### 2.1.1 詞卡展示頁面 (FlashcardsPage)
#### 2.1.1 主要用戶
- **英語學習者**: 使用 DramaLing 學習英語詞彙的用戶
- **目標**: 有效管理和學習英語詞彙,提升英語水平
- **技能水平**: 具備基本的電腦操作能力,英語水平從初學者到高級
#### 2.1.2 用戶畫像
- **年齡**: 16-45歲
- **職業**: 學生、上班族、語言學習愛好者
- **使用場景**: 通勤時間、休息時間、專門的學習時段
- **設備**: 手機、平板、電腦
- **學習目標**: 考試準備、工作需要、興趣提升
### 2.2 用戶故事 (User Stories)
#### 2.2.1 詞卡檢視與瀏覽
```
作為一個英語學習者,
我想要瀏覽我所有的詞卡,
這樣我可以回顧我已經學習的詞彙。
驗收標準:
- 我可以看到詞卡列表,包含詞彙、翻譯、例句圖片
- 每個詞卡顯示 CEFR 難度等級和掌握度
- 我可以在「所有詞卡」和「收藏詞卡」之間切換
- 詞卡按創建時間排序顯示
```
#### 2.2.2 詞卡搜尋與篩選
```
作為一個英語學習者,
我想要快速找到特定的詞卡,
這樣我可以有效地複習特定的詞彙。
驗收標準:
- 我可以透過搜尋框輸入關鍵字搜尋詞彙、翻譯或定義
- 我可以使用進階篩選按 CEFR 等級、詞性、掌握度篩選
- 我可以使用快速篩選按鈕找到需要加強的詞卡
- 搜尋結果會高亮顯示關鍵字,並顯示找到的詞卡數量
```
#### 2.2.3 詞卡收藏管理
```
作為一個英語學習者,
我想要標記重要的詞卡為收藏,
這樣我可以優先複習重要的詞彙。
驗收標準:
- 我可以點擊星星圖標將詞卡加入收藏
- 已收藏的詞卡會顯示實心黃色星星
- 我可以在收藏分頁中查看所有收藏的詞卡
- 我可以隨時取消收藏某個詞卡
```
#### 2.2.4 詞卡編輯與管理
```
作為一個英語學習者,
我想要編輯或刪除詞卡,
這樣我可以更正錯誤或移除不需要的詞卡。
驗收標準:
- 我可以點擊編輯按鈕修改詞卡的任何欄位
- 我可以刪除不需要的詞卡,系統會要求確認
- 編輯後的詞卡會立即更新並保存
- 刪除操作會顯示成功或失敗的回饋訊息
```
#### 2.2.5 詞卡創建
```
作為一個英語學習者,
我想要手動創建新的詞卡,
這樣我可以將遇到的新詞彙加入學習列表。
驗收標準:
- 我可以點擊「新增詞卡」按鈕開啟創建表單
- 我可以填寫詞彙、翻譯、定義、發音、詞性、例句等欄位
- 提交後新詞卡會出現在詞卡列表中
- 如果有錯誤,系統會顯示明確的錯誤訊息
```
#### 2.2.6 詞卡詳細檢視
```
作為一個英語學習者,
我想要查看詞卡的完整詳細資訊,
這樣我可以深入了解詞彙的各種用法和學習狀態。
驗收標準:
- 我可以點擊「詳細」按鈕進入詞卡詳細頁面
- 詳細頁面顯示所有詞卡信息和學習統計
- 我可以在詳細頁面進行編輯或收藏操作
- 我可以查看學習記錄和複習歷史
```
### 2.3 用戶流程 (User Flows)
#### 2.3.1 詞卡瀏覽流程
```
用戶進入詞卡頁面 → 查看詞卡列表 → 選擇分頁(所有詞卡/收藏詞卡) → 瀏覽詞卡
用戶可以執行以下操作:
- 點擊收藏/取消收藏
- 點擊編輯進入編輯模式
- 點擊刪除(需確認)
- 點擊詳細查看完整信息
- 點擊發音按鈕播放音頻
```
#### 2.3.2 詞卡搜尋流程
```
用戶進入詞卡頁面 → 點擊搜尋框 → 輸入關鍵字 → 查看即時篩選結果
用戶可以進一步操作:
- 點擊「進階篩選」設定更多條件
- 使用快速篩選按鈕
- 清除搜尋條件
- 對搜尋結果執行詞卡操作
```
#### 2.3.3 詞卡創建流程
```
用戶進入詞卡頁面 → 點擊「新增詞卡」按鈕 → 填寫詞卡表單 → 點擊保存
表單驗證:
- 成功:新詞卡出現在列表中,顯示成功訊息
- 失敗:顯示錯誤訊息,保持表單開啟狀態
```
#### 2.3.4 詞卡編輯流程
```
用戶選擇詞卡 → 點擊「編輯」按鈕 → 修改詞卡欄位 → 點擊保存
編輯驗證:
- 成功:詞卡更新,顯示成功訊息,關閉編輯表單
- 失敗:顯示錯誤訊息,保持編輯模式
```
#### 2.3.5 詞卡刪除流程
```
用戶選擇詞卡 → 點擊「刪除」按鈕 → 確認刪除對話框 → 用戶確認
刪除處理:
- 成功:詞卡從列表中移除,顯示成功訊息
- 失敗:顯示錯誤訊息,詞卡保持在列表中
```
#### 2.3.6 詞卡收藏管理流程
```
用戶瀏覽詞卡 → 點擊星星圖標 → 切換收藏狀態
收藏狀態更新:
- 加入收藏:星星變為實心黃色,顯示成功訊息
- 取消收藏:星星變為空心灰色,顯示成功訊息
- 在收藏分頁中即時更新詞卡列表
```
#### 2.3.7 AI 生成詞卡流程 (與其他頁面整合)
```
用戶在詞卡頁面 → 點擊「AI 生成詞卡」按鈕 → 跳轉到 /generate 頁面
AI 分析流程:
用戶輸入句子 → AI 分析 → 查看分析結果 → 點擊保存詞卡 → 返回詞卡頁面查看
```
### 2.4 用戶體驗目標
#### 2.4.1 易用性目標
- **直觀操作**: 新用戶在 5 分鐘內能完成基本詞卡操作
- **快速搜尋**: 搜尋結果在 300ms 內顯示
- **清楚回饋**: 所有操作都有明確的成功/失敗回饋
- **一致設計**: 整個詞卡管理流程保持視覺和交互一致性
#### 2.4.2 效率目標
- **批量操作**: 支援多選和批量操作(未來功能)
- **鍵盤快捷鍵**: 支援 ESC 清除搜尋等常用快捷鍵
- **智能提示**: 搜尋框提供輸入建議(未來功能)
- **離線功能**: 基本瀏覽功能支援離線使用(未來功能)
## 3. 功能需求分析
### 3.1 核心功能模組
#### 3.1.1 詞卡展示頁面 (FlashcardsPage)
- **位置**: `/flashcards`
- **主要功能**: 集中管理和展示所有詞卡
@ -281,14 +461,35 @@ class FlashcardsService {
- **對比度**: 符合 WCAG 2.1 AA 標準
- **文字大小**: 支援縮放至 200% 而不影響功能
## 4. 技術實現規格
## 4. 技術約束與架構
### 4.1 前端技術架構
- **框架**: Next.js 15 with App Router
- **語言**: TypeScript
- **樣式**: Tailwind CSS
- **狀態管理**: React useState/useEffect hooks
- **API 通信**: Fetch API with error handling
> 📋 **架構文檔引用**
>
> 本功能需求必須遵循既有的系統架構,完整技術規格請參考:
> - [系統架構總覽](../04_technical/system-architecture.md)
> - [後端架構詳細說明](../04_technical/backend-architecture.md)
> - [前端架構詳細說明](../04_technical/frontend-architecture.md)
> - [詞卡 API 規格](../04_technical/flashcard-api-specification.md)
### 4.1 技術架構約束
#### 前端技術限制
- **框架**: 必須使用 Next.js 15 with App Router
- **語言**: 必須使用 TypeScript
- **樣式**: 必須使用 Tailwind CSS
- **狀態管理**: 使用 React hooks (useState/useEffect)
- **API 通信**: 使用現有的 flashcardsService
#### 後端技術限制
- **框架**: 必須使用 ASP.NET Core 8.0
- **資料庫**: 必須使用 SQLite + Entity Framework Core
- **API 格式**: 必須遵循現有的 RESTful API 設計
- **認證**: 必須相容現有的 JWT 認證機制
#### 資料模型約束
- **詞卡模型**: 必須使用現有的 Flashcard.cs 實體
- **API 回應**: 必須遵循 `{success, data?, error?}` 格式
- **關聯設計**: 必須保持與 User、CardSet 的現有關聯
### 4.2 資料處理流程

View File

@ -0,0 +1,945 @@
# DramaLing 詞卡管理 API 規格書
## 1. API 概覽
### 1.1 基本資訊
- **基礎 URL**: `http://localhost:5008/api` (開發環境)
- **控制器**: `FlashcardsController`
- **路由前綴**: `/api/flashcards`
- **認證方式**: JWT Bearer Token (開發階段暫時關閉)
- **資料格式**: JSON (UTF-8)
### 1.2 架構依賴
> 📋 **技術架構參考文檔**
>
> 本 API 規格書依賴以下文檔,建議閱讀順序:
>
> **🏗️ 系統架構文檔**
> - [系統架構總覽](../../04_technical/system-architecture.md) - 了解整體架構設計
> - [後端架構詳細說明](../../04_technical/backend-architecture.md) - 了解 ASP.NET Core 架構細節
>
> **📋 需求規格文檔**
> - [詞卡管理功能產品需求規格](../../01_requirement/詞卡管理功能產品需求規格.md) - 了解功能需求和用戶故事
## 2. 資料模型定義
### 2.1 詞卡實體 (Flashcard Entity)
#### C# 實體模型
```csharp
public class Flashcard
{
// 基本識別
public Guid Id { get; set; }
public Guid UserId { get; set; }
public Guid? CardSetId { get; set; }
// 詞卡內容
[Required, MaxLength(255)]
public string Word { get; set; }
[Required]
public string Translation { get; set; }
[Required]
public string Definition { get; set; }
[MaxLength(50)]
public string? PartOfSpeech { get; set; }
[MaxLength(255)]
public string? Pronunciation { get; set; }
public string? Example { get; set; }
public string? ExampleTranslation { get; set; }
// SM-2 學習算法參數
public float EasinessFactor { get; set; } = 2.5f;
public int Repetitions { get; set; } = 0;
public int IntervalDays { get; set; } = 1;
public DateTime NextReviewDate { get; set; }
// 學習統計
[Range(0, 100)]
public int MasteryLevel { get; set; } = 0;
public int TimesReviewed { get; set; } = 0;
public int TimesCorrect { get; set; } = 0;
public DateTime? LastReviewedAt { get; set; }
// 狀態管理
public bool IsFavorite { get; set; } = false;
public bool IsArchived { get; set; } = false;
[MaxLength(10)]
public string? DifficultyLevel { get; set; } // A1-C2
// 時間戳記
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
```
#### TypeScript 前端型別定義
```typescript
interface Flashcard {
id: string;
word: string;
translation: string;
definition: string;
partOfSpeech: string;
pronunciation: string;
example: string;
exampleTranslation?: string;
masteryLevel: number; // 0-100
timesReviewed: number;
isFavorite: boolean;
nextReviewDate: string; // ISO Date
difficultyLevel: string; // A1, A2, B1, B2, C1, C2
createdAt: string; // ISO Date
updatedAt?: string; // ISO Date
}
interface CreateFlashcardRequest {
word: string;
translation: string;
definition: string;
pronunciation: string;
partOfSpeech: string;
example: string;
exampleTranslation?: string;
}
```
### 2.2 API 回應格式標準
#### 成功回應格式
```json
{
"success": true,
"data": {
// 實際資料內容
},
"message": "操作成功描述" // 可選
}
```
#### 錯誤回應格式
```json
{
"success": false,
"error": "錯誤描述",
"isDuplicate": true, // 特殊情況:重複資料
"existingCard": { /* 現有詞卡資料 */ } // 重複時的現有資料
}
```
## 3. API 端點規格
### 3.1 端點清單
| 方法 | 端點 | 描述 | 狀態 |
|------|------|------|------|
| GET | `/api/flashcards` | 取得詞卡列表 | ✅ 已實現 |
| GET | `/api/flashcards/{id}` | 取得單一詞卡 | ✅ 已實現 |
| POST | `/api/flashcards` | 創建新詞卡 | ✅ 已實現 |
| PUT | `/api/flashcards/{id}` | 更新詞卡 | ✅ 已實現 |
| DELETE | `/api/flashcards/{id}` | 刪除詞卡 | ✅ 已實現 |
| POST | `/api/flashcards/{id}/favorite` | 切換收藏狀態 | ✅ 已實現 |
### 3.2 詳細 API 規格
#### 📖 GET /api/flashcards
**功能**: 取得用戶的詞卡列表,支援搜尋和篩選
**查詢參數**:
```typescript
interface GetFlashcardsParams {
search?: string; // 搜尋關鍵字,搜尋範圍:詞彙、翻譯、定義
favoritesOnly?: boolean; // 僅顯示收藏詞卡 (預設: false)
}
```
**實際實現邏輯**:
```csharp
// 搜尋篩選邏輯
if (!string.IsNullOrEmpty(search))
{
query = query.Where(f =>
f.Word.Contains(search) ||
f.Translation.Contains(search) ||
(f.Definition != null && f.Definition.Contains(search)));
}
// 收藏篩選
if (favoritesOnly)
{
query = query.Where(f => f.IsFavorite);
}
// 排序:按創建時間降序
var flashcards = await query.OrderByDescending(f => f.CreatedAt).ToListAsync();
```
**成功回應**:
```json
{
"success": true,
"data": {
"flashcards": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"word": "sophisticated",
"translation": "精密的",
"definition": "Highly developed or complex",
"partOfSpeech": "adjective",
"pronunciation": "/səˈfɪstɪkeɪtɪd/",
"example": "A sophisticated system",
"exampleTranslation": "一個精密的系統",
"masteryLevel": 75,
"timesReviewed": 12,
"isFavorite": true,
"nextReviewDate": "2025-09-25T00:00:00Z",
"difficultyLevel": "C1",
"createdAt": "2025-09-20T08:30:00Z",
"updatedAt": "2025-09-24T10:15:00Z"
}
],
"count": 1
}
}
```
**請求範例**:
```bash
# 取得所有詞卡
curl "http://localhost:5008/api/flashcards"
# 搜尋包含 "sophisticated" 的詞卡
curl "http://localhost:5008/api/flashcards?search=sophisticated"
# 僅取得收藏詞卡
curl "http://localhost:5008/api/flashcards?favoritesOnly=true"
# 組合搜尋:搜尋收藏詞卡中包含 "精密" 的詞卡
curl "http://localhost:5008/api/flashcards?search=精密&favoritesOnly=true"
```
#### 📖 GET /api/flashcards/{id}
**功能**: 取得單一詞卡的完整資訊
**路徑參數**:
- `id`: 詞卡唯一識別碼 (GUID 格式)
**成功回應**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"word": "sophisticated",
// ... 完整詞卡資料,格式同列表 API
"createdAt": "2025-09-20T08:30:00Z",
"updatedAt": "2025-09-24T10:15:00Z"
}
}
```
**錯誤回應**:
```json
{
"success": false,
"error": "詞卡不存在"
}
```
#### ✏️ POST /api/flashcards
**功能**: 創建新的詞卡
**請求體**:
```json
{
"word": "elaborate",
"translation": "詳細說明",
"definition": "To explain in detail",
"pronunciation": "/ɪˈlæbərət/",
"partOfSpeech": "verb",
"example": "Please elaborate on your idea",
"exampleTranslation": "請詳細說明你的想法"
}
```
**實際實現邏輯**:
```csharp
// 1. 自動創建測試用戶 (開發階段)
var testUser = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (testUser == null) {
// 自動創建測試用戶邏輯
}
// 2. 重複詞卡檢測
var existing = await _context.Flashcards
.FirstOrDefaultAsync(f => f.UserId == userId &&
f.Word.ToLower() == request.Word.ToLower() &&
!f.IsArchived);
// 3. 創建新詞卡
var flashcard = new Flashcard
{
Id = Guid.NewGuid(),
UserId = userId,
Word = request.Word,
Translation = request.Translation,
// ... 其他欄位
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
```
**成功回應**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"word": "elaborate",
// ... 完整創建的詞卡資料
"createdAt": "2025-09-24T10:30:00Z"
},
"message": "詞卡創建成功"
}
```
**重複詞卡回應**:
```json
{
"success": false,
"error": "詞卡已存在",
"isDuplicate": true,
"existingCard": {
"id": "existing-id",
"word": "elaborate",
// ... 現有詞卡資料
}
}
```
#### ✏️ PUT /api/flashcards/{id}
**功能**: 更新現有詞卡
**路徑參數**:
- `id`: 詞卡唯一識別碼 (GUID)
**請求體**: 與 POST 相同格式
**實際實現邏輯**:
```csharp
// 1. 查找現有詞卡
var flashcard = await _context.Flashcards
.FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId && !f.IsArchived);
if (flashcard == null)
{
return NotFound(new { Success = false, Error = "詞卡不存在" });
}
// 2. 更新欄位
flashcard.Word = request.Word;
flashcard.Translation = request.Translation;
// ... 更新其他欄位
flashcard.UpdatedAt = DateTime.UtcNow;
// 3. 保存變更
await _context.SaveChangesAsync();
```
**成功回應**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
// ... 更新後的完整詞卡資料
"updatedAt": "2025-09-24T10:35:00Z"
},
"message": "詞卡更新成功"
}
```
#### 🗑️ DELETE /api/flashcards/{id}
**功能**: 刪除詞卡 (軟刪除機制)
**路徑參數**:
- `id`: 詞卡唯一識別碼 (GUID)
**實際實現邏輯**:
```csharp
// 軟刪除:設定 IsArchived = true
var flashcard = await _context.Flashcards
.FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId && !f.IsArchived);
if (flashcard == null)
{
return NotFound(new { Success = false, Error = "詞卡不存在" });
}
flashcard.IsArchived = true;
flashcard.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
```
**成功回應**:
```json
{
"success": true,
"message": "詞卡已刪除"
}
```
#### ⭐ POST /api/flashcards/{id}/favorite
**功能**: 切換詞卡的收藏狀態
**路徑參數**:
- `id`: 詞卡唯一識別碼 (GUID)
**實際實現邏輯**:
```csharp
var flashcard = await _context.Flashcards
.FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId && !f.IsArchived);
if (flashcard == null)
{
return NotFound(new { Success = false, Error = "詞卡不存在" });
}
// 切換收藏狀態
flashcard.IsFavorite = !flashcard.IsFavorite;
flashcard.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
```
**成功回應**:
```json
{
"success": true,
"data": {
"isFavorite": true
},
"message": "已加入收藏"
}
```
## 4. 前端整合規格
### 4.1 FlashcardsService 類別
#### TypeScript 服務實現
```typescript
class FlashcardsService {
private readonly baseURL = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5008'}/api`;
// 統一請求處理
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const response = await fetch(`${this.baseURL}${endpoint}`, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Network error' }));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
return response.json();
}
// API 方法實現
async getFlashcards(search?: string, favoritesOnly: boolean = false): Promise<ApiResponse<{flashcards: Flashcard[], count: number}>> {
const params = new URLSearchParams();
if (search) params.append('search', search);
if (favoritesOnly) params.append('favoritesOnly', 'true');
const queryString = params.toString();
const endpoint = `/flashcards${queryString ? `?${queryString}` : ''}`;
return await this.makeRequest<ApiResponse<{flashcards: Flashcard[], count: number}>>(endpoint);
}
async createFlashcard(data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>> {
return await this.makeRequest<ApiResponse<Flashcard>>('/flashcards', {
method: 'POST',
body: JSON.stringify(data),
});
}
async updateFlashcard(id: string, data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>> {
return await this.makeRequest<ApiResponse<Flashcard>>(`/flashcards/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
async deleteFlashcard(id: string): Promise<ApiResponse<void>> {
return await this.makeRequest<ApiResponse<void>>(`/flashcards/${id}`, {
method: 'DELETE',
});
}
async toggleFavorite(id: string): Promise<ApiResponse<void>> {
return await this.makeRequest<ApiResponse<void>>(`/flashcards/${id}/favorite`, {
method: 'POST',
});
}
}
export const flashcardsService = new FlashcardsService();
```
### 4.2 前端使用範例
#### 詞卡列表載入
```typescript
const loadFlashcards = async () => {
try {
setLoading(true);
const result = await flashcardsService.getFlashcards();
if (result.success && result.data) {
setFlashcards(result.data.flashcards);
} else {
setError(result.error || 'Failed to load flashcards');
}
} catch (err) {
setError('Failed to load flashcards');
} finally {
setLoading(false);
}
};
```
#### 詞卡保存 (含重複檢測)
```typescript
const handleSaveWord = async (word: string, analysis: any) => {
try {
const cardData = {
word: word,
translation: analysis.translation || '',
definition: analysis.definition || '',
pronunciation: analysis.pronunciation || `/${word}/`,
partOfSpeech: analysis.partOfSpeech || 'unknown',
example: `Example sentence with ${word}.`
};
const response = await flashcardsService.createFlashcard(cardData);
if (response.success) {
alert(`✅ 已成功將「${word}」保存到詞卡庫!`);
return { success: true };
} else if (response.error && response.error.includes('已存在')) {
alert(`⚠️ 詞卡「${word}」已經存在於詞卡庫中`);
return { success: false, error: 'duplicate' };
} else {
throw new Error(response.error || '保存失敗');
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : '保存失敗';
alert(`❌ 保存詞卡失敗: ${errorMessage}`);
return { success: false, error: errorMessage };
}
};
```
## 5. 搜尋與篩選功能
### 5.1 後端搜尋實現
#### 支援的搜尋欄位
```csharp
// 目前實現的搜尋範圍
query = query.Where(f =>
f.Word.Contains(search) || // 詞彙本身
f.Translation.Contains(search) || // 中文翻譯
(f.Definition != null && f.Definition.Contains(search)) // 英文定義
);
// 未來可擴展的搜尋範圍
// f.Example.Contains(search) || // 例句內容
// f.ExampleTranslation.Contains(search) // 例句翻譯
```
#### 搜尋邏輯特性
- **大小寫敏感**: 目前使用 `Contains()` 進行大小寫敏感搜尋
- **部分匹配**: 支援關鍵字部分匹配
- **邏輯運算**: OR 邏輯 (任一欄位包含關鍵字即匹配)
### 5.2 前端搜尋與篩選
#### 即時搜尋實現
```typescript
// 前端即時篩選邏輯
const filteredCards = allCards.filter(card => {
// 基本文字搜尋
if (searchTerm) {
const searchLower = searchTerm.toLowerCase();
const matchesText =
card.word?.toLowerCase().includes(searchLower) ||
card.translation?.toLowerCase().includes(searchLower) ||
card.definition?.toLowerCase().includes(searchLower);
if (!matchesText) return false;
}
// CEFR 等級篩選
if (searchFilters.cefrLevel && card.difficultyLevel !== searchFilters.cefrLevel) {
return false;
}
// 詞性篩選
if (searchFilters.partOfSpeech && card.partOfSpeech !== searchFilters.partOfSpeech) {
return false;
}
// 掌握度篩選
if (searchFilters.masteryLevel) {
const mastery = card.masteryLevel || 0;
if (searchFilters.masteryLevel === 'high' && mastery < 80) return false;
if (searchFilters.masteryLevel === 'medium' && (mastery < 60 || mastery >= 80)) return false;
if (searchFilters.masteryLevel === 'low' && mastery >= 60) return false;
}
// 收藏篩選
if (searchFilters.onlyFavorites && !card.isFavorite) {
return false;
}
return true;
});
```
#### 進階篩選選項
```typescript
interface SearchFilters {
cefrLevel: string; // A1, A2, B1, B2, C1, C2
partOfSpeech: string; // noun, verb, adjective, adverb, preposition, interjection
masteryLevel: string; // high (80%+), medium (60-79%), low (<60%)
onlyFavorites: boolean; // 僅收藏詞卡
}
```
## 6. 錯誤處理機制
### 6.1 後端錯誤處理
#### 統一錯誤處理模式
```csharp
try
{
// API 邏輯
return Ok(new { Success = true, Data = result });
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, "Database error during flashcard operation");
return StatusCode(500, new { Success = false, Error = "資料庫操作失敗" });
}
catch (ArgumentException ex)
{
_logger.LogWarning(ex, "Invalid argument for flashcard operation");
return BadRequest(new { Success = false, Error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error during flashcard operation");
return StatusCode(500, new { Success = false, Error = "內部伺服器錯誤" });
}
```
#### 特殊情況處理
```csharp
// 重複詞卡檢測
if (existing != null)
{
return Ok(new
{
Success = false,
Error = "詞卡已存在",
IsDuplicate = true,
ExistingCard = new { /* 現有詞卡資料 */ }
});
}
// 詞卡不存在
if (flashcard == null)
{
return NotFound(new { Success = false, Error = "詞卡不存在" });
}
```
### 6.2 前端錯誤處理
#### API 服務層錯誤處理
```typescript
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
try {
const response = await fetch(`${this.baseURL}${endpoint}`, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Network error' }));
throw new Error(errorData.error || errorData.details || `HTTP ${response.status}`);
}
return response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
```
#### 用戶反饋機制
```typescript
// 成功操作反饋
if (response.success) {
alert(`✅ 已成功將「${word}」保存到詞卡庫!`);
return { success: true };
}
// 重複詞卡反饋
else if (response.error && response.error.includes('已存在')) {
alert(`⚠️ 詞卡「${word}」已經存在於詞卡庫中`);
return { success: false, error: 'duplicate' };
}
// 一般錯誤反饋
else {
alert(`❌ 保存詞卡失敗: ${response.error}`);
return { success: false, error: response.error };
}
```
## 7. 認證與授權
### 7.1 開發階段認證
#### 目前實現 (測試模式)
```csharp
[AllowAnonymous] // 暫時移除認證要求
public class FlashcardsController : ControllerBase
{
private Guid GetUserId()
{
// 使用固定測試用戶 ID
return Guid.Parse("00000000-0000-0000-0000-000000000001");
}
}
```
#### 自動測試用戶創建
```csharp
// 確保測試用戶存在
var testUser = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (testUser == null)
{
testUser = new User
{
Id = userId,
Username = "testuser",
Email = "test@example.com",
DisplayName = "測試用戶",
SubscriptionType = "free",
EnglishLevel = "A2",
CreatedAt = DateTime.UtcNow
};
_context.Users.Add(testUser);
await _context.SaveChangesAsync();
}
```
### 7.2 生產環境認證 (未來啟用)
#### JWT Token 解析
```csharp
[Authorize] // 生產環境啟用
public class FlashcardsController : ControllerBase
{
private Guid GetUserId()
{
var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ??
User.FindFirst("sub")?.Value;
if (Guid.TryParse(userIdString, out var userId))
return userId;
throw new UnauthorizedAccessException("Invalid user ID in token");
}
}
```
## 8. 效能優化
### 8.1 資料庫查詢優化
#### 索引建議
```sql
-- 用戶詞卡查詢索引
CREATE INDEX IX_Flashcards_UserId_IsArchived ON Flashcards(UserId, IsArchived);
-- 搜尋優化索引
CREATE INDEX IX_Flashcards_Word ON Flashcards(Word);
CREATE INDEX IX_Flashcards_Translation ON Flashcards(Translation);
-- 收藏篩選索引
CREATE INDEX IX_Flashcards_IsFavorite ON Flashcards(IsFavorite);
-- 複合查詢索引
CREATE INDEX IX_Flashcards_UserId_IsFavorite_IsArchived ON Flashcards(UserId, IsFavorite, IsArchived);
```
#### 查詢優化技巧
```csharp
// 使用 AsNoTracking 提升查詢效能 (只讀查詢)
var flashcards = await query
.AsNoTracking()
.OrderByDescending(f => f.CreatedAt)
.ToListAsync();
// 選擇性載入欄位 (避免載入不必要的關聯資料)
.Select(f => new {
f.Id, f.Word, f.Translation, f.Definition,
// 僅選擇需要的欄位
})
```
### 8.2 快取策略 (未來實現)
#### 記憶體快取
```csharp
// 用戶詞卡列表快取 (30分鐘)
var cacheKey = $"flashcards:user:{userId}";
var cachedCards = await _cacheService.GetAsync<List<Flashcard>>(cacheKey);
if (cachedCards == null)
{
cachedCards = await LoadFlashcardsFromDatabase(userId);
await _cacheService.SetAsync(cacheKey, cachedCards, TimeSpan.FromMinutes(30));
}
```
#### 搜尋結果快取
```csharp
// 搜尋結果快取 (10分鐘)
var searchCacheKey = $"search:{userId}:{searchTerm}:{favoritesOnly}";
var cachedResults = await _cacheService.GetAsync<SearchResult>(searchCacheKey);
```
## 9. 測試規格
### 9.1 API 測試用例
#### 功能測試
```bash
# 測試詞卡創建
curl -X POST http://localhost:5008/api/flashcards \
-H "Content-Type: application/json" \
-d '{
"word": "test",
"translation": "測試",
"definition": "A trial or examination",
"pronunciation": "/test/",
"partOfSpeech": "noun",
"example": "This is a test sentence"
}'
# 測試搜尋功能
curl "http://localhost:5008/api/flashcards?search=test"
# 測試收藏功能
curl -X POST http://localhost:5008/api/flashcards/{id}/favorite
# 測試詞卡更新
curl -X PUT http://localhost:5008/api/flashcards/{id} \
-H "Content-Type: application/json" \
-d '{ /* 更新的詞卡資料 */ }'
# 測試詞卡刪除
curl -X DELETE http://localhost:5008/api/flashcards/{id}
```
#### 邊界條件測試
```bash
# 測試重複詞卡創建
curl -X POST http://localhost:5008/api/flashcards \
-d '{"word": "existing-word", ...}'
# 預期回應: success: false, isDuplicate: true
# 測試不存在的詞卡操作
curl http://localhost:5008/api/flashcards/non-existent-id
# 預期回應: 404 Not Found
# 測試空搜尋
curl "http://localhost:5008/api/flashcards?search="
# 預期回應: 返回所有詞卡
```
### 9.2 效能測試
#### 載入測試
```bash
# 測試大量詞卡載入 (1000+ 詞卡)
time curl "http://localhost:5008/api/flashcards"
# 預期: < 2秒
# 測試搜尋效能
time curl "http://localhost:5008/api/flashcards?search=sophisticated"
# 預期: < 300ms
```
## 10. 部署與監控
### 10.1 健康檢查
#### API 健康檢查端點
```csharp
// Program.cs 中配置
services.AddHealthChecks()
.AddDbContextCheck<DramaLingDbContext>();
app.MapHealthChecks("/health");
```
**健康檢查請求**:
```bash
curl http://localhost:5008/health
```
### 10.2 日誌監控
#### 結構化日誌
```csharp
_logger.LogInformation("Creating flashcard for user {UserId}, word: {Word}",
userId, request.Word);
_logger.LogError(ex, "Failed to create flashcard for user {UserId}", userId);
_logger.LogWarning("Duplicate flashcard creation attempt: {Word} for user {UserId}",
request.Word, userId);
```
#### 關鍵指標監控
- **API 響應時間**: 平均 < 200ms
- **成功率**: > 99.5%
- **重複詞卡檢測**: 準確率 100%
- **資料庫連接**: 健康狀態監控
---
**文檔版本**: v1.0
**建立日期**: 2025-09-24
**基於**: FlashcardsController.cs v1.0
**維護負責**: API 開發團隊
**更新頻率**: 控制器變更時同步更新
> 📋 **相關參考文檔**
>
> **📋 需求與規格**
> - [詞卡管理功能需求規格](../../01_requirement/詞卡管理功能產品需求規格.md) - 查看完整功能需求和用戶故事
>
> **🏗️ 技術架構**
> - [後端架構詳細說明](../../04_technical/backend-architecture.md) - 了解後端技術實現細節
> - [前端架構詳細說明](../../04_technical/frontend-architecture.md) - 了解前端整合方式

View File

@ -0,0 +1,590 @@
# DramaLing 後端架構詳細說明
## 1. 技術棧概覽
### 1.1 核心技術
- **框架**: ASP.NET Core 8.0
- **語言**: C# .NET 8
- **ORM**: Entity Framework Core 8.0
- **資料庫**: SQLite 3.x
- **認證**: JWT Bearer Token
- **依賴注入**: Microsoft.Extensions.DependencyInjection
### 1.2 專案結構
```
backend/DramaLing.Api/
├── Controllers/ # API 控制器
│ ├── FlashcardsController.cs
│ ├── AIController.cs
│ └── AuthController.cs
├── Models/
│ ├── Entities/ # 資料模型
│ │ ├── Flashcard.cs
│ │ ├── User.cs
│ │ └── CardSet.cs
│ ├── DTOs/ # 資料傳輸物件
│ └── Configuration/ # 配置模型
├── Data/ # 資料存取層
│ ├── DramaLingDbContext.cs
│ └── Migrations/
├── Services/ # 業務邏輯層
│ ├── AI/ # AI 服務
│ ├── Caching/ # 快取服務
│ └── AuthService.cs
├── Extensions/ # 擴展方法
│ └── ServiceCollectionExtensions.cs
└── Program.cs # 應用程式入口
```
## 2. 資料模型架構
### 2.1 詞卡實體模型 (Flashcard)
```csharp
public class Flashcard
{
// 主鍵和關聯
public Guid Id { get; set; }
public Guid UserId { get; set; }
public Guid? CardSetId { get; set; }
// 詞卡內容
[Required, MaxLength(255)]
public string Word { get; set; }
[Required]
public string Translation { get; set; }
[Required]
public string Definition { get; set; }
[MaxLength(50)]
public string? PartOfSpeech { get; set; }
[MaxLength(255)]
public string? Pronunciation { get; set; }
public string? Example { get; set; }
public string? ExampleTranslation { get; set; }
// SM-2 學習算法參數
public float EasinessFactor { get; set; } = 2.5f;
public int Repetitions { get; set; } = 0;
public int IntervalDays { get; set; } = 1;
public DateTime NextReviewDate { get; set; }
// 學習統計
[Range(0, 100)]
public int MasteryLevel { get; set; } = 0;
public int TimesReviewed { get; set; } = 0;
public int TimesCorrect { get; set; } = 0;
public DateTime? LastReviewedAt { get; set; }
// 狀態管理
public bool IsFavorite { get; set; } = false;
public bool IsArchived { get; set; } = false;
[MaxLength(10)]
public string? DifficultyLevel { get; set; } // A1-C2
// 時間戳記
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// 導航屬性
public virtual User User { get; set; }
public virtual CardSet? CardSet { get; set; }
public virtual ICollection<StudyRecord> StudyRecords { get; set; }
public virtual ICollection<FlashcardTag> FlashcardTags { get; set; }
public virtual ICollection<ErrorReport> ErrorReports { get; set; }
}
```
### 2.2 資料庫關聯設計
```
Users (1) ──────────────── (*) Flashcards
│ │
│ │ (*)
│ │
└─── (1) CardSets (*) ───────┘
StudyRecords (*) ──── (1) Flashcards
ErrorReports (*) ──── (1) Flashcards
FlashcardTags (*) ─── (1) Flashcards
```
## 3. API 架構設計
### 3.1 控制器架構
#### FlashcardsController.cs
```csharp
[ApiController]
[Route("api/flashcards")]
[AllowAnonymous] // 開發階段暫時移除認證
public class FlashcardsController : ControllerBase
{
private readonly DramaLingDbContext _context;
private readonly ILogger<FlashcardsController> _logger;
// 標準 RESTful API 端點
[HttpGet] // GET /api/flashcards
[HttpGet("{id}")] // GET /api/flashcards/{id}
[HttpPost] // POST /api/flashcards
[HttpPut("{id}")] // PUT /api/flashcards/{id}
[HttpDelete("{id}")] // DELETE /api/flashcards/{id}
[HttpPost("{id}/favorite")] // POST /api/flashcards/{id}/favorite
}
```
### 3.2 API 回應格式標準化
#### 成功回應格式
```json
{
"success": true,
"data": {
"flashcards": [...],
"count": 42
},
"message": "操作成功"
}
```
#### 錯誤回應格式
```json
{
"success": false,
"error": "錯誤描述",
"details": "詳細錯誤信息",
"timestamp": "2025-09-24T10:30:00Z"
}
```
### 3.3 查詢參數支援
#### GET /api/flashcards
```csharp
public async Task<ActionResult> GetFlashcards(
[FromQuery] string? search = null, // 搜尋關鍵字
[FromQuery] bool favoritesOnly = false // 僅收藏詞卡
)
```
## 4. 服務層架構
### 4.1 依賴注入配置 (ServiceCollectionExtensions.cs)
```csharp
public static class ServiceCollectionExtensions
{
// 資料庫服務配置
public static IServiceCollection AddDatabaseServices(...)
// Repository 服務配置
public static IServiceCollection AddRepositoryServices(...)
// 快取服務配置
public static IServiceCollection AddCachingServices(...)
// AI 服務配置
public static IServiceCollection AddAIServices(...)
// 業務服務配置
public static IServiceCollection AddBusinessServices(...)
// 認證服務配置
public static IServiceCollection AddAuthenticationServices(...)
// CORS 政策配置
public static IServiceCollection AddCorsServices(...)
}
```
### 4.2 業務服務層
#### 已實現的服務
```csharp
// 認證服務
services.AddScoped<IAuthService, AuthService>();
// 使用量追蹤
services.AddScoped<IUsageTrackingService, UsageTrackingService>();
// Azure 語音服務
services.AddScoped<IAzureSpeechService, AzureSpeechService>();
// 音頻快取
services.AddScoped<IAudioCacheService, AudioCacheService>();
// AI 提供商管理
services.AddScoped<IAIProviderManager, AIProviderManager>();
services.AddScoped<IAIProvider, GeminiAIProvider>();
```
## 5. 資料存取層
### 5.1 DbContext 配置
```csharp
public class DramaLingDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Flashcard> Flashcards { get; set; }
public DbSet<CardSet> CardSets { get; set; }
public DbSet<StudyRecord> StudyRecords { get; set; }
public DbSet<ErrorReport> ErrorReports { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 詞卡配置
modelBuilder.Entity<Flashcard>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Word).IsRequired().HasMaxLength(255);
entity.Property(e => e.Translation).IsRequired();
entity.Property(e => e.Definition).IsRequired();
// 關聯配置
entity.HasOne(f => f.User)
.WithMany(u => u.Flashcards)
.HasForeignKey(f => f.UserId);
entity.HasOne(f => f.CardSet)
.WithMany(cs => cs.Flashcards)
.HasForeignKey(f => f.CardSetId)
.IsRequired(false); // CardSetId 可為空
});
}
}
```
### 5.2 資料庫連接配置
#### 開發環境
```csharp
// 環境變數或配置檔案
var connectionString = Environment.GetEnvironmentVariable("DRAMALING_DB_CONNECTION")
?? configuration.GetConnectionString("DefaultConnection")
?? "Data Source=dramaling_test.db";
services.AddDbContext<DramaLingDbContext>(options =>
options.UseSqlite(connectionString));
```
#### 記憶體資料庫 (測試用)
```csharp
var useInMemoryDb = Environment.GetEnvironmentVariable("USE_INMEMORY_DB") == "true";
if (useInMemoryDb)
{
services.AddDbContext<DramaLingDbContext>(options =>
options.UseSqlite("Data Source=:memory:"));
}
```
## 6. 認證與授權
### 6.1 JWT 配置
```csharp
public static IServiceCollection AddAuthenticationServices(...)
{
var supabaseUrl = Environment.GetEnvironmentVariable("DRAMALING_SUPABASE_URL")
?? "https://localhost";
var jwtSecret = Environment.GetEnvironmentVariable("DRAMALING_SUPABASE_JWT_SECRET")
?? "dev-secret-minimum-32-characters-long-for-jwt-signing-in-development-mode-only";
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = supabaseUrl,
ValidAudience = "authenticated",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret))
};
});
}
```
### 6.2 開發階段認證處理
```csharp
// 暫時移除認證要求,使用固定測試用戶
private Guid GetUserId()
{
return Guid.Parse("00000000-0000-0000-0000-000000000001");
// 生產環境將啟用:
// var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// if (Guid.TryParse(userIdString, out var userId))
// return userId;
// throw new UnauthorizedAccessException("Invalid user ID in token");
}
```
## 7. CORS 設定
### 7.1 跨域政策配置
```csharp
services.AddCors(options =>
{
options.AddPolicy("AllowFrontend", policy =>
{
policy.WithOrigins("http://localhost:3000", "http://localhost:3001", "http://localhost:3002")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromMinutes(5));
});
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
```
## 8. AI 服務整合
### 8.1 AI 提供商架構
```csharp
// AI 提供商介面
public interface IAIProvider
{
Task<SentenceAnalysisResult> AnalyzeSentenceAsync(string inputText, AnalysisOptions options);
}
// Gemini AI 實作
public class GeminiAIProvider : IAIProvider
{
private readonly HttpClient _httpClient;
private readonly GeminiOptions _options;
public async Task<SentenceAnalysisResult> AnalyzeSentenceAsync(...)
{
// 調用 Google Gemini API
// 處理回應和錯誤
// 返回標準化結果
}
}
```
### 8.2 AI 服務配置
```csharp
// 強型別配置
services.Configure<GeminiOptions>(configuration.GetSection(GeminiOptions.SectionName));
services.AddSingleton<IValidateOptions<GeminiOptions>, GeminiOptionsValidator>();
// AI 服務註冊
services.AddHttpClient<GeminiAIProvider>();
services.AddScoped<IAIProvider, GeminiAIProvider>();
services.AddScoped<IAIProviderManager, AIProviderManager>();
```
## 9. 錯誤處理架構
### 9.1 全域異常處理
```csharp
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
if (errorFeature != null)
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError(errorFeature.Error, "Unhandled exception occurred");
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var response = new
{
success = false,
error = "Internal server error",
timestamp = DateTime.UtcNow
};
await context.Response.WriteAsync(JsonSerializer.Serialize(response));
}
});
});
```
### 9.2 控制器級錯誤處理
```csharp
try
{
var result = await flashcardsService.CreateFlashcard(data);
return Ok(new { success = true, data = result });
}
catch (ValidationException ex)
{
return BadRequest(new { success = false, error = ex.Message });
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, "Database error during flashcard creation");
return StatusCode(500, new { success = false, error = "Database operation failed" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error during flashcard creation");
return StatusCode(500, new { success = false, error = "Internal server error" });
}
```
## 10. 開發與部署
### 10.1 開發環境設定
#### 啟動開發伺服器
```bash
cd backend
dotnet run --project DramaLing.Api
# 伺服器運行於: http://localhost:5008
# Swagger UI: http://localhost:5008/swagger
```
#### 環境變數設定
```bash
export DRAMALING_DB_CONNECTION="Data Source=dramaling_test.db"
export DRAMALING_SUPABASE_URL="https://localhost"
export DRAMALING_SUPABASE_JWT_SECRET="dev-secret-minimum-32-characters-long-for-jwt-signing-in-development-mode-only"
export USE_INMEMORY_DB="false"
```
### 10.2 資料庫管理
#### Entity Framework 遷移
```bash
# 新增遷移
dotnet ef migrations add MigrationName
# 更新資料庫
dotnet ef database update
# 查看遷移狀態
dotnet ef migrations list
```
#### 測試資料初始化
```csharp
// 自動創建測試用戶
var testUser = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (testUser == null)
{
testUser = new User
{
Id = userId,
Email = "test@dramaling.com",
Name = "Test User",
CreatedAt = DateTime.UtcNow
};
_context.Users.Add(testUser);
await _context.SaveChangesAsync();
}
```
## 11. 效能優化
### 11.1 查詢優化
```csharp
// 使用 AsNoTracking 提升查詢效能
var flashcards = await _context.Flashcards
.AsNoTracking()
.Where(f => f.UserId == userId)
.OrderByDescending(f => f.CreatedAt)
.ToListAsync();
// 避免 N+1 查詢問題
var flashcardsWithDetails = await _context.Flashcards
.Include(f => f.StudyRecords)
.Include(f => f.CardSet)
.Where(f => f.UserId == userId)
.ToListAsync();
```
### 11.2 快取策略
```csharp
// 記憶體快取服務
services.AddMemoryCache();
services.AddScoped<ICacheService, HybridCacheService>();
// 快取使用範例
var cacheKey = $"flashcards:user:{userId}";
var cachedCards = await _cacheService.GetAsync<List<Flashcard>>(cacheKey);
if (cachedCards == null)
{
cachedCards = await LoadFlashcardsFromDatabase(userId);
await _cacheService.SetAsync(cacheKey, cachedCards, TimeSpan.FromMinutes(30));
}
```
## 12. 安全性措施
### 12.1 輸入驗證
```csharp
// 模型驗證特性
[Required, MaxLength(255)]
public string Word { get; set; }
// 控制器層驗證
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
```
### 12.2 SQL 注入防護
```csharp
// Entity Framework 自動參數化查詢
var flashcards = _context.Flashcards
.Where(f => f.Word.Contains(searchTerm)) // 自動參數化
.ToList();
```
### 12.3 XSS 防護
```csharp
// 自動 HTML 編碼
public string Definition { get; set; } // EF Core 自動處理
```
## 13. 監控與日誌
### 13.1 結構化日誌
```csharp
_logger.LogInformation("Creating flashcard for user {UserId}, word: {Word}",
userId, request.Word);
_logger.LogError(ex, "Failed to create flashcard for user {UserId}", userId);
```
### 13.2 健康檢查
```csharp
services.AddHealthChecks()
.AddDbContextCheck<DramaLingDbContext>();
app.MapHealthChecks("/health");
```
---
**文檔版本**: v1.0
**建立日期**: 2025-09-24
**維護負責**: 後端開發團隊
**下次審核**: 架構變更時
> 📋 相關文檔:
> - [系統架構總覽](./system-architecture.md)
> - [前端架構詳細說明](./frontend-architecture.md)
> - [詞卡 API 規格](./flashcard-api-specification.md)

View File

@ -0,0 +1,579 @@
# DramaLing API 規格總覽
## 1. API 架構概覽
### 1.1 基礎資訊
- **基礎 URL**: `http://localhost:5008/api` (開發環境)
- **協議**: HTTP/HTTPS
- **資料格式**: JSON
- **認證方式**: JWT Bearer Token
- **CORS**: 允許 localhost:3000-3002
### 1.2 標準回應格式
#### 成功回應格式
```json
{
"success": true,
"data": {
// 實際資料內容
},
"message": "操作成功" // 可選
}
```
#### 錯誤回應格式
```json
{
"success": false,
"error": "錯誤描述",
"details": "詳細錯誤信息", // 可選
"timestamp": "2025-09-24T10:30:00Z" // 可選
}
```
## 2. 詞卡管理 API
### 2.1 API 端點清單
| 方法 | 端點 | 描述 | 認證 |
|------|------|------|------|
| GET | `/api/flashcards` | 取得詞卡列表 | ✅ |
| GET | `/api/flashcards/{id}` | 取得單一詞卡 | ✅ |
| POST | `/api/flashcards` | 創建新詞卡 | ✅ |
| PUT | `/api/flashcards/{id}` | 更新詞卡 | ✅ |
| DELETE | `/api/flashcards/{id}` | 刪除詞卡 | ✅ |
| POST | `/api/flashcards/{id}/favorite` | 切換收藏狀態 | ✅ |
### 2.2 詳細 API 規格
#### GET /api/flashcards
**描述**: 取得用戶的詞卡列表,支援搜尋和篩選
**查詢參數**:
```typescript
interface GetFlashcardsQuery {
search?: string; // 搜尋關鍵字 (詞彙、翻譯、定義)
favoritesOnly?: boolean; // 僅顯示收藏詞卡 (預設: false)
}
```
**回應範例**:
```json
{
"success": true,
"data": {
"flashcards": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"word": "sophisticated",
"translation": "精密的",
"definition": "Highly developed or complex",
"partOfSpeech": "adjective",
"pronunciation": "/səˈfɪstɪkeɪtɪd/",
"example": "A sophisticated system",
"exampleTranslation": "一個精密的系統",
"masteryLevel": 75,
"timesReviewed": 12,
"isFavorite": true,
"nextReviewDate": "2025-09-25T00:00:00Z",
"difficultyLevel": "C1",
"createdAt": "2025-09-20T08:30:00Z",
"updatedAt": "2025-09-24T10:15:00Z"
}
],
"count": 1
}
}
```
#### POST /api/flashcards
**描述**: 創建新的詞卡
**請求體**:
```json
{
"word": "sophisticated",
"translation": "精密的",
"definition": "Highly developed or complex",
"pronunciation": "/səˈfɪstɪkeɪtɪd/",
"partOfSpeech": "adjective",
"example": "A sophisticated system",
"exampleTranslation": "一個精密的系統"
}
```
**回應範例**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"word": "sophisticated",
// ... 完整詞卡資料
"createdAt": "2025-09-24T10:30:00Z"
},
"message": "詞卡創建成功"
}
```
#### PUT /api/flashcards/{id}
**描述**: 更新現有詞卡
**路徑參數**:
- `id`: 詞卡唯一識別碼 (GUID)
**請求體**: 與 POST 相同格式
**回應範例**:
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
// ... 更新後的詞卡資料
"updatedAt": "2025-09-24T10:35:00Z"
},
"message": "詞卡更新成功"
}
```
#### DELETE /api/flashcards/{id}
**描述**: 刪除詞卡 (軟刪除,設定 IsArchived = true)
**路徑參數**:
- `id`: 詞卡唯一識別碼 (GUID)
**回應範例**:
```json
{
"success": true,
"message": "詞卡已刪除"
}
```
#### POST /api/flashcards/{id}/favorite
**描述**: 切換詞卡的收藏狀態
**路徑參數**:
- `id`: 詞卡唯一識別碼 (GUID)
**回應範例**:
```json
{
"success": true,
"data": {
"isFavorite": true
},
"message": "已加入收藏"
}
```
## 3. AI 分析 API
### 3.1 API 端點
| 方法 | 端點 | 描述 | 認證 |
|------|------|------|------|
| POST | `/api/ai/analyze-sentence` | AI 句子分析 | ✅ |
### 3.2 句子分析 API
#### POST /api/ai/analyze-sentence
**描述**: 使用 AI 分析英語句子,提供詞彙分析、語法檢查、翻譯等功能
**請求體**:
```json
{
"inputText": "The sophisticated algorithm processes data efficiently.",
"analysisMode": "full",
"options": {
"includeGrammarCheck": true,
"includeVocabularyAnalysis": true,
"includeTranslation": true,
"includeIdiomDetection": true,
"includeExamples": true
}
}
```
**回應範例**:
```json
{
"success": true,
"data": {
"originalText": "The sophisticated algorithm processes data efficiently.",
"sentenceMeaning": "這個精密的算法高效地處理資料。",
"grammarCorrection": {
"hasErrors": false,
"correctedText": null,
"corrections": [],
"confidenceScore": 0.95
},
"vocabularyAnalysis": {
"sophisticated": {
"word": "sophisticated",
"translation": "精密的",
"definition": "Highly developed or complex",
"partOfSpeech": "adjective",
"pronunciation": "/səˈfɪstɪkeɪtɪd/",
"difficultyLevel": "C1",
"frequency": "high",
"cefrLevel": "C1",
"synonyms": ["advanced", "complex", "refined"]
},
"algorithm": {
"word": "algorithm",
"translation": "算法",
"definition": "A set of rules for solving problems",
"partOfSpeech": "noun",
"pronunciation": "/ˈælɡərɪðəm/",
"difficultyLevel": "B2",
"frequency": "medium",
"cefrLevel": "B2"
}
},
"idioms": [
{
"idiom": "processes data",
"translation": "處理資料",
"definition": "To handle and analyze information",
"difficultyLevel": "B1",
"frequency": "high",
"cefrLevel": "B1"
}
]
},
"processingTime": "2.34s"
}
```
## 4. 認證 API
### 4.1 API 端點
| 方法 | 端點 | 描述 | 認證 |
|------|------|------|------|
| POST | `/api/auth/login` | 用戶登入 | ❌ |
| POST | `/api/auth/register` | 用戶註冊 | ❌ |
| POST | `/api/auth/refresh` | 更新 Token | ✅ |
| POST | `/api/auth/logout` | 用戶登出 | ✅ |
### 4.2 認證流程
#### 開發階段認證
```csharp
// 目前使用固定測試用戶 ID
private Guid GetUserId()
{
return Guid.Parse("00000000-0000-0000-0000-000000000001");
}
// 控制器暫時設定為 [AllowAnonymous]
[AllowAnonymous]
public class FlashcardsController : ControllerBase
```
#### 生產環境認證 (未來啟用)
```csharp
// JWT Token 解析
private Guid GetUserId()
{
var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (Guid.TryParse(userIdString, out var userId))
return userId;
throw new UnauthorizedAccessException("Invalid user ID in token");
}
```
## 5. 錯誤代碼標準
### 5.1 HTTP 狀態碼使用
| 狀態碼 | 意義 | 使用場景 |
|--------|------|----------|
| 200 | OK | 請求成功 |
| 201 | Created | 資源創建成功 |
| 400 | Bad Request | 請求參數錯誤 |
| 401 | Unauthorized | 認證失敗 |
| 403 | Forbidden | 權限不足 |
| 404 | Not Found | 資源不存在 |
| 409 | Conflict | 資源衝突 (如重複創建) |
| 500 | Internal Server Error | 伺服器內部錯誤 |
### 5.2 自定義錯誤碼
| 錯誤碼 | 描述 | HTTP 狀態 |
|--------|------|-----------|
| `FLASHCARD_NOT_FOUND` | 詞卡不存在 | 404 |
| `FLASHCARD_ALREADY_EXISTS` | 詞卡已存在 | 409 |
| `INVALID_CEFR_LEVEL` | 無效的 CEFR 等級 | 400 |
| `USER_NOT_FOUND` | 用戶不存在 | 404 |
| `DATABASE_ERROR` | 資料庫操作失敗 | 500 |
| `AI_SERVICE_UNAVAILABLE` | AI 服務不可用 | 503 |
## 6. 請求/回應範例
### 6.1 詞卡 CRUD 完整範例
#### 創建詞卡
```bash
curl -X POST http://localhost:5008/api/flashcards \
-H "Content-Type: application/json" \
-d '{
"word": "elaborate",
"translation": "詳細說明",
"definition": "To explain in detail",
"pronunciation": "/ɪˈlæbərət/",
"partOfSpeech": "verb",
"example": "Please elaborate on your idea",
"exampleTranslation": "請詳細說明你的想法"
}'
```
#### 更新詞卡
```bash
curl -X PUT http://localhost:5008/api/flashcards/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-d '{
"word": "elaborate",
"translation": "詳細闡述",
"definition": "To explain something in greater detail",
"pronunciation": "/ɪˈlæbərət/",
"partOfSpeech": "verb",
"example": "Could you elaborate on that point?",
"exampleTranslation": "你能詳細闡述那個觀點嗎?"
}'
```
#### 查詢詞卡 (帶搜尋)
```bash
curl "http://localhost:5008/api/flashcards?search=elaborate&favoritesOnly=false"
```
#### 切換收藏
```bash
curl -X POST http://localhost:5008/api/flashcards/550e8400-e29b-41d4-a716-446655440000/favorite
```
### 6.2 AI 分析範例
#### 句子分析請求
```bash
curl -X POST http://localhost:5008/api/ai/analyze-sentence \
-H "Content-Type: application/json" \
-d '{
"inputText": "I need to elaborate on this concept",
"analysisMode": "full",
"options": {
"includeGrammarCheck": true,
"includeVocabularyAnalysis": true,
"includeTranslation": true,
"includeIdiomDetection": true,
"includeExamples": true
}
}'
```
## 7. 資料驗證規則
### 7.1 詞卡資料驗證
#### 必填欄位
```csharp
[Required]
[MaxLength(255)]
public string Word { get; set; }
[Required]
public string Translation { get; set; }
[Required]
public string Definition { get; set; }
[Required]
public string Example { get; set; }
```
#### 選填欄位約束
```csharp
[MaxLength(50)]
public string? PartOfSpeech { get; set; }
[MaxLength(255)]
public string? Pronunciation { get; set; }
[MaxLength(10)]
public string? DifficultyLevel { get; set; } // A1, A2, B1, B2, C1, C2
```
#### 數值範圍驗證
```csharp
[Range(0, 100)]
public int MasteryLevel { get; set; } = 0;
[Range(0, int.MaxValue)]
public int TimesReviewed { get; set; } = 0;
```
### 7.2 前端驗證規則
#### TypeScript 型別約束
```typescript
interface CreateFlashcardRequest {
word: string; // 1-255 字元
translation: string; // 必填
definition: string; // 必填
pronunciation: string; // 選填,建議 IPA 格式
partOfSpeech: string; // 選填,預設 'noun'
example: string; // 必填
exampleTranslation?: string; // 選填
}
```
## 8. 快取策略
### 8.1 伺服器端快取
#### 記憶體快取
```csharp
// 常用詞卡快取 30 分鐘
var cacheKey = $"flashcards:user:{userId}";
await _cacheService.SetAsync(cacheKey, flashcards, TimeSpan.FromMinutes(30));
```
#### 查詢結果快取
```csharp
// 搜尋結果快取 10 分鐘
var searchCacheKey = $"search:{userId}:{searchTerm}:{favoritesOnly}";
await _cacheService.SetAsync(searchCacheKey, results, TimeSpan.FromMinutes(10));
```
### 8.2 客戶端快取
#### API 服務層快取
```typescript
// 簡單的記憶體快取 (未來可改用 SWR 或 React Query)
class FlashcardsService {
private cache = new Map<string, any>();
async getFlashcards(search?: string, favoritesOnly: boolean = false) {
const cacheKey = `flashcards:${search || 'all'}:${favoritesOnly}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const result = await this.makeRequest(endpoint);
this.cache.set(cacheKey, result);
// 5 分鐘後清除快取
setTimeout(() => this.cache.delete(cacheKey), 5 * 60 * 1000);
return result;
}
}
```
## 9. 速率限制
### 9.1 API 速率限制 (未來實作)
| 端點類型 | 限制 | 時間窗口 |
|----------|------|----------|
| 詞卡 CRUD | 100 requests | 每分鐘 |
| AI 分析 | 10 requests | 每分鐘 |
| 搜尋 | 200 requests | 每分鐘 |
### 9.2 使用量追蹤
#### AI API 使用量
```csharp
// 記錄 AI API 使用量
public class UsageTrackingService
{
public async Task RecordApiUsage(Guid userId, string apiType, decimal cost)
{
var usage = new ApiUsage
{
UserId = userId,
ApiType = apiType,
Cost = cost,
Timestamp = DateTime.UtcNow
};
_context.ApiUsages.Add(usage);
await _context.SaveChangesAsync();
}
}
```
## 10. 開發工具
### 10.1 API 文檔
#### Swagger 配置
```csharp
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "DramaLing API", Version = "v1" });
// JWT 認證配置
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
});
// 存取位置: http://localhost:5008/swagger
```
### 10.2 API 測試
#### 使用 curl 測試
```bash
# 設定基礎 URL
export API_BASE="http://localhost:5008/api"
# 測試詞卡列表
curl "$API_BASE/flashcards"
# 測試詞卡創建
curl -X POST "$API_BASE/flashcards" \
-H "Content-Type: application/json" \
-d @test-flashcard.json
```
#### 使用 Postman Collection (未來)
```json
{
"info": {
"name": "DramaLing API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Flashcards",
"item": [
// 詞卡相關 API 測試
]
}
]
}
```
---
**文檔版本**: v1.0
**建立日期**: 2025-09-24
**維護負責**: API 開發團隊
**更新頻率**: API 變更時即時更新
> 📋 相關文檔:
> - [系統架構總覽](./system-architecture.md)
> - [後端架構詳細說明](./backend-architecture.md)
> - [前端架構詳細說明](./frontend-architecture.md)

View File

@ -0,0 +1,693 @@
# DramaLing 前端架構詳細說明
## 1. 技術棧概覽
### 1.1 核心技術
- **框架**: Next.js 15 (App Router)
- **語言**: TypeScript 5.x
- **樣式框架**: Tailwind CSS 3.x
- **UI 組件**: React 18.x + 自定義組件
- **狀態管理**: React useState/useEffect hooks
- **API 通信**: Fetch API + 自定義 Service 層
### 1.2 開發工具
- **套件管理**: npm
- **建置工具**: Next.js 內建 (Webpack + SWC)
- **型別檢查**: TypeScript Compiler
- **代碼格式化**: Prettier (如果配置)
- **代碼檢查**: ESLint (如果配置)
## 2. 專案結構
### 2.1 目錄架構
```
frontend/
├── app/ # Next.js App Router 頁面
│ ├── flashcards/ # 詞卡管理頁面
│ │ ├── page.tsx # 詞卡列表頁面
│ │ └── [id]/ # 動態詞卡詳細頁面
│ │ └── page.tsx
│ ├── generate/ # AI 生成詞卡頁面
│ │ └── page.tsx
│ ├── settings/ # 設定頁面
│ │ └── page.tsx
│ ├── layout.tsx # 根佈局
│ ├── page.tsx # 首頁
│ └── globals.css # 全域樣式
├── components/ # 可重用組件
│ ├── ClickableTextV2.tsx # 可點擊文字組件
│ ├── FlashcardForm.tsx # 詞卡表單組件
│ ├── Navigation.tsx # 導航組件
│ ├── ProtectedRoute.tsx # 路由保護組件
│ └── AudioPlayer.tsx # 音頻播放組件
├── lib/ # 工具函數和服務
│ ├── services/ # API 服務層
│ │ ├── flashcards.ts # 詞卡 API 服務
│ │ └── auth.ts # 認證 API 服務
│ └── utils/ # 工具函數
├── public/ # 靜態資源
│ └── images/ # 圖片資源
├── package.json # 專案配置
├── tailwind.config.js # Tailwind 配置
├── tsconfig.json # TypeScript 配置
└── next.config.js # Next.js 配置
```
## 3. 頁面架構設計
### 3.1 詞卡管理頁面 (app/flashcards/page.tsx)
#### 組件層級結構
```
FlashcardsPage (Protected Route Wrapper)
└── FlashcardsContent (Main Logic Component)
├── Navigation (Top Navigation Bar)
├── Page Header (Title + Action Buttons)
├── Tab System (All Cards / Favorites)
├── Search & Filter Section
│ ├── Main Search Input
│ ├── Advanced Filters (Collapsible)
│ └── Quick Filter Buttons
├── Flashcard List Display
│ └── Flashcard Card Components (Repeated)
└── FlashcardForm Modal (When Editing)
```
#### 狀態管理架構
```typescript
// 主要狀態
const [activeTab, setActiveTab] = useState<'all-cards' | 'favorites'>('all-cards')
const [searchTerm, setSearchTerm] = useState<string>('')
const [searchFilters, setSearchFilters] = useState<SearchFilters>({
cefrLevel: '',
partOfSpeech: '',
masteryLevel: '',
onlyFavorites: false
})
// 資料狀態
const [flashcards, setFlashcards] = useState<Flashcard[]>([])
const [loading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<string | null>(null)
// 表單狀態
const [showForm, setShowForm] = useState<boolean>(false)
const [editingCard, setEditingCard] = useState<Flashcard | null>(null)
```
### 3.2 AI 分析頁面 (app/generate/page.tsx)
#### 組件結構
```
GeneratePage (Protected Route Wrapper)
└── GenerateContent (Main Logic Component)
├── Navigation
├── Input Section (Conditional Rendering)
│ ├── Text Input Area
│ ├── Character Counter
│ └── Analysis Button
└── Analysis Results Section (Conditional Rendering)
├── Star Explanation Banner
├── Grammar Correction Panel (If Needed)
├── Main Sentence Display
│ ├── Vocabulary Statistics Cards
│ ├── ClickableTextV2 Component
│ ├── Translation Section
│ └── Idioms Display Section
└── Action Buttons
```
#### 分析結果資料流
```typescript
// AI 分析請求
handleAnalyzeSentence()
→ fetch('/api/ai/analyze-sentence')
→ setSentenceAnalysis(apiData)
→ setShowAnalysisView(true)
// 詞卡保存流程
handleSaveWord()
→ flashcardsService.createFlashcard()
→ alert(success/failure message)
```
## 4. 組件設計模式
### 4.1 可重用組件設計
#### ClickableTextV2 組件
```typescript
interface ClickableTextProps {
text: string; // 顯示文字
analysis?: Record<string, WordAnalysis>; // 詞彙分析資料
onWordClick?: (word: string, analysis: WordAnalysis) => void;
onSaveWord?: (word: string, analysis: WordAnalysis) => Promise<{success: boolean}>;
remainingUsage?: number; // 剩餘使用次數
showIdiomsInline?: boolean; // 是否內嵌顯示慣用語
}
// 設計特色:
// - 詞彙點擊互動
// - CEFR 等級顏色編碼
// - 星星標記 (高頻詞彙)
// - 彈出式詞彙詳情
// - Portal 渲染優化
```
#### FlashcardForm 組件
```typescript
interface FlashcardFormProps {
initialData?: Partial<Flashcard>; // 編輯時的初始資料
isEdit?: boolean; // 是否為編輯模式
onSuccess: () => void; // 成功回調
onCancel: () => void; // 取消回調
}
// 表單欄位:
// - word (必填)
// - translation (必填)
// - definition (必填)
// - pronunciation (選填)
// - partOfSpeech (選填,下拉選單)
// - example (必填)
// - exampleTranslation (選填)
```
### 4.2 路由保護模式
#### ProtectedRoute 組件
```typescript
// 用途:保護需要認證的頁面
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
// 檢查認證狀態
// 未登入時導向登入頁面
// 已登入時顯示子組件
return (
<div>
{/* 認證檢查邏輯 */}
{children}
</div>
)
}
// 使用方式:
<ProtectedRoute>
<FlashcardsContent />
</ProtectedRoute>
```
## 5. API 服務層設計
### 5.1 服務類別架構
#### FlashcardsService 類別
```typescript
class FlashcardsService {
private readonly baseURL = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5008'}/api`;
// 統一的請求處理方法
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T>
// CRUD 操作方法
async getFlashcards(search?: string, favoritesOnly?: boolean): Promise<ApiResponse<{flashcards: Flashcard[], count: number}>>
async getFlashcard(id: string): Promise<ApiResponse<Flashcard>>
async createFlashcard(data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>>
async updateFlashcard(id: string, data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>>
async deleteFlashcard(id: string): Promise<ApiResponse<void>>
async toggleFavorite(id: string): Promise<ApiResponse<void>>
}
// 單例模式匯出
export const flashcardsService = new FlashcardsService();
```
### 5.2 型別定義標準化
#### 核心型別定義
```typescript
// 詞卡介面定義
export interface Flashcard {
id: string;
word: string;
translation: string;
definition: string;
partOfSpeech: string;
pronunciation: string;
example: string;
exampleTranslation?: string;
masteryLevel: number;
timesReviewed: number;
isFavorite: boolean;
nextReviewDate: string;
difficultyLevel: string;
createdAt: string;
updatedAt?: string;
}
// API 回應格式
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
// 創建請求格式
export interface CreateFlashcardRequest {
word: string;
translation: string;
definition: string;
pronunciation: string;
partOfSpeech: string;
example: string;
exampleTranslation?: string;
}
```
## 6. 樣式系統架構
### 6.1 Tailwind CSS 配置
#### 主要設計系統
```javascript
// tailwind.config.js
module.exports = {
content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#3B82F6', // 主色調藍色
hover: '#2563EB' // 懸停狀態
}
}
}
}
}
```
#### CEFR 等級顏色系統
```typescript
const getCEFRColor = (level: string) => {
switch (level) {
case 'A1': return 'bg-green-100 text-green-700 border-green-200'
case 'A2': return 'bg-blue-100 text-blue-700 border-blue-200'
case 'B1': return 'bg-yellow-100 text-yellow-700 border-yellow-200'
case 'B2': return 'bg-orange-100 text-orange-700 border-orange-200'
case 'C1': return 'bg-red-100 text-red-700 border-red-200'
case 'C2': return 'bg-purple-100 text-purple-700 border-purple-200'
default: return 'bg-gray-100 text-gray-700 border-gray-200'
}
}
```
### 6.2 響應式設計模式
#### 斷點策略
```css
/* 手機版 */
@media (max-width: 767px) {
.flashcard-grid { grid-template-columns: 1fr; }
.search-filters { flex-direction: column; }
}
/* 平板版 */
@media (min-width: 768px) and (max-width: 1023px) {
.flashcard-grid { grid-template-columns: repeat(2, 1fr); }
}
/* 桌面版 */
@media (min-width: 1024px) {
.flashcard-grid { grid-template-columns: repeat(3, 1fr); }
}
```
## 7. 效能優化策略
### 7.1 React 效能優化
#### useMemo 和 useCallback 使用
```typescript
// 快取詞彙統計計算
const vocabularyStats = useMemo(() => {
if (!sentenceAnalysis?.vocabularyAnalysis) return defaultStats;
// 複雜計算邏輯
return calculateStats(sentenceAnalysis.vocabularyAnalysis);
}, [sentenceAnalysis])
// 快取事件處理函數
const handleWordClick = useCallback(async (word: string, event: React.MouseEvent) => {
// 事件處理邏輯
}, [findWordAnalysis, onWordClick, calculatePopupPosition])
```
#### 組件懶載入
```typescript
// 動態導入大型組件
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <div>載入中...</div>,
ssr: false
})
```
### 7.2 資料載入優化
#### 條件式資料載入
```typescript
useEffect(() => {
// 只在需要時載入資料
if (activeTab === 'favorites') {
loadFavoriteFlashcards();
} else {
loadAllFlashcards();
}
}, [activeTab])
```
#### 錯誤邊界處理
```typescript
// API 服務層錯誤處理
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
try {
const response = await fetch(`${this.baseURL}${endpoint}`, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Network error' }));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
return response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
```
## 8. 狀態管理模式
### 8.1 本地狀態管理
#### 頁面級狀態
```typescript
// 每個頁面管理自己的狀態
function FlashcardsContent() {
// UI 狀態
const [activeTab, setActiveTab] = useState('all-cards')
const [showForm, setShowForm] = useState(false)
// 資料狀態
const [flashcards, setFlashcards] = useState<Flashcard[]>([])
const [loading, setLoading] = useState(true)
// 搜尋狀態
const [searchTerm, setSearchTerm] = useState('')
const [searchFilters, setSearchFilters] = useState(defaultFilters)
}
```
#### 跨組件狀態同步
```typescript
// 通過 props 和 callback 實現父子組件通信
<FlashcardForm
initialData={editingCard}
isEdit={!!editingCard}
onSuccess={handleFormSuccess} // 成功後重新載入資料
onCancel={() => setShowForm(false)}
/>
```
### 8.2 持久化狀態
#### localStorage 使用
```typescript
// 用戶偏好設定
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2'
// 搜尋歷史 (未來功能)
const searchHistory = JSON.parse(localStorage.getItem('searchHistory') || '[]')
```
## 9. 用戶體驗設計
### 9.1 載入狀態處理
#### 統一載入狀態
```typescript
if (loading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-lg">載入中...</div>
</div>
)
}
```
#### 按鈕載入狀態
```typescript
<button
disabled={loading}
className="disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? '載入中...' : '提交'}
</button>
```
### 9.2 錯誤狀態處理
#### 頁面級錯誤
```typescript
if (error) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-red-600">{error}</div>
</div>
)
}
```
#### 表單錯誤
```typescript
{error && (
<div className="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-lg">
{error}
</div>
)}
```
### 9.3 空狀態設計
#### 友善的空狀態
```typescript
{filteredCards.length === 0 ? (
<div className="text-center py-12">
<p className="text-gray-500 mb-4">沒有找到詞卡</p>
<Link href="/generate" className="bg-primary text-white px-4 py-2 rounded-lg">
創建新詞卡
</Link>
</div>
) : (
// 詞卡列表
)}
```
## 10. 互動設計模式
### 10.1 搜尋互動
#### 即時搜尋
```typescript
// 輸入時即時過濾,無防抖延遲
const filteredCards = allCards.filter(card => {
if (searchTerm) {
const searchLower = searchTerm.toLowerCase()
return card.word?.toLowerCase().includes(searchLower) ||
card.translation?.toLowerCase().includes(searchLower) ||
card.definition?.toLowerCase().includes(searchLower)
}
return true
})
```
#### 搜尋結果高亮
```typescript
const highlightSearchTerm = (text: string, searchTerm: string) => {
if (!searchTerm || !text) return text
const regex = new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi')
const parts = text.split(regex)
return parts.map((part, index) =>
regex.test(part) ? (
<mark key={index} className="bg-yellow-200 text-yellow-900 px-1 rounded">
{part}
</mark>
) : part
)
}
```
### 10.2 彈窗互動設計
#### Portal 渲染模式
```typescript
import { createPortal } from 'react-dom'
const VocabPopup = () => {
if (!selectedWord || !mounted) return null
return createPortal(
<>
{/* 遮罩層 */}
<div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={closePopup} />
{/* 彈窗內容 */}
<div className="fixed z-50 bg-white rounded-xl shadow-lg" style={{...}}>
{/* 詞彙詳細資訊 */}
</div>
</>,
document.body
)
}
```
### 10.3 鍵盤操作支援
#### ESC 鍵清除搜尋
```typescript
<input
onKeyDown={(e) => {
if (e.key === 'Escape') {
setSearchTerm('')
}
}}
/>
```
## 11. 開發與建置
### 11.1 開發環境
#### 開發伺服器啟動
```bash
cd frontend
npm run dev
# 開發伺服器運行於: http://localhost:3000
# Hot Reload 啟用
# Fast Refresh 啟用
```
#### 環境變數配置
```bash
# .env.local
NEXT_PUBLIC_API_URL=http://localhost:5008
```
### 11.2 建置優化
#### Next.js 配置 (next.config.js)
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true, // 啟用 App Router
},
images: {
domains: ['localhost'], // 圖片域名白名單
}
}
module.exports = nextConfig
```
#### TypeScript 配置 (tsconfig.json)
```json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": ["./*"] // 路徑別名
}
}
}
```
## 12. 測試策略
### 12.1 組件測試
```typescript
// 未來實作Jest + React Testing Library
import { render, screen, fireEvent } from '@testing-library/react'
import { FlashcardForm } from './FlashcardForm'
test('should submit form with valid data', async () => {
const onSuccess = jest.fn()
render(<FlashcardForm onSuccess={onSuccess} onCancel={() => {}} />)
// 填寫表單
fireEvent.change(screen.getByLabelText('單字'), { target: { value: 'test' } })
// 提交表單
fireEvent.click(screen.getByText('創建詞卡'))
// 驗證結果
expect(onSuccess).toHaveBeenCalled()
})
```
### 12.2 API 服務測試
```typescript
// Mock fetch 進行單元測試
global.fetch = jest.fn()
test('should create flashcard successfully', async () => {
const mockResponse = { success: true, data: mockFlashcard }
;(fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => mockResponse,
})
const result = await flashcardsService.createFlashcard(mockData)
expect(result.success).toBe(true)
})
```
---
**文檔版本**: v1.0
**建立日期**: 2025-09-24
**維護負責**: 前端開發團隊
**下次審核**: 架構變更時
> 📋 相關文檔:
> - [系統架構總覽](./system-architecture.md)
> - [後端架構詳細說明](./backend-architecture.md)
> - [詞卡 API 規格](./flashcard-api-specification.md)

View File

@ -0,0 +1,185 @@
# DramaLing 系統架構總覽
## 1. 系統架構概要
### 1.1 整體架構圖
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ 前端 (React) │◄──►│ 後端 API │◄──►│ 外部服務 │
│ Next.js 15 │ │ ASP.NET Core │ │ Google AI │
│ TypeScript │ │ C# .NET 8 │ │ Azure Speech │
│ │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ │
│ 資料存儲 │
│ SQLite DB │
│ 本地檔案 │
│ │
└─────────────────┘
```
### 1.2 核心組件
#### 🖥️ **前端層 (Client)**
- **技術棧**: Next.js 15 + TypeScript + Tailwind CSS
- **部署**: http://localhost:3000 (開發環境)
- **主要職責**: 用戶介面、用戶互動、API 呼叫
#### ⚙️ **後端層 (Server)**
- **技術棧**: ASP.NET Core 8.0 + C#
- **部署**: http://localhost:5008 (開發環境)
- **主要職責**: 業務邏輯、API 服務、資料處理
#### 💾 **資料層 (Data)**
- **資料庫**: SQLite + Entity Framework Core
- **檔案位置**: `dramaling_test.db`
- **主要職責**: 資料持久化、關聯管理
#### 🌐 **外部服務層 (External)**
- **AI 服務**: Google Gemini API
- **語音服務**: Azure Speech Service
- **主要職責**: AI 分析、語音合成
## 2. 技術棧詳細說明
### 2.1 前端技術棧
| 技術組件 | 版本 | 用途 | 檔案位置 |
|---------|------|------|----------|
| Next.js | 15.x | React 框架 | `/frontend` |
| TypeScript | 5.x | 型別安全 | `.tsx`, `.ts` 檔案 |
| Tailwind CSS | 3.x | 樣式框架 | `tailwind.config.js` |
| React | 18.x | UI 組件 | `/components` |
### 2.2 後端技術棧
| 技術組件 | 版本 | 用途 | 檔案位置 |
|---------|------|------|----------|
| ASP.NET Core | 8.0 | Web API 框架 | `/backend/DramaLing.Api` |
| Entity Framework | 8.0 | ORM 框架 | `/Data` |
| SQLite | 3.x | 資料庫 | `dramaling_test.db` |
| JWT | - | 身份驗證 | `/Services/AuthService.cs` |
### 2.3 開發工具
| 工具 | 用途 | 配置檔案 |
|------|------|----------|
| npm | 前端套件管理 | `package.json` |
| dotnet | 後端專案管理 | `*.csproj` |
| Git | 版本控制 | `.gitignore` |
## 3. 服務間通信
### 3.1 前後端通信
- **協議**: HTTP/HTTPS
- **格式**: JSON
- **認證**: JWT Token
- **CORS**: 配置允許 localhost:3000-3002
### 3.2 API 基礎規範
- **基礎路徑**: `http://localhost:5008/api`
- **內容類型**: `application/json`
- **錯誤格式**: 標準化錯誤回應
- **成功格式**: `{success: boolean, data?: T, error?: string}`
## 4. 資料流架構
### 4.1 典型請求流程
```
用戶操作 → React Component → API Service → HTTP Request → ASP.NET Controller → Business Service → Entity Framework → SQLite
↓ ↑
Response ← State Update ← API Response ← HTTP Response ← JSON Serialization ← Business Logic ← Database Query ←────────┘
```
### 4.2 錯誤處理流程
```
異常發生 → Exception Handling → Error Response → Frontend Error State → User Feedback
```
## 5. 安全架構
### 5.1 認證機制
- **JWT Token**: 用戶身份驗證
- **Token 來源**: Supabase 相容格式
- **驗證位置**: ASP.NET Core Middleware
### 5.2 資料保護
- **輸入驗證**: 前端 + 後端雙重驗證
- **SQL 注入防護**: Entity Framework 參數化查詢
- **XSS 防護**: React 內建保護機制
## 6. 開發環境
### 6.1 本地開發設定
#### 前端開發伺服器
```bash
cd frontend
npm run dev
# 運行於: http://localhost:3000
```
#### 後端開發伺服器
```bash
cd backend
dotnet run --project DramaLing.Api
# 運行於: http://localhost:5008
```
### 6.2 環境變數
#### 後端環境變數
```bash
DRAMALING_DB_CONNECTION=Data Source=dramaling_test.db
DRAMALING_SUPABASE_URL=https://localhost
DRAMALING_SUPABASE_JWT_SECRET=dev-secret-minimum-32-characters-long-for-jwt-signing-in-development-mode-only
USE_INMEMORY_DB=false
```
#### 前端環境變數
```bash
NEXT_PUBLIC_API_URL=http://localhost:5008
```
## 7. 部署架構
### 7.1 開發環境
- **前端**: npm run dev (Hot Reload)
- **後端**: dotnet run (Hot Reload)
- **資料庫**: 本地 SQLite 檔案
### 7.2 生產環境 (未來)
- **前端**: Vercel / Netlify
- **後端**: Azure App Service / AWS EC2
- **資料庫**: PostgreSQL / Azure SQL
## 8. 監控與維護
### 8.1 日誌系統
- **前端**: Console.log (開發), Sentry (生產)
- **後端**: ILogger, 結構化日誌
- **API**: HTTP 請求/回應日誌
### 8.2 效能監控
- **前端**: Next.js 內建分析
- **後端**: ASP.NET Core 效能計數器
- **資料庫**: EF Core 查詢分析
---
**文檔版本**: v1.0
**建立日期**: 2025-09-24
**維護負責**: 開發團隊
**更新頻率**: 架構變更時即時更新
> 📋 此文檔為系統架構總覽,詳細技術規格請參考:
> - [後端架構詳細說明](./backend-architecture.md)
> - [前端架構詳細說明](./frontend-architecture.md)
> - [詞卡 API 規格](./flashcard-api-specification.md)