873 lines
15 KiB
Markdown
873 lines
15 KiB
Markdown
# DramaLing API 規格文檔
|
||
|
||
## 1. API 概述
|
||
|
||
### 1.1 基本資訊
|
||
- **Base URL**:
|
||
- 開發: `http://localhost:3000/api`
|
||
- 生產: `https://api.dramaling.com`
|
||
- **版本**: v1
|
||
- **協議**: HTTPS
|
||
- **格式**: JSON
|
||
- **認證**: Bearer Token (JWT)
|
||
|
||
### 1.2 通用規範
|
||
|
||
#### 請求標頭
|
||
```http
|
||
Content-Type: application/json
|
||
Authorization: Bearer <token>
|
||
Accept-Language: zh-TW
|
||
X-Request-ID: <uuid>
|
||
```
|
||
|
||
#### 響應格式
|
||
```typescript
|
||
interface SuccessResponse<T> {
|
||
success: true;
|
||
data: T;
|
||
meta?: {
|
||
page?: number;
|
||
limit?: number;
|
||
total?: number;
|
||
hasMore?: boolean;
|
||
};
|
||
}
|
||
|
||
interface ErrorResponse {
|
||
success: false;
|
||
error: {
|
||
code: string;
|
||
message: string;
|
||
details?: any;
|
||
};
|
||
timestamp: string;
|
||
}
|
||
```
|
||
|
||
#### HTTP 狀態碼
|
||
- `200 OK` - 請求成功
|
||
- `201 Created` - 資源創建成功
|
||
- `204 No Content` - 刪除成功
|
||
- `400 Bad Request` - 請求參數錯誤
|
||
- `401 Unauthorized` - 未認證
|
||
- `403 Forbidden` - 無權限
|
||
- `404 Not Found` - 資源不存在
|
||
- `409 Conflict` - 資源衝突
|
||
- `422 Unprocessable Entity` - 驗證失敗
|
||
- `429 Too Many Requests` - 請求過多
|
||
- `500 Internal Server Error` - 伺服器錯誤
|
||
|
||
## 2. 認證 API
|
||
|
||
### 2.1 註冊
|
||
**POST** `/api/auth/register`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"email": "user@example.com",
|
||
"password": "SecurePass123!",
|
||
"username": "johndoe"
|
||
}
|
||
```
|
||
|
||
#### 響應 (201)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"user": {
|
||
"id": "uuid",
|
||
"email": "user@example.com",
|
||
"username": "johndoe",
|
||
"emailVerified": false,
|
||
"createdAt": "2024-03-15T10:00:00Z"
|
||
},
|
||
"tokens": {
|
||
"accessToken": "eyJ...",
|
||
"refreshToken": "eyJ...",
|
||
"expiresIn": 900
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 錯誤響應 (409)
|
||
```json
|
||
{
|
||
"success": false,
|
||
"error": {
|
||
"code": "EMAIL_EXISTS",
|
||
"message": "Email already registered"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.2 登入
|
||
**POST** `/api/auth/login`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"email": "user@example.com",
|
||
"password": "SecurePass123!",
|
||
"rememberMe": true
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"user": {
|
||
"id": "uuid",
|
||
"email": "user@example.com",
|
||
"username": "johndoe",
|
||
"avatarUrl": "https://...",
|
||
"lastLoginAt": "2024-03-15T10:00:00Z"
|
||
},
|
||
"tokens": {
|
||
"accessToken": "eyJ...",
|
||
"refreshToken": "eyJ...",
|
||
"expiresIn": 2592000
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.3 Google OAuth
|
||
**POST** `/api/auth/google`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"idToken": "google_id_token"
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"user": {
|
||
"id": "uuid",
|
||
"email": "user@gmail.com",
|
||
"username": "user",
|
||
"provider": "google",
|
||
"avatarUrl": "https://..."
|
||
},
|
||
"tokens": {
|
||
"accessToken": "eyJ...",
|
||
"refreshToken": "eyJ..."
|
||
},
|
||
"isNewUser": false
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.4 重新整理 Token
|
||
**POST** `/api/auth/refresh`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"refreshToken": "eyJ..."
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"accessToken": "eyJ...",
|
||
"refreshToken": "eyJ...",
|
||
"expiresIn": 900
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.5 登出
|
||
**POST** `/api/auth/logout`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"refreshToken": "eyJ..."
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"message": "Logged out successfully"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.6 忘記密碼
|
||
**POST** `/api/auth/forgot-password`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"email": "user@example.com"
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"message": "Password reset email sent"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.7 重設密碼
|
||
**POST** `/api/auth/reset-password`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"token": "reset_token",
|
||
"newPassword": "NewSecurePass123!"
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"message": "Password reset successfully"
|
||
}
|
||
}
|
||
```
|
||
|
||
## 3. 用戶 API
|
||
|
||
### 3.1 取得個人資料
|
||
**GET** `/api/users/me`
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "uuid",
|
||
"email": "user@example.com",
|
||
"username": "johndoe",
|
||
"avatarUrl": "https://...",
|
||
"emailVerified": true,
|
||
"createdAt": "2024-03-01T00:00:00Z",
|
||
"stats": {
|
||
"totalFlashcards": 150,
|
||
"totalDecks": 5,
|
||
"studyStreak": 7,
|
||
"level": 3,
|
||
"experience": 1250
|
||
},
|
||
"preferences": {
|
||
"dailyGoal": 20,
|
||
"reminderTime": "09:00",
|
||
"reminderEnabled": true,
|
||
"theme": "light",
|
||
"language": "zh-TW"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.2 更新個人資料
|
||
**PATCH** `/api/users/me`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"username": "newusername",
|
||
"avatarUrl": "https://..."
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "uuid",
|
||
"username": "newusername",
|
||
"avatarUrl": "https://...",
|
||
"updatedAt": "2024-03-15T10:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.3 更新偏好設定
|
||
**PUT** `/api/users/me/preferences`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"dailyGoal": 30,
|
||
"reminderTime": "20:00",
|
||
"reminderEnabled": true,
|
||
"theme": "dark",
|
||
"language": "zh-TW",
|
||
"soundEnabled": true,
|
||
"autoPlayAudio": false
|
||
}
|
||
```
|
||
|
||
## 4. 卡組 API
|
||
|
||
### 4.1 取得卡組列表
|
||
**GET** `/api/decks`
|
||
|
||
#### 查詢參數
|
||
- `page` (number): 頁數,預設 1
|
||
- `limit` (number): 每頁數量,預設 20
|
||
- `sort` (string): 排序方式 `created_at` | `updated_at` | `name`
|
||
- `order` (string): 排序順序 `asc` | `desc`
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"id": "uuid",
|
||
"name": "Business English",
|
||
"description": "Common business vocabulary",
|
||
"coverImage": "https://...",
|
||
"flashcardCount": 45,
|
||
"isPublic": false,
|
||
"tags": ["business", "professional"],
|
||
"createdAt": "2024-03-01T00:00:00Z",
|
||
"updatedAt": "2024-03-15T00:00:00Z"
|
||
}
|
||
],
|
||
"meta": {
|
||
"page": 1,
|
||
"limit": 20,
|
||
"total": 5,
|
||
"hasMore": false
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 取得單一卡組
|
||
**GET** `/api/decks/{deckId}`
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "uuid",
|
||
"name": "Business English",
|
||
"description": "Common business vocabulary",
|
||
"coverImage": "https://...",
|
||
"flashcardCount": 45,
|
||
"isPublic": false,
|
||
"tags": ["business", "professional"],
|
||
"flashcards": [
|
||
{
|
||
"id": "uuid",
|
||
"word": "negotiate",
|
||
"translation": "協商",
|
||
"difficulty": "intermediate"
|
||
}
|
||
],
|
||
"createdAt": "2024-03-01T00:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3 創建卡組
|
||
**POST** `/api/decks`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"name": "TOEFL Vocabulary",
|
||
"description": "Essential TOEFL words",
|
||
"coverImage": "https://...",
|
||
"isPublic": false,
|
||
"tags": ["toefl", "exam"]
|
||
}
|
||
```
|
||
|
||
#### 響應 (201)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": "uuid",
|
||
"name": "TOEFL Vocabulary",
|
||
"description": "Essential TOEFL words",
|
||
"flashcardCount": 0,
|
||
"createdAt": "2024-03-15T10:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4 更新卡組
|
||
**PATCH** `/api/decks/{deckId}`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"name": "Updated Name",
|
||
"description": "Updated description"
|
||
}
|
||
```
|
||
|
||
### 4.5 刪除卡組
|
||
**DELETE** `/api/decks/{deckId}`
|
||
|
||
#### 響應 (204)
|
||
無內容
|
||
|
||
## 5. 詞卡 API
|
||
|
||
### 5.1 取得詞卡列表
|
||
**GET** `/api/flashcards`
|
||
|
||
#### 查詢參數
|
||
- `deckId` (string): 卡組 ID
|
||
- `search` (string): 搜尋關鍵字
|
||
- `tags` (string[]): 標籤篩選
|
||
- `difficulty` (string): 難度篩選
|
||
- `status` (string): 學習狀態 `new` | `learning` | `review` | `mastered`
|
||
- `page` (number): 頁數
|
||
- `limit` (number): 每頁數量
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"id": "uuid",
|
||
"deckId": "uuid",
|
||
"word": "negotiate",
|
||
"translation": "協商",
|
||
"definition": "To discuss something with someone in order to reach an agreement",
|
||
"partOfSpeech": "verb",
|
||
"pronunciation": "/nɪˈɡoʊʃieɪt/",
|
||
"exampleSentence": "We need to negotiate a better deal.",
|
||
"exampleTranslation": "我們需要協商一個更好的交易。",
|
||
"difficulty": "intermediate",
|
||
"tags": ["business", "communication"],
|
||
"learningStatus": {
|
||
"status": "learning",
|
||
"nextReviewDate": "2024-03-16T00:00:00Z",
|
||
"accuracy": 75
|
||
}
|
||
}
|
||
],
|
||
"meta": {
|
||
"page": 1,
|
||
"limit": 20,
|
||
"total": 150,
|
||
"hasMore": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 取得單一詞卡
|
||
**GET** `/api/flashcards/{flashcardId}`
|
||
|
||
### 5.3 創建詞卡
|
||
**POST** `/api/flashcards`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"deckId": "uuid",
|
||
"word": "negotiate",
|
||
"translation": "協商",
|
||
"definition": "To discuss something with someone in order to reach an agreement",
|
||
"partOfSpeech": "verb",
|
||
"pronunciation": "/nɪˈɡoʊʃieɪt/",
|
||
"exampleSentence": "We need to negotiate a better deal.",
|
||
"exampleTranslation": "我們需要協商一個更好的交易。",
|
||
"difficulty": "intermediate",
|
||
"memoryTip": "Think of 'go' + 'she' + 'ate' = negotiate",
|
||
"tags": ["business", "communication"]
|
||
}
|
||
```
|
||
|
||
### 5.4 更新詞卡
|
||
**PATCH** `/api/flashcards/{flashcardId}`
|
||
|
||
### 5.5 刪除詞卡
|
||
**DELETE** `/api/flashcards/{flashcardId}`
|
||
|
||
### 5.6 批量操作
|
||
**POST** `/api/flashcards/bulk`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"flashcardIds": ["uuid1", "uuid2", "uuid3"],
|
||
"operation": "move",
|
||
"targetDeckId": "uuid"
|
||
}
|
||
```
|
||
|
||
## 6. AI 生成 API
|
||
|
||
### 6.1 生成詞卡
|
||
**POST** `/api/ai/generate`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"text": "The quick brown fox jumps over the lazy dog...",
|
||
"theme": "daily_conversation",
|
||
"count": 10,
|
||
"difficulty": "intermediate",
|
||
"includeExamples": true,
|
||
"targetDeckId": "uuid"
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"requestId": "uuid",
|
||
"status": "processing",
|
||
"estimatedTime": 5000,
|
||
"flashcards": [
|
||
{
|
||
"word": "negotiate",
|
||
"translation": "協商",
|
||
"definition": "To discuss...",
|
||
"pronunciation": "/nɪˈɡoʊʃieɪt/",
|
||
"example": "We need to negotiate...",
|
||
"difficulty": "intermediate"
|
||
}
|
||
],
|
||
"metadata": {
|
||
"tokensUsed": 1500,
|
||
"processingTime": 3200,
|
||
"model": "gemini-pro"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.2 取得生成狀態
|
||
**GET** `/api/ai/generate/{requestId}`
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"requestId": "uuid",
|
||
"status": "completed",
|
||
"flashcards": [...],
|
||
"completedAt": "2024-03-15T10:00:05Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.3 取得用戶配額
|
||
**GET** `/api/ai/quota`
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"daily": {
|
||
"used": 25,
|
||
"limit": 50,
|
||
"resetsAt": "2024-03-16T00:00:00Z"
|
||
},
|
||
"monthly": {
|
||
"used": 500,
|
||
"limit": 1500
|
||
},
|
||
"isPremium": false
|
||
}
|
||
}
|
||
```
|
||
|
||
## 7. 學習 API
|
||
|
||
### 7.1 開始學習會話
|
||
**POST** `/api/learning/sessions`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"deckId": "uuid",
|
||
"mode": "flashcard",
|
||
"cardLimit": 20
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"sessionId": "uuid",
|
||
"cards": [...],
|
||
"totalCards": 20,
|
||
"newCards": 5,
|
||
"reviewCards": 15
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.2 提交學習結果
|
||
**POST** `/api/learning/reviews`
|
||
|
||
#### 請求
|
||
```json
|
||
{
|
||
"sessionId": "uuid",
|
||
"flashcardId": "uuid",
|
||
"rating": 4,
|
||
"timeSpent": 15,
|
||
"isCorrect": true
|
||
}
|
||
```
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"nextReviewDate": "2024-03-18T00:00:00Z",
|
||
"interval": 3,
|
||
"easeFactor": 2.5,
|
||
"repetitions": 2
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.3 取得待複習詞卡
|
||
**GET** `/api/learning/due`
|
||
|
||
#### 查詢參數
|
||
- `deckId` (string): 特定卡組
|
||
- `limit` (number): 數量限制
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"dueCards": [...],
|
||
"newCards": [...],
|
||
"totalDue": 25,
|
||
"totalNew": 10
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.4 結束學習會話
|
||
**POST** `/api/learning/sessions/{sessionId}/complete`
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"summary": {
|
||
"cardsStudied": 20,
|
||
"correctAnswers": 18,
|
||
"accuracy": 90,
|
||
"timeSpent": 600,
|
||
"experience": 100,
|
||
"streakDays": 8
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 8. 統計 API
|
||
|
||
### 8.1 取得學習統計
|
||
**GET** `/api/stats`
|
||
|
||
#### 查詢參數
|
||
- `period` (string): `daily` | `weekly` | `monthly` | `yearly`
|
||
- `startDate` (string): YYYY-MM-DD
|
||
- `endDate` (string): YYYY-MM-DD
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"overview": {
|
||
"totalCards": 500,
|
||
"masteredCards": 200,
|
||
"learningCards": 250,
|
||
"newCards": 50,
|
||
"studyStreak": 15,
|
||
"totalStudyTime": 12000
|
||
},
|
||
"daily": [
|
||
{
|
||
"date": "2024-03-15",
|
||
"cardsStudied": 30,
|
||
"newCards": 5,
|
||
"reviewCards": 25,
|
||
"accuracy": 85,
|
||
"studyTime": 45
|
||
}
|
||
],
|
||
"heatmap": {
|
||
"2024-03-15": 3,
|
||
"2024-03-14": 2,
|
||
"2024-03-13": 4
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 取得成就列表
|
||
**GET** `/api/stats/achievements`
|
||
|
||
#### 響應 (200)
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"unlocked": [
|
||
{
|
||
"id": "first_card",
|
||
"name": "First Step",
|
||
"description": "Create your first flashcard",
|
||
"icon": "🎯",
|
||
"unlockedAt": "2024-03-01T00:00:00Z",
|
||
"points": 10
|
||
}
|
||
],
|
||
"inProgress": [
|
||
{
|
||
"id": "week_streak",
|
||
"name": "Week Warrior",
|
||
"description": "Study for 7 days in a row",
|
||
"progress": 85,
|
||
"target": 7,
|
||
"current": 6
|
||
}
|
||
],
|
||
"totalPoints": 250
|
||
}
|
||
}
|
||
```
|
||
|
||
## 9. 錯誤處理
|
||
|
||
### 9.1 錯誤碼列表
|
||
|
||
| 錯誤碼 | HTTP 狀態 | 描述 |
|
||
|--------|-----------|------|
|
||
| `UNAUTHORIZED` | 401 | 未認證 |
|
||
| `FORBIDDEN` | 403 | 無權限 |
|
||
| `NOT_FOUND` | 404 | 資源不存在 |
|
||
| `VALIDATION_ERROR` | 422 | 驗證失敗 |
|
||
| `EMAIL_EXISTS` | 409 | Email 已存在 |
|
||
| `USERNAME_EXISTS` | 409 | 用戶名已存在 |
|
||
| `INVALID_CREDENTIALS` | 401 | 認證資訊錯誤 |
|
||
| `TOKEN_EXPIRED` | 401 | Token 過期 |
|
||
| `RATE_LIMIT_EXCEEDED` | 429 | 請求過多 |
|
||
| `QUOTA_EXCEEDED` | 402 | 配額超限 |
|
||
| `AI_SERVICE_ERROR` | 503 | AI 服務錯誤 |
|
||
| `DATABASE_ERROR` | 500 | 資料庫錯誤 |
|
||
|
||
### 9.2 錯誤響應範例
|
||
```json
|
||
{
|
||
"success": false,
|
||
"error": {
|
||
"code": "VALIDATION_ERROR",
|
||
"message": "Validation failed",
|
||
"details": {
|
||
"email": "Invalid email format",
|
||
"password": "Password must be at least 8 characters"
|
||
}
|
||
},
|
||
"timestamp": "2024-03-15T10:00:00Z"
|
||
}
|
||
```
|
||
|
||
## 10. Rate Limiting
|
||
|
||
### 10.1 限制規則
|
||
- 一般 API: 100 requests/minute
|
||
- AI 生成: 10 requests/minute
|
||
- 認證相關: 5 requests/minute
|
||
|
||
### 10.2 響應標頭
|
||
```http
|
||
X-RateLimit-Limit: 100
|
||
X-RateLimit-Remaining: 95
|
||
X-RateLimit-Reset: 1710496800
|
||
```
|
||
|
||
### 10.3 超限響應 (429)
|
||
```json
|
||
{
|
||
"success": false,
|
||
"error": {
|
||
"code": "RATE_LIMIT_EXCEEDED",
|
||
"message": "Too many requests",
|
||
"retryAfter": 60
|
||
}
|
||
}
|
||
```
|
||
|
||
## 11. Webhook
|
||
|
||
### 11.1 事件類型
|
||
- `user.created` - 新用戶註冊
|
||
- `flashcard.generated` - AI 生成完成
|
||
- `achievement.unlocked` - 成就解鎖
|
||
- `subscription.updated` - 訂閱更新
|
||
|
||
### 11.2 Webhook 格式
|
||
```json
|
||
{
|
||
"id": "evt_uuid",
|
||
"type": "flashcard.generated",
|
||
"created": "2024-03-15T10:00:00Z",
|
||
"data": {
|
||
"requestId": "uuid",
|
||
"userId": "uuid",
|
||
"flashcardCount": 10
|
||
}
|
||
}
|
||
```
|
||
|
||
### 11.3 簽名驗證
|
||
```javascript
|
||
const signature = req.headers['x-webhook-signature'];
|
||
const payload = JSON.stringify(req.body);
|
||
const expected = crypto
|
||
.createHmac('sha256', WEBHOOK_SECRET)
|
||
.update(payload)
|
||
.digest('hex');
|
||
|
||
if (signature !== expected) {
|
||
throw new Error('Invalid signature');
|
||
}
|
||
``` |