16 KiB
16 KiB
前後端分離架構策略
文件版本: 1.0
建立日期: 2025-09-10
更新日期: 2025-09-10
負責人: Drama Ling 開發團隊
📋 概述
Drama Ling 採用前後端分離架構,後端提供統一的API服務,同時支援Web前端和Flutter移動端。這種架構確保了API的一致性,並為未來的擴展奠定了基礎。
🏗 整體架構設計
架構圖
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架構)
技術棧
{
"基礎技術": {
"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整合範例
// 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移動端
技術棧
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整合範例
// 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/*)
[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/*)
[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/*)
[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();
}
}
認證策略
雙重認證系統
// 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
🔄 開發工作流程
前端開發流程
- 本地開發環境
# 啟動前端開發伺服器
cd apps/web-native
npm install
npm run dev # http://localhost:3000
# 啟動後端API服務
cd ../../backend
dotnet run # http://localhost:5000
- API測試和整合
// 開發時的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;
- CORS配置 (後端)
// Program.cs
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder
.WithOrigins(
"http://localhost:3000", // 前端開發環境
"https://app.dramaling.com" // 生產環境
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
app.UseCors();
部署策略
開發環境
前端:
- 開發伺服器: Vite dev server (http://localhost:3000)
- 熱重載: 支援
- API代理: 配置到後端開發服務
後端:
- 開發伺服器: dotnet run (http://localhost:5000)
- 資料庫: 本地PostgreSQL
- 快取: 本地Redis
生產環境
前端:
- 構建: 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. 一致的回應格式
// 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. 版本控制策略
[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 實現,保持向後相容
}
🚀 效能優化
前端優化
// 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');
後端優化
// 快取策略
[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測試
// 測試用例範例
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');
}
});
});
錯誤處理
// 統一錯誤處理
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 開發團隊