# 前後端分離架構策略 **文件版本**: 1.0 **建立日期**: 2025-09-10 **更新日期**: 2025-09-10 **負責人**: Drama Ling 開發團隊 ## 📋 概述 Drama Ling 採用**前後端分離架構**,後端提供統一的API服務,同時支援Web前端和Flutter移動端。這種架構確保了API的一致性,並為未來的擴展奠定了基礎。 ## 🏗 整體架構設計 ### 架構圖 ```mermaid graph TB subgraph "客戶端層" WEB[Web Frontend
Vite + Modern JS
Port: 3000] APP[Flutter Mobile App
Dart + Flutter] end subgraph "API層" API[統一API服務
.NET Core Web API
Port: 5000] subgraph "API端點" WEB_API[/api/v1/web/*
Web專用API] MOBILE_API[/api/v1/mobile/*
移動端專用API] COMMON_API[/api/v1/common/*
共用API] end end subgraph "資料層" DB[(PostgreSQL
主資料庫)] CACHE[(Redis
快取)] STORAGE[檔案儲存
本地/雲端] end subgraph "外部服務" AI[AI服務
OpenAI GPT-4o-mini] PAYMENT[支付服務
Stripe/藍新] EMAIL[通知服務
Email/Push] end WEB --> WEB_API APP --> MOBILE_API WEB --> COMMON_API APP --> COMMON_API WEB_API --> API MOBILE_API --> API COMMON_API --> API API --> DB API --> CACHE API --> STORAGE API --> AI API --> PAYMENT API --> EMAIL ``` ## 🎯 前端架構設計 ### Web前端 (現代JavaScript架構) #### 技術棧 ```javascript { "基礎技術": { "HTML": "HTML5 語義化標籤", "CSS": "SCSS/Sass + CSS Grid + Flexbox", "JavaScript": "ES2022+ + ES6 Modules" }, "開發工具": { "構建工具": "Vite 5.x", "包管理": "npm", "代碼品質": "ESLint + Prettier" }, "架構模式": { "組織方式": "模組化架構", "狀態管理": "原生JavaScript類別", "路由": "SPA + History API", "API通信": "Fetch API" } } ``` #### 目錄結構 ``` apps/web/ ├── index.html # 主入口頁面 ├── src/ │ ├── modules/ # 核心模組 │ │ ├── VocabularyApp.js # 詞彙學習應用 │ │ ├── VocabularyState.js # 狀態管理 │ │ └── AuthModule.js # 認證模組 │ ├── components/ # 可重用組件 │ │ ├── BaseComponent.js # 基礎組件類別 │ │ └── AudioManager.js # 音頻管理 │ ├── utils/ # 工具函數 │ │ ├── api.js # API請求封裝 │ │ ├── storage.js # 本地儲存 │ │ └── helpers.js # 輔助函數 │ ├── styles/ # 樣式檔案 │ │ ├── main.scss # 主樣式 │ │ ├── variables.scss # SCSS變數 │ │ └── vocabulary.scss # 功能專用樣式 │ └── main.js # 應用入口 ├── package.json # 依賴配置 └── vite.config.js # Vite配置 ``` #### API整合範例 ```javascript // src/utils/api.js class ApiClient { constructor() { this.baseURL = 'http://localhost:5000/api/v1'; this.webEndpoint = `${this.baseURL}/web`; this.commonEndpoint = `${this.baseURL}/common`; } async request(url, options = {}) { const response = await fetch(url, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.getToken()}`, ...options.headers }, ...options }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } // Web專用API async getVocabulary(id) { return this.request(`${this.webEndpoint}/vocabulary/${id}`); } async searchVocabulary(query) { return this.request(`${this.webEndpoint}/vocabulary/search?q=${query}`); } // 共用API async getUserProfile() { return this.request(`${this.commonEndpoint}/user/profile`); } getToken() { return localStorage.getItem('auth_token'); } } export const apiClient = new ApiClient(); ``` ### Flutter移動端 #### 技術棧 ```yaml framework: "Flutter 3.16+" language: "Dart 3.0+" state_management: "Riverpod" networking: "Dio + Retrofit" local_storage: "Hive" authentication: "JWT Token" api_integration: "RESTful API Client" ``` #### API整合範例 ```dart // lib/services/api_service.dart class ApiService { static const String baseUrl = 'http://localhost:5000/api/v1'; static const String mobileEndpoint = '$baseUrl/mobile'; static const String commonEndpoint = '$baseUrl/common'; final Dio _dio = Dio(); ApiService() { _dio.interceptors.add(AuthInterceptor()); } // 移動端專用API Future getVocabulary(int id) async { final response = await _dio.get('$mobileEndpoint/vocabulary/$id'); return VocabularyDto.fromJson(response.data); } // 共用API Future getUserProfile() async { final response = await _dio.get('$commonEndpoint/user/profile'); return UserProfile.fromJson(response.data); } } ``` ## 🔧 後端API設計 ### API端點策略 #### 1. Web端專用API (`/api/v1/web/*`) ```csharp [ApiController] [Route("api/v1/web/[controller]")] public class VocabularyController : ControllerBase { [HttpGet("{id}")] public async Task GetVocabulary(int id) { // 返回豐富的資料結構,適合Web展示 var vocabulary = await _service.GetDetailedVocabularyAsync(id); return Ok(new WebApiResponse { Data = vocabulary, Meta = new { RelatedWords = await _service.GetRelatedWordsAsync(id), LearningAnalytics = await _service.GetAnalyticsAsync(id) } }); } [HttpGet("search")] public async Task Search([FromQuery] SearchRequest request) { // 支援複雜的搜尋功能 var result = await _service.SearchAsync(request); return Ok(new WebApiResponse { Data = result.Items, Meta = new { Pagination = result.Pagination, Filters = result.AvailableFilters, Aggregations = result.Aggregations } }); } } ``` #### 2. 移動端專用API (`/api/v1/mobile/*`) ```csharp [ApiController] [Route("api/v1/mobile/[controller]")] public class VocabularyController : ControllerBase { [HttpGet("{id}")] public async Task GetVocabulary(int id) { // 返回精簡的資料結構,適合移動端 var vocabulary = await _service.GetBasicVocabularyAsync(id); return Ok(new MobileApiResponse { Data = vocabulary, Success = true, Timestamp = DateTimeOffset.UtcNow }); } [HttpPost("sync")] public async Task SyncData([FromBody] SyncRequest request) { // 增量同步,減少資料傳輸 var changes = await _service.GetChangesAsync(request.LastSyncTime); return Ok(new MobileApiResponse { Data = changes, Success = true }); } } ``` #### 3. 共用API (`/api/v1/common/*`) ```csharp [ApiController] [Route("api/v1/common/[controller]")] public class UserController : ControllerBase { [HttpGet("profile")] public async Task GetProfile() { // 兩端都需要的基本用戶資料 var profile = await _service.GetUserProfileAsync(User.GetUserId()); return Ok(new ApiResponse { Data = profile }); } [HttpPost("preferences")] public async Task UpdatePreferences([FromBody] PreferencesDto preferences) { // 用戶偏好設定,兩端共用 await _service.UpdatePreferencesAsync(User.GetUserId(), preferences); return Ok(); } } ``` ### 認證策略 #### 雙重認證系統 ```csharp // Program.cs builder.Services.AddAuthentication() .AddJwtBearer("JwtBearer", options => { // 移動端JWT認證 options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }) .AddCookie("Cookie", options => { // Web端Cookie認證 options.LoginPath = "/auth/login"; options.LogoutPath = "/auth/logout"; options.AccessDeniedPath = "/auth/forbidden"; options.ExpireTimeSpan = TimeSpan.FromDays(30); options.SlidingExpiration = true; }); // 控制器上的認證配置 [Authorize(AuthenticationSchemes = "JwtBearer")] // 移動端API [Authorize(AuthenticationSchemes = "Cookie")] // Web端API ``` ## 🔄 開發工作流程 ### 前端開發流程 1. **本地開發環境** ```bash # 啟動前端開發伺服器 cd apps/web-native npm install npm run dev # http://localhost:3000 # 啟動後端API服務 cd ../../backend dotnet run # http://localhost:5000 ``` 2. **API測試和整合** ```javascript // 開發時的API配置 const API_CONFIG = { development: 'http://localhost:5000/api/v1', production: 'https://api.dramaling.com/api/v1' }; // 環境檢測 const apiBase = process.env.NODE_ENV === 'production' ? API_CONFIG.production : API_CONFIG.development; ``` 3. **CORS配置 (後端)** ```csharp // Program.cs builder.Services.AddCors(options => { options.AddDefaultPolicy(builder => { builder .WithOrigins( "http://localhost:3000", // 前端開發環境 "https://app.dramaling.com" // 生產環境 ) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); app.UseCors(); ``` ### 部署策略 #### 開發環境 ```yaml 前端: - 開發伺服器: Vite dev server (http://localhost:3000) - 熱重載: 支援 - API代理: 配置到後端開發服務 後端: - 開發伺服器: dotnet run (http://localhost:5000) - 資料庫: 本地PostgreSQL - 快取: 本地Redis ``` #### 生產環境 ```yaml 前端: - 構建: npm run build - 部署: 靜態檔案 + CDN - 域名: https://app.dramaling.com 後端: - 容器化: Docker - 部署: VPS/雲端服務 - 域名: https://api.dramaling.com - 資料庫: PostgreSQL (雲端) - 快取: Redis (雲端) ``` ## 📊 API設計最佳實踐 ### 1. RESTful設計原則 ``` GET /api/v1/web/vocabulary # 獲取詞彙清單 GET /api/v1/web/vocabulary/{id} # 獲取單個詞彙 POST /api/v1/web/vocabulary # 創建新詞彙 PUT /api/v1/web/vocabulary/{id} # 更新詞彙 DELETE /api/v1/web/vocabulary/{id} # 刪除詞彙 GET /api/v1/mobile/vocabulary/sync # 移動端專用同步 POST /api/v1/mobile/learning/progress # 學習進度更新 ``` ### 2. 一致的回應格式 ```csharp // Web端回應格式 public class WebApiResponse { public T Data { get; set; } public object Meta { get; set; } public Dictionary Links { get; set; } public DateTimeOffset Timestamp { get; set; } } // 移動端回應格式 public class MobileApiResponse { public T Data { get; set; } public string Message { get; set; } public bool Success { get; set; } public DateTimeOffset Timestamp { get; set; } } // 錯誤回應格式 public class ErrorResponse { public string Error { get; set; } public string Message { get; set; } public int Code { get; set; } public object Details { get; set; } } ``` ### 3. 版本控制策略 ```csharp [ApiVersion("1.0")] [ApiController] [Route("api/v{version:apiVersion}/web/[controller]")] public class VocabularyController : ControllerBase { // v1.0 實現 } [ApiVersion("2.0")] [ApiController] [Route("api/v{version:apiVersion}/web/[controller]")] public class VocabularyV2Controller : ControllerBase { // v2.0 實現,保持向後相容 } ``` ## 🚀 效能優化 ### 前端優化 ```javascript // API請求快取 class CachedApiClient extends ApiClient { constructor() { super(); this.cache = new Map(); this.cacheTimeout = 5 * 60 * 1000; // 5分鐘 } async cachedRequest(url, options = {}) { const cacheKey = `${url}-${JSON.stringify(options)}`; const cached = this.cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { return cached.data; } const data = await this.request(url, options); this.cache.set(cacheKey, { data, timestamp: Date.now() }); return data; } } // 懶載入模組 const loadVocabularyModule = () => import('./modules/VocabularyApp.js'); const loadAuthModule = () => import('./modules/AuthModule.js'); ``` ### 後端優化 ```csharp // 快取策略 [HttpGet("{id}")] [ResponseCache(Duration = 300)] // 5分鐘快取 public async Task GetVocabulary(int id) { var cacheKey = $"vocabulary:{id}"; var cached = await _cache.GetStringAsync(cacheKey); if (cached != null) { return Ok(JsonSerializer.Deserialize(cached)); } var vocabulary = await _service.GetVocabularyAsync(id); await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(vocabulary), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) }); return Ok(vocabulary); } // 分頁查詢優化 [HttpGet] public async Task GetVocabularies( [FromQuery] int page = 1, [FromQuery] int size = 20) { var result = await _service.GetVocabulariesAsync(page, size); return Ok(new PagedResponse { Data = result.Items, Page = page, Size = size, Total = result.Total, HasNext = result.HasNext, HasPrevious = result.HasPrevious }); } ``` ## 📝 開發指南 ### API測試 ```javascript // 測試用例範例 describe('Vocabulary API', () => { test('should get vocabulary details', async () => { const vocabulary = await apiClient.getVocabulary(1); expect(vocabulary).toHaveProperty('word'); expect(vocabulary).toHaveProperty('definition'); expect(vocabulary).toHaveProperty('phonetic'); }); test('should handle API errors gracefully', async () => { try { await apiClient.getVocabulary(999999); } catch (error) { expect(error.message).toContain('404'); } }); }); ``` ### 錯誤處理 ```javascript // 統一錯誤處理 class ApiClient { async request(url, options = {}) { try { const response = await fetch(url, options); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new ApiError(response.status, errorData.message || 'Request failed'); } return await response.json(); } catch (error) { if (error instanceof ApiError) { throw error; } throw new NetworkError('Network request failed', error); } } } class ApiError extends Error { constructor(status, message) { super(message); this.status = status; this.name = 'ApiError'; } } class NetworkError extends Error { constructor(message, originalError) { super(message); this.originalError = originalError; this.name = 'NetworkError'; } } ``` --- **最後更新**: 2025-09-10 **版本**: 1.0 - 前後端分離架構策略 **維護者**: Drama Ling 開發團隊