dramaling-app/docs/04_technical/01_architecture/frontend-backend-separation...

635 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 前後端分離架構策略
**文件版本**: 1.0
**建立日期**: 2025-09-10
**更新日期**: 2025-09-10
**負責人**: Drama Ling 開發團隊
## 📋 概述
Drama Ling 採用**前後端分離架構**後端提供統一的API服務同時支援Web前端和Flutter移動端。這種架構確保了API的一致性並為未來的擴展奠定了基礎。
## 🏗 整體架構設計
### 架構圖
```mermaid
graph TB
subgraph "客戶端層"
WEB[Web Frontend<br/>Vite + Modern JS<br/>Port: 3000]
APP[Flutter Mobile App<br/>Dart + Flutter]
end
subgraph "API層"
API[統一API服務<br/>.NET Core Web API<br/>Port: 5000]
subgraph "API端點"
WEB_API[/api/v1/web/*<br/>Web專用API]
MOBILE_API[/api/v1/mobile/*<br/>移動端專用API]
COMMON_API[/api/v1/common/*<br/>共用API]
end
end
subgraph "資料層"
DB[(PostgreSQL<br/>主資料庫)]
CACHE[(Redis<br/>快取)]
STORAGE[檔案儲存<br/>本地/雲端]
end
subgraph "外部服務"
AI[AI服務<br/>OpenAI GPT-4o-mini]
PAYMENT[支付服務<br/>Stripe/藍新]
EMAIL[通知服務<br/>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<VocabularyDto> getVocabulary(int id) async {
final response = await _dio.get('$mobileEndpoint/vocabulary/$id');
return VocabularyDto.fromJson(response.data);
}
// 共用API
Future<UserProfile> 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<IActionResult> GetVocabulary(int id)
{
// 返回豐富的資料結構適合Web展示
var vocabulary = await _service.GetDetailedVocabularyAsync(id);
return Ok(new WebApiResponse<DetailedVocabularyDto>
{
Data = vocabulary,
Meta = new
{
RelatedWords = await _service.GetRelatedWordsAsync(id),
LearningAnalytics = await _service.GetAnalyticsAsync(id)
}
});
}
[HttpGet("search")]
public async Task<IActionResult> Search([FromQuery] SearchRequest request)
{
// 支援複雜的搜尋功能
var result = await _service.SearchAsync(request);
return Ok(new WebApiResponse<SearchResult>
{
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<IActionResult> GetVocabulary(int id)
{
// 返回精簡的資料結構,適合移動端
var vocabulary = await _service.GetBasicVocabularyAsync(id);
return Ok(new MobileApiResponse<BasicVocabularyDto>
{
Data = vocabulary,
Success = true,
Timestamp = DateTimeOffset.UtcNow
});
}
[HttpPost("sync")]
public async Task<IActionResult> SyncData([FromBody] SyncRequest request)
{
// 增量同步,減少資料傳輸
var changes = await _service.GetChangesAsync(request.LastSyncTime);
return Ok(new MobileApiResponse<SyncResult>
{
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<IActionResult> GetProfile()
{
// 兩端都需要的基本用戶資料
var profile = await _service.GetUserProfileAsync(User.GetUserId());
return Ok(new ApiResponse<UserProfileDto>
{
Data = profile
});
}
[HttpPost("preferences")]
public async Task<IActionResult> 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<T>
{
public T Data { get; set; }
public object Meta { get; set; }
public Dictionary<string, string> Links { get; set; }
public DateTimeOffset Timestamp { get; set; }
}
// 移動端回應格式
public class MobileApiResponse<T>
{
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<IActionResult> GetVocabulary(int id)
{
var cacheKey = $"vocabulary:{id}";
var cached = await _cache.GetStringAsync(cacheKey);
if (cached != null)
{
return Ok(JsonSerializer.Deserialize<VocabularyDto>(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<IActionResult> GetVocabularies(
[FromQuery] int page = 1,
[FromQuery] int size = 20)
{
var result = await _service.GetVocabulariesAsync(page, size);
return Ok(new PagedResponse<VocabularyDto>
{
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 開發團隊