887 lines
23 KiB
Markdown
887 lines
23 KiB
Markdown
# 系統整合與部署規格
|
||
|
||
## 📋 **文件資訊**
|
||
|
||
- **文件名稱**: 系統整合與部署規格
|
||
- **版本**: v2.0
|
||
- **建立日期**: 2025-01-25
|
||
- **最後更新**: 2025-01-25
|
||
- **負責團隊**: DramaLing DevOps團隊
|
||
- **適用系統**: AI句子分析功能全棧系統
|
||
|
||
---
|
||
|
||
## 🏗️ **系統架構圖**
|
||
|
||
### **整體架構**
|
||
```
|
||
┌─────────────────┐ HTTP/JSON ┌──────────────────┐ Gemini API ┌─────────────────┐
|
||
│ │ Request │ │ Request │ │
|
||
│ Frontend │ ──────────────► │ Backend API │ ──────────────► │ Google Gemini │
|
||
│ (Next.js) │ │ (.NET Core) │ │ AI Service │
|
||
│ Port 3000 │ ◄────────────── │ Port 5008 │ ◄────────────── │ │
|
||
│ │ Response │ │ Response │ │
|
||
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
||
│ │
|
||
│ │
|
||
▼ ▼
|
||
┌─────────────────┐ ┌──────────────────┐
|
||
│ Local Storage │ │ SQLite Database │
|
||
│ - user_level │ │ - Cache Data │
|
||
│ - auth_token │ │ - Usage Stats │
|
||
└─────────────────┘ └──────────────────┘
|
||
```
|
||
|
||
### **數據流向**
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as 用戶
|
||
participant F as 前端(3000)
|
||
participant B as 後端(5008)
|
||
participant G as Gemini API
|
||
participant D as 資料庫
|
||
|
||
U->>F: 1. 輸入英文句子
|
||
U->>F: 2. 點擊「分析句子」
|
||
F->>F: 3. 驗證輸入(≤300字符)
|
||
F->>F: 4. 讀取userLevel (localStorage)
|
||
F->>B: 5. POST /api/ai/analyze-sentence
|
||
B->>B: 6. 輸入驗證和處理
|
||
B->>G: 7. 調用Gemini API
|
||
G->>B: 8. 返回AI分析結果
|
||
B->>B: 9. 解析和格式化數據
|
||
B->>D: 10. 記錄使用統計 (可選)
|
||
B->>F: 11. 返回結構化分析結果
|
||
F->>F: 12. 計算個人化統計
|
||
F->>F: 13. 渲染詞彙標記和統計卡片
|
||
F->>U: 14. 顯示完整分析結果
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 **前後端整合規格**
|
||
|
||
### **API整合詳細設計**
|
||
|
||
#### **前端請求實現**
|
||
```typescript
|
||
// 位置: frontend/app/generate/page.tsx
|
||
const handleAnalyzeSentence = async () => {
|
||
try {
|
||
const response = await fetch('http://localhost:5008/api/ai/analyze-sentence', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${getAuthToken()}` // 可選
|
||
},
|
||
body: JSON.stringify({
|
||
inputText: textInput,
|
||
analysisMode: 'full',
|
||
options: {
|
||
includeGrammarCheck: true,
|
||
includeVocabularyAnalysis: true,
|
||
includeTranslation: true,
|
||
includeIdiomDetection: true,
|
||
includeExamples: true
|
||
}
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`API請求失敗: ${response.status}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
handleAnalysisResult(result.data);
|
||
} catch (error) {
|
||
handleAnalysisError(error);
|
||
}
|
||
};
|
||
```
|
||
|
||
#### **數據處理邏輯**
|
||
```typescript
|
||
// 前端個人化統計計算
|
||
const calculateVocabularyStats = (vocabularyAnalysis, idioms, userLevel) => {
|
||
const userIndex = CEFR_LEVELS.indexOf(userLevel);
|
||
let simple = 0, moderate = 0, difficult = 0;
|
||
|
||
Object.values(vocabularyAnalysis).forEach(word => {
|
||
const wordIndex = CEFR_LEVELS.indexOf(word.difficultyLevel);
|
||
if (userIndex > wordIndex) simple++;
|
||
else if (userIndex === wordIndex) moderate++;
|
||
else difficult++;
|
||
});
|
||
|
||
return {
|
||
simpleCount: simple,
|
||
moderateCount: moderate,
|
||
difficultCount: difficult,
|
||
idiomCount: idioms.length
|
||
};
|
||
};
|
||
```
|
||
|
||
### **錯誤處理整合**
|
||
|
||
#### **前端錯誤處理**
|
||
```typescript
|
||
const handleAnalysisError = (error) => {
|
||
console.error('Analysis error:', error);
|
||
|
||
// 顯示用戶友善的錯誤訊息
|
||
if (error.message.includes('timeout')) {
|
||
setErrorMessage('分析服務繁忙,請稍後再試');
|
||
} else if (error.message.includes('network')) {
|
||
setErrorMessage('網路連接問題,請檢查網路狀態');
|
||
} else {
|
||
setErrorMessage('分析過程中發生錯誤,請稍後再試');
|
||
}
|
||
|
||
// 提供降級體驗
|
||
setFallbackAnalysisView(textInput);
|
||
};
|
||
```
|
||
|
||
#### **後端錯誤映射**
|
||
```csharp
|
||
// 位置: backend/Controllers/AIController.cs
|
||
private ApiErrorResponse CreateErrorResponse(string code, string message, object? details, string requestId)
|
||
{
|
||
var userFriendlyMessage = code switch
|
||
{
|
||
"INVALID_INPUT" => "輸入格式不正確,請檢查文本內容",
|
||
"AI_SERVICE_ERROR" => "AI分析服務暫時不可用,請稍後重試",
|
||
"RATE_LIMIT_EXCEEDED" => "請求過於頻繁,請稍候再試",
|
||
"TIMEOUT" => "分析超時,請嘗試較短的句子",
|
||
_ => "系統暫時不可用,請稍後重試"
|
||
};
|
||
|
||
return new ApiErrorResponse
|
||
{
|
||
Success = false,
|
||
Error = new ApiError
|
||
{
|
||
Code = code,
|
||
Message = userFriendlyMessage,
|
||
Details = details,
|
||
Suggestions = GetSuggestionsForError(code)
|
||
},
|
||
RequestId = requestId,
|
||
Timestamp = DateTime.UtcNow
|
||
};
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 **開發環境配置**
|
||
|
||
### **環境準備**
|
||
|
||
#### **必要軟體**
|
||
```yaml
|
||
開發工具:
|
||
- Node.js: >= 18.0.0
|
||
- .NET SDK: >= 8.0.0
|
||
- Git: >= 2.40.0
|
||
- VSCode: 最新版本
|
||
|
||
瀏覽器支援:
|
||
- Chrome: >= 90 (開發調試用)
|
||
- Safari: >= 14 (測試用)
|
||
- Firefox: >= 88 (測試用)
|
||
|
||
可選工具:
|
||
- Docker: >= 20.0 (容器化部署)
|
||
- Redis: >= 6.0 (本地快取測試)
|
||
- Postman: API測試
|
||
```
|
||
|
||
#### **環境變數配置**
|
||
```bash
|
||
# 後端環境變數
|
||
export GEMINI_API_KEY="your-gemini-api-key"
|
||
export ASPNETCORE_ENVIRONMENT="Development"
|
||
export DRAMALING_DB_CONNECTION="Data Source=dramaling_test.db"
|
||
|
||
# 前端環境變數 (可選)
|
||
export NEXT_PUBLIC_API_URL="http://localhost:5008"
|
||
export NEXT_PUBLIC_ENVIRONMENT="development"
|
||
```
|
||
|
||
### **啟動流程**
|
||
|
||
#### **開發環境啟動腳本**
|
||
```bash
|
||
#!/bin/bash
|
||
# 位置: start-development.sh
|
||
|
||
echo "🚀 啟動 DramaLing 開發環境..."
|
||
|
||
# 1. 檢查必要軟體
|
||
check_prerequisites() {
|
||
command -v node >/dev/null 2>&1 || { echo "需要安裝 Node.js"; exit 1; }
|
||
command -v dotnet >/dev/null 2>&1 || { echo "需要安裝 .NET SDK"; exit 1; }
|
||
}
|
||
|
||
# 2. 啟動後端 API (Port 5008)
|
||
start_backend() {
|
||
echo "🔧 啟動後端 API..."
|
||
cd backend/DramaLing.Api
|
||
dotnet restore
|
||
dotnet run &
|
||
BACKEND_PID=$!
|
||
echo "後端 PID: $BACKEND_PID"
|
||
}
|
||
|
||
# 3. 啟動前端 (Port 3000)
|
||
start_frontend() {
|
||
echo "🎨 啟動前端..."
|
||
cd ../../frontend
|
||
npm install
|
||
npm run dev &
|
||
FRONTEND_PID=$!
|
||
echo "前端 PID: $FRONTEND_PID"
|
||
}
|
||
|
||
# 4. 健康檢查
|
||
health_check() {
|
||
echo "🏥 執行健康檢查..."
|
||
sleep 10
|
||
|
||
# 檢查後端
|
||
if curl -f http://localhost:5008/health >/dev/null 2>&1; then
|
||
echo "✅ 後端服務正常"
|
||
else
|
||
echo "❌ 後端服務異常"
|
||
fi
|
||
|
||
# 檢查前端
|
||
if curl -f http://localhost:3000 >/dev/null 2>&1; then
|
||
echo "✅ 前端服務正常"
|
||
else
|
||
echo "❌ 前端服務異常"
|
||
fi
|
||
}
|
||
|
||
# 執行啟動流程
|
||
check_prerequisites
|
||
start_backend
|
||
start_frontend
|
||
health_check
|
||
|
||
echo "🎉 開發環境啟動完成!"
|
||
echo "前端: http://localhost:3000"
|
||
echo "後端API: http://localhost:5008"
|
||
echo "API文檔: http://localhost:5008/swagger"
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 **測試整合策略**
|
||
|
||
### **整合測試架構**
|
||
|
||
#### **API整合測試**
|
||
```csharp
|
||
[TestFixture]
|
||
public class AIAnalysisIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
|
||
{
|
||
private readonly WebApplicationFactory<Program> _factory;
|
||
private readonly HttpClient _client;
|
||
|
||
[SetUp]
|
||
public void Setup()
|
||
{
|
||
_client = _factory.CreateClient();
|
||
}
|
||
|
||
[Test]
|
||
public async Task AnalyzeSentence_EndToEnd_ReturnsValidResponse()
|
||
{
|
||
// Arrange
|
||
var request = new
|
||
{
|
||
inputText = "She just join the team, so let's cut her some slack.",
|
||
analysisMode = "full"
|
||
};
|
||
|
||
// Act
|
||
var response = await _client.PostAsJsonAsync("/api/ai/analyze-sentence", request);
|
||
|
||
// Assert
|
||
response.EnsureSuccessStatusCode();
|
||
var result = await response.Content.ReadFromJsonAsync<AnalysisResponse>();
|
||
|
||
Assert.That(result.Success, Is.True);
|
||
Assert.That(result.Data.VocabularyAnalysis.Count, Is.GreaterThan(0));
|
||
Assert.That(result.Data.SentenceMeaning, Is.Not.Empty);
|
||
Assert.That(result.Data.Idioms.Count, Is.GreaterThan(0));
|
||
}
|
||
}
|
||
```
|
||
|
||
#### **前端E2E測試**
|
||
```typescript
|
||
// 使用 Playwright 或 Cypress
|
||
describe('AI Analysis E2E Flow', () => {
|
||
test('complete analysis workflow', async ({ page }) => {
|
||
// 1. 導航到分析頁面
|
||
await page.goto('http://localhost:3000/generate');
|
||
|
||
// 2. 輸入測試句子
|
||
await page.fill('[data-testid="text-input"]',
|
||
'She just join the team, so let\'s cut her some slack.');
|
||
|
||
// 3. 點擊分析按鈕
|
||
await page.click('[data-testid="analyze-button"]');
|
||
|
||
// 4. 等待分析完成
|
||
await page.waitForSelector('[data-testid="analysis-result"]', { timeout: 10000 });
|
||
|
||
// 5. 驗證結果
|
||
await expect(page.locator('[data-testid="grammar-correction"]')).toBeVisible();
|
||
await expect(page.locator('[data-testid="vocabulary-analysis"]')).toBeVisible();
|
||
await expect(page.locator('[data-testid="idioms-section"]')).toBeVisible();
|
||
await expect(page.locator('[data-testid="statistics-cards"]')).toBeVisible();
|
||
|
||
// 6. 測試詞彙點擊
|
||
await page.click('[data-testid="word-she"]');
|
||
await expect(page.locator('[data-testid="vocab-popup"]')).toBeVisible();
|
||
});
|
||
});
|
||
```
|
||
|
||
### **性能測試整合**
|
||
|
||
#### **負載測試配置**
|
||
```yaml
|
||
# 使用 k6 或 JMeter
|
||
負載測試場景:
|
||
- 正常負載: 100 用戶,持續 10 分鐘
|
||
- 壓力測試: 500 用戶,持續 5 分鐘
|
||
- 尖峰測試: 1000 用戶,持續 2 分鐘
|
||
|
||
性能指標:
|
||
- 回應時間P95: < 5秒
|
||
- 錯誤率: < 1%
|
||
- 吞吐量: > 100 RPS
|
||
- 資源使用: CPU < 80%, Memory < 70%
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 **部署架構**
|
||
|
||
### **環境配置**
|
||
|
||
#### **開發環境 (Development)**
|
||
```yaml
|
||
基礎設施:
|
||
- 本地開發機器
|
||
- SQLite 資料庫
|
||
- In-Memory 快取
|
||
- Gemini API (測試金鑰)
|
||
|
||
配置特點:
|
||
- 詳細日誌輸出
|
||
- 熱重載支援
|
||
- Swagger API 文檔
|
||
- CORS 寬鬆政策
|
||
```
|
||
|
||
#### **測試環境 (Staging)**
|
||
```yaml
|
||
基礎設施:
|
||
- 雲端虛擬機 或 Docker 容器
|
||
- PostgreSQL 資料庫
|
||
- Redis 快取
|
||
- Gemini API (測試金鑰)
|
||
|
||
配置特點:
|
||
- 生產環境模擬
|
||
- 效能監控啟用
|
||
- 自動化測試整合
|
||
- 安全掃描
|
||
```
|
||
|
||
#### **生產環境 (Production)**
|
||
```yaml
|
||
基礎設施:
|
||
- Kubernetes 叢集 或 雲端服務
|
||
- PostgreSQL 高可用性叢集
|
||
- Redis 叢集
|
||
- Gemini API (生產金鑰)
|
||
- CDN 和負載均衡
|
||
|
||
配置特點:
|
||
- 高可用性 (99.9%+)
|
||
- 自動擴容
|
||
- 全面監控和告警
|
||
- 災難恢復機制
|
||
```
|
||
|
||
### **容器化部署**
|
||
|
||
#### **Docker Compose 配置**
|
||
```yaml
|
||
# docker-compose.yml
|
||
version: '3.8'
|
||
|
||
services:
|
||
# 後端 API 服務
|
||
backend:
|
||
build:
|
||
context: ./backend/DramaLing.Api
|
||
dockerfile: Dockerfile
|
||
ports:
|
||
- "5008:5008"
|
||
environment:
|
||
- ASPNETCORE_ENVIRONMENT=Production
|
||
- GEMINI_API_KEY=${GEMINI_API_KEY}
|
||
- ConnectionStrings__DefaultConnection=${DB_CONNECTION}
|
||
depends_on:
|
||
- database
|
||
- redis
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-f", "http://localhost:5008/health"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 40s
|
||
|
||
# 前端服務
|
||
frontend:
|
||
build:
|
||
context: ./frontend
|
||
dockerfile: Dockerfile
|
||
ports:
|
||
- "3000:3000"
|
||
environment:
|
||
- NEXT_PUBLIC_API_URL=http://backend:5008
|
||
depends_on:
|
||
- backend
|
||
|
||
# 資料庫服務
|
||
database:
|
||
image: postgres:15-alpine
|
||
environment:
|
||
- POSTGRES_DB=dramaling
|
||
- POSTGRES_USER=${DB_USER}
|
||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
ports:
|
||
- "5432:5432"
|
||
|
||
# 快取服務
|
||
redis:
|
||
image: redis:7-alpine
|
||
ports:
|
||
- "6379:6379"
|
||
volumes:
|
||
- redis_data:/data
|
||
|
||
volumes:
|
||
postgres_data:
|
||
redis_data:
|
||
```
|
||
|
||
#### **Kubernetes 部署配置**
|
||
```yaml
|
||
# k8s-deployment.yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: dramaling-backend
|
||
spec:
|
||
replicas: 3
|
||
selector:
|
||
matchLabels:
|
||
app: dramaling-backend
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: dramaling-backend
|
||
spec:
|
||
containers:
|
||
- name: backend
|
||
image: dramaling/backend:latest
|
||
ports:
|
||
- containerPort: 5008
|
||
env:
|
||
- name: GEMINI_API_KEY
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: ai-secrets
|
||
key: gemini-api-key
|
||
- name: ConnectionStrings__DefaultConnection
|
||
valueFrom:
|
||
configMapKeyRef:
|
||
name: app-config
|
||
key: db-connection
|
||
resources:
|
||
requests:
|
||
memory: "256Mi"
|
||
cpu: "250m"
|
||
limits:
|
||
memory: "512Mi"
|
||
cpu: "500m"
|
||
livenessProbe:
|
||
httpGet:
|
||
path: /health
|
||
port: 5008
|
||
initialDelaySeconds: 30
|
||
periodSeconds: 30
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /health
|
||
port: 5008
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 5
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 **監控與可觀測性**
|
||
|
||
### **日誌整合**
|
||
|
||
#### **結構化日誌配置**
|
||
```json
|
||
// appsettings.Production.json
|
||
{
|
||
"Serilog": {
|
||
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.ApplicationInsights"],
|
||
"MinimumLevel": {
|
||
"Default": "Information",
|
||
"Override": {
|
||
"Microsoft": "Warning",
|
||
"System": "Warning",
|
||
"DramaLing": "Information"
|
||
}
|
||
},
|
||
"WriteTo": [
|
||
{
|
||
"Name": "Console",
|
||
"Args": {
|
||
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"
|
||
}
|
||
},
|
||
{
|
||
"Name": "ApplicationInsights",
|
||
"Args": {
|
||
"instrumentationKey": "{ApplicationInsights:InstrumentationKey}"
|
||
}
|
||
}
|
||
],
|
||
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
|
||
}
|
||
}
|
||
```
|
||
|
||
#### **前端錯誤追蹤**
|
||
```typescript
|
||
// 錯誤邊界和監控
|
||
class ErrorBoundary extends React.Component {
|
||
componentDidCatch(error, errorInfo) {
|
||
// 發送錯誤到監控服務
|
||
console.error('React Error Boundary:', error, errorInfo);
|
||
|
||
// 可選:整合 Sentry 或其他錯誤追蹤服務
|
||
if (typeof window !== 'undefined' && window.gtag) {
|
||
window.gtag('event', 'exception', {
|
||
description: error.toString(),
|
||
fatal: false
|
||
});
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### **健康檢查系統**
|
||
|
||
#### **深度健康檢查**
|
||
```csharp
|
||
public class SystemHealthCheck : IHealthCheck
|
||
{
|
||
private readonly IGeminiService _geminiService;
|
||
private readonly DramaLingDbContext _dbContext;
|
||
|
||
public async Task<HealthCheckResult> CheckHealthAsync(
|
||
HealthCheckContext context, CancellationToken cancellationToken = default)
|
||
{
|
||
var checks = new Dictionary<string, HealthStatus>();
|
||
|
||
// 檢查資料庫連接
|
||
try
|
||
{
|
||
await _dbContext.Database.CanConnectAsync(cancellationToken);
|
||
checks["database"] = HealthStatus.Healthy;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
checks["database"] = HealthStatus.Unhealthy;
|
||
}
|
||
|
||
// 檢查 AI 服務
|
||
try
|
||
{
|
||
var isHealthy = await _geminiService.HealthCheckAsync();
|
||
checks["gemini_api"] = isHealthy ? HealthStatus.Healthy : HealthStatus.Degraded;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
checks["gemini_api"] = HealthStatus.Unhealthy;
|
||
}
|
||
|
||
// 檢查記憶體使用
|
||
var memoryUsage = GC.GetTotalMemory(false);
|
||
checks["memory"] = memoryUsage < 500_000_000 ? HealthStatus.Healthy : HealthStatus.Degraded;
|
||
|
||
var overallStatus = checks.Values.All(s => s == HealthStatus.Healthy)
|
||
? HealthStatus.Healthy
|
||
: checks.Values.Any(s => s == HealthStatus.Unhealthy)
|
||
? HealthStatus.Unhealthy
|
||
: HealthStatus.Degraded;
|
||
|
||
return new HealthCheckResult(overallStatus,
|
||
description: $"System health: {string.Join(", ", checks.Select(c => $"{c.Key}:{c.Value}"))}",
|
||
data: checks.ToDictionary(c => c.Key, c => (object)c.Value.ToString()));
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔒 **安全整合**
|
||
|
||
### **HTTPS 配置**
|
||
```yaml
|
||
開發環境:
|
||
- HTTP: localhost:3000, localhost:5008
|
||
- 自簽證書: dotnet dev-certs https --trust
|
||
|
||
生產環境:
|
||
- HTTPS: 強制重定向
|
||
- TLS 1.3: 最低版本要求
|
||
- HSTS: 嚴格傳輸安全
|
||
- 證書: Let's Encrypt 或企業CA
|
||
```
|
||
|
||
### **CORS 政策**
|
||
```csharp
|
||
// 開發環境 CORS 配置
|
||
services.AddCors(options =>
|
||
{
|
||
options.AddPolicy("Development", policy =>
|
||
{
|
||
policy.WithOrigins("http://localhost:3000", "http://localhost:3001")
|
||
.AllowAnyHeader()
|
||
.AllowAnyMethod()
|
||
.AllowCredentials();
|
||
});
|
||
|
||
options.AddPolicy("Production", policy =>
|
||
{
|
||
policy.WithOrigins("https://dramaling.com", "https://app.dramaling.com")
|
||
.AllowAnyHeader()
|
||
.AllowAnyMethod()
|
||
.AllowCredentials();
|
||
});
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 **監控整合**
|
||
|
||
### **應用程式監控**
|
||
|
||
#### **關鍵指標儀表板**
|
||
```yaml
|
||
業務指標:
|
||
- 每日分析次數
|
||
- 用戶活躍度
|
||
- 功能使用分佈
|
||
- AI分析成功率
|
||
|
||
技術指標:
|
||
- API回應時間分佈
|
||
- 資料庫查詢性能
|
||
- 記憶體和CPU使用
|
||
- 錯誤率和異常統計
|
||
|
||
用戶體驗指標:
|
||
- 頁面載入時間
|
||
- 首次內容繪製 (FCP)
|
||
- 最大內容繪製 (LCP)
|
||
- 累積佈局偏移 (CLS)
|
||
```
|
||
|
||
#### **告警配置**
|
||
```yaml
|
||
嚴重告警:
|
||
- API 錯誤率 > 5% (5分鐘內)
|
||
- 回應時間P95 > 10秒 (5分鐘內)
|
||
- 服務不可用 > 2分鐘
|
||
- 資料庫連接失敗
|
||
|
||
警告告警:
|
||
- CPU 使用率 > 80% (10分鐘內)
|
||
- 記憶體使用率 > 85% (10分鐘內)
|
||
- AI API 調用失敗率 > 10%
|
||
- 磁碟空間不足 < 10%
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 **故障排除指南**
|
||
|
||
### **常見問題和解決方案**
|
||
|
||
#### **連接問題**
|
||
```yaml
|
||
問題: CORS 錯誤
|
||
症狀: "Access to fetch blocked by CORS policy"
|
||
解決: 檢查後端 CORS 設定,確認前端域名在允許清單
|
||
|
||
問題: 連接被拒絕
|
||
症狀: "Connection refused" 或 "ECONNREFUSED"
|
||
解決: 確認後端服務正在運行,檢查埠號是否正確
|
||
|
||
問題: 超時錯誤
|
||
症狀: "Request timeout" 或響應超過 30 秒
|
||
解決: 檢查 AI API 金鑰,網路連接,增加超時設定
|
||
```
|
||
|
||
#### **資料問題**
|
||
```yaml
|
||
問題: AI 回應格式錯誤
|
||
症狀: "Cannot read property 'vocabularyAnalysis' of undefined"
|
||
解決: 檢查 Gemini API 回應格式,更新錯誤處理邏輯
|
||
|
||
問題: 詞彙分析為空
|
||
症狀: 分析結果不包含詞彙資訊
|
||
解決: 檢查 AI Prompt 設計,確認輸入文本有效
|
||
|
||
問題: 統計數字不一致
|
||
症狀: 統計卡片數字與實際標記不符
|
||
解決: 檢查前端統計計算邏輯,確認分類算法正確
|
||
```
|
||
|
||
### **調試工具**
|
||
|
||
#### **開發調試指令**
|
||
```bash
|
||
# 檢查服務狀態
|
||
curl -I http://localhost:5008/health
|
||
curl -I http://localhost:3000
|
||
|
||
# 測試 API 端點
|
||
curl -X POST http://localhost:5008/api/ai/analyze-sentence \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"inputText":"Test sentence","analysisMode":"full"}'
|
||
|
||
# 檢查日誌
|
||
docker logs dramaling-backend
|
||
docker logs dramaling-frontend
|
||
|
||
# 檢查資源使用
|
||
docker stats
|
||
top -p $(pgrep dotnet)
|
||
```
|
||
|
||
#### **生產監控指令**
|
||
```bash
|
||
# 健康檢查
|
||
kubectl get pods -l app=dramaling
|
||
kubectl describe pod dramaling-backend-xxx
|
||
|
||
# 查看日誌
|
||
kubectl logs -f deployment/dramaling-backend
|
||
kubectl logs -f deployment/dramaling-frontend
|
||
|
||
# 性能監控
|
||
kubectl top pods
|
||
kubectl top nodes
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 **部署檢查清單**
|
||
|
||
### **部署前檢查**
|
||
- [ ] 所有測試通過 (單元、整合、E2E)
|
||
- [ ] 安全掃描無嚴重漏洞
|
||
- [ ] 性能基準測試達標
|
||
- [ ] 配置檔案正確設定
|
||
- [ ] 環境變數和金鑰配置完成
|
||
- [ ] 資料庫遷移腳本準備
|
||
- [ ] 監控和告警配置完成
|
||
- [ ] 回滾計劃準備
|
||
|
||
### **部署後驗證**
|
||
- [ ] 健康檢查端點回應正常
|
||
- [ ] API 功能端到端測試通過
|
||
- [ ] 前端頁面載入和功能正常
|
||
- [ ] 監控指標顯示正常
|
||
- [ ] 日誌記錄正確產生
|
||
- [ ] 告警機制測試正常
|
||
- [ ] 負載測試驗證性能
|
||
- [ ] 安全掃描確認無新漏洞
|
||
|
||
---
|
||
|
||
## 🔄 **CI/CD 流程**
|
||
|
||
### **持續整合流程**
|
||
```yaml
|
||
觸發條件:
|
||
- 主分支推送 (main)
|
||
- Pull Request 建立
|
||
- 標籤建立 (v*.*.*)
|
||
|
||
建置步驟:
|
||
1. 程式碼檢出
|
||
2. 依賴安裝
|
||
3. 靜態分析 (ESLint, SonarQube)
|
||
4. 單元測試執行
|
||
5. 測試覆蓋率檢查
|
||
6. 安全掃描
|
||
7. 建置 Docker 映像
|
||
8. 整合測試執行
|
||
|
||
部署條件:
|
||
- 所有測試通過
|
||
- 程式碼覆蓋率 > 80%
|
||
- 安全掃描通過
|
||
- 人工審核批准 (生產部署)
|
||
```
|
||
|
||
### **持續部署流程**
|
||
```yaml
|
||
測試環境自動部署:
|
||
- 主分支每次推送自動部署
|
||
- 自動執行煙霧測試
|
||
- 通知團隊部署狀態
|
||
|
||
生產環境部署:
|
||
- 手動觸發或定期發布
|
||
- 藍綠部署或滾動更新
|
||
- 自動回滾機制
|
||
- 部署後監控和驗證
|
||
```
|
||
|
||
---
|
||
|
||
**文件版本**: v2.0
|
||
**DevOps負責人**: DramaLing DevOps團隊
|
||
**最後更新**: 2025-01-25
|
||
**下次審查**: 2025-02-25
|
||
|
||
**關聯文件**:
|
||
- 《AI句子分析功能產品需求規格》- 產品需求和用戶故事
|
||
- 《AI分析API技術實現規格》- API設計和技術實現
|
||
- 《AI驅動產品後端技術架構指南》- 架構設計指導原則 |