feat: Add Rust API Server with REST and GraphQL support
- Implement JWT authentication layer - Add Engine Registry with 20+ conversion engines - Implement Conversion Service with background job processing - REST API endpoints for file conversion operations - GraphQL API with queries and mutations - Comprehensive error handling with conversion suggestions - Integration tests for both REST and GraphQL APIs - Complete API documentation Features: - REST API: /api/v1/* endpoints - GraphQL API: /graphql endpoint with playground - JWT Bearer token authentication - Engine validation with alternative suggestions - File download via API (not exposing file paths) - Support for FFmpeg, ImageMagick, LibreOffice, Pandoc, and more
This commit is contained in:
parent
d0388066a5
commit
e083e5d11d
18 changed files with 5235 additions and 0 deletions
614
api-server/docs/API_SPEC.md
Normal file
614
api-server/docs/API_SPEC.md
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
# ConvertX API 規格文件
|
||||
|
||||
## 概述
|
||||
|
||||
ConvertX API Server 是一個獨立的檔案轉換 API 服務,同時提供 REST API 和 GraphQL API。
|
||||
兩種 API 是完全獨立的服務,使用者可以選擇只使用其中一種。
|
||||
|
||||
## 認證
|
||||
|
||||
### JWT Bearer Token
|
||||
|
||||
所有 API 請求(除了健康檢查)都需要在 HTTP Header 中提供有效的 JWT Token:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <jwt-token>
|
||||
```
|
||||
|
||||
### Token 結構
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "user-unique-id",
|
||||
"exp": 1735689600,
|
||||
"iat": 1735603200,
|
||||
"email": "user@example.com",
|
||||
"roles": ["user", "admin"]
|
||||
}
|
||||
```
|
||||
|
||||
| 欄位 | 必填 | 說明 |
|
||||
|------|------|------|
|
||||
| `sub` | ✓ | 使用者唯一識別碼 |
|
||||
| `exp` | ✓ | Token 過期時間(Unix timestamp) |
|
||||
| `iat` | ✓ | Token 簽發時間(Unix timestamp) |
|
||||
| `email` | - | 使用者 Email |
|
||||
| `roles` | - | 使用者角色列表 |
|
||||
|
||||
### 認證錯誤回應
|
||||
|
||||
| 狀況 | 錯誤碼 | HTTP 狀態 |
|
||||
|------|--------|-----------|
|
||||
| 缺少 Authorization Header | `MISSING_AUTH_HEADER` | 401 |
|
||||
| Token 格式錯誤 | `INVALID_TOKEN` | 401 |
|
||||
| Token 簽名無效 | `INVALID_TOKEN` | 401 |
|
||||
| Token 已過期 | `TOKEN_EXPIRED` | 401 |
|
||||
|
||||
---
|
||||
|
||||
## REST API 規格
|
||||
|
||||
### 基礎資訊
|
||||
|
||||
- **Base URL**: `http://localhost:3001/api/v1`
|
||||
- **Content-Type**: `application/json`(一般請求)或 `multipart/form-data`(檔案上傳)
|
||||
|
||||
---
|
||||
|
||||
### 健康檢查
|
||||
|
||||
#### `GET /health` 或 `GET /api/v1/health`
|
||||
|
||||
檢查 API Server 運作狀態。不需要認證。
|
||||
|
||||
**回應**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"version": "0.1.0",
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 引擎管理
|
||||
|
||||
#### `GET /api/v1/engines`
|
||||
|
||||
列出所有可用的轉換引擎。
|
||||
|
||||
**Headers**
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**回應**
|
||||
|
||||
```json
|
||||
{
|
||||
"engines": [
|
||||
{
|
||||
"id": "ffmpeg",
|
||||
"name": "FFmpeg",
|
||||
"description": "Audio and video conversion using FFmpeg",
|
||||
"supported_input_formats": ["mp4", "webm", "avi", "mkv", "mov", "mp3", "wav", "flac", "ogg", "m4a", "gif"],
|
||||
"supported_output_formats": ["webm", "avi", "mkv", "mov", "mp3", "wav", "flac", "ogg", "gif", "m4a", "aac", "mp4"]
|
||||
},
|
||||
{
|
||||
"id": "imagemagick",
|
||||
"name": "ImageMagick",
|
||||
"description": "Image format conversion using ImageMagick",
|
||||
"supported_input_formats": ["png", "jpg", "jpeg", "gif", "bmp", "webp", "tiff", "svg"],
|
||||
"supported_output_formats": ["jpg", "jpeg", "gif", "bmp", "webp", "tiff", "ico", "pdf", "png"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `GET /api/v1/engines/:engine_id`
|
||||
|
||||
取得特定引擎的詳細資訊。
|
||||
|
||||
**參數**
|
||||
- `engine_id`: 引擎識別碼(如 `ffmpeg`, `imagemagick`)
|
||||
|
||||
**回應 (200)**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "ffmpeg",
|
||||
"name": "FFmpeg",
|
||||
"description": "Audio and video conversion using FFmpeg",
|
||||
"supported_input_formats": ["mp4", "webm", "avi"],
|
||||
"supported_output_formats": ["mp4", "webm", "mp3"]
|
||||
}
|
||||
```
|
||||
|
||||
**回應 (404)**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "ENGINE_NOT_FOUND",
|
||||
"message": "Engine not found: nonexistent"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `GET /api/v1/engines/:engine_id/conversions`
|
||||
|
||||
取得特定引擎支援的轉換對應表。
|
||||
|
||||
**回應**
|
||||
|
||||
```json
|
||||
{
|
||||
"engine_id": "ffmpeg",
|
||||
"conversions": {
|
||||
"mp4": ["webm", "avi", "mkv", "mov", "mp3", "wav", "flac", "ogg", "gif"],
|
||||
"webm": ["mp4", "avi", "mkv", "mov", "mp3", "wav", "flac", "ogg", "gif"],
|
||||
"mp3": ["wav", "flac", "ogg", "m4a", "aac"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 轉檔任務
|
||||
|
||||
#### `POST /api/v1/convert`
|
||||
|
||||
建立新的轉檔任務。
|
||||
|
||||
**Headers**
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
**表單欄位**
|
||||
|
||||
| 欄位 | 類型 | 必填 | 說明 |
|
||||
|------|------|------|------|
|
||||
| `file` | File | ✓ | 要轉換的檔案 |
|
||||
| `engine` | String | ✓ | 轉換引擎 ID |
|
||||
| `target_format` | String | ✓ | 目標格式(不含點) |
|
||||
| `options` | String | - | JSON 格式的轉換選項 |
|
||||
|
||||
**回應 (201)**
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"status": "pending",
|
||||
"message": "Conversion job created successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**回應 (422) - 不支援的轉換**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "UNSUPPORTED_CONVERSION",
|
||||
"message": "Unsupported conversion from pdf to mp4 using engine ffmpeg",
|
||||
"suggestions": [
|
||||
{
|
||||
"engine": "libreoffice",
|
||||
"from": "pdf",
|
||||
"to": "docx"
|
||||
},
|
||||
{
|
||||
"engine": "libreoffice",
|
||||
"from": "pdf",
|
||||
"to": "html"
|
||||
},
|
||||
{
|
||||
"engine": "calibre",
|
||||
"from": "pdf",
|
||||
"to": "epub"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `GET /api/v1/jobs`
|
||||
|
||||
列出當前使用者的所有任務。
|
||||
|
||||
**回應**
|
||||
|
||||
```json
|
||||
{
|
||||
"jobs": [
|
||||
{
|
||||
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"status": "completed",
|
||||
"original_filename": "video.mp4",
|
||||
"source_format": "mp4",
|
||||
"target_format": "webm",
|
||||
"engine": "ffmpeg",
|
||||
"download_url": "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000/download",
|
||||
"created_at": "2024-01-15T10:00:00Z",
|
||||
"completed_at": "2024-01-15T10:01:30Z"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `GET /api/v1/jobs/:job_id`
|
||||
|
||||
取得特定任務的狀態。
|
||||
|
||||
**任務狀態**
|
||||
|
||||
| 狀態 | 說明 |
|
||||
|------|------|
|
||||
| `pending` | 等待處理 |
|
||||
| `processing` | 處理中 |
|
||||
| `completed` | 完成 |
|
||||
| `failed` | 失敗 |
|
||||
|
||||
**回應 (200) - 完成**
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"status": "completed",
|
||||
"original_filename": "video.mp4",
|
||||
"source_format": "mp4",
|
||||
"target_format": "webm",
|
||||
"engine": "ffmpeg",
|
||||
"download_url": "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000/download",
|
||||
"created_at": "2024-01-15T10:00:00Z",
|
||||
"completed_at": "2024-01-15T10:01:30Z"
|
||||
}
|
||||
```
|
||||
|
||||
**回應 (200) - 失敗**
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"status": "failed",
|
||||
"original_filename": "corrupted.mp4",
|
||||
"source_format": "mp4",
|
||||
"target_format": "webm",
|
||||
"engine": "ffmpeg",
|
||||
"error_message": "Conversion failed: Invalid data found when processing input",
|
||||
"created_at": "2024-01-15T10:00:00Z",
|
||||
"completed_at": "2024-01-15T10:00:05Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `GET /api/v1/jobs/:job_id/download`
|
||||
|
||||
下載轉換後的檔案。
|
||||
|
||||
**前提條件**
|
||||
- 任務狀態必須是 `completed`
|
||||
- 只有任務建立者可以下載
|
||||
|
||||
**回應 Headers**
|
||||
```
|
||||
Content-Type: <mime-type>
|
||||
Content-Disposition: attachment; filename="output.webm"
|
||||
```
|
||||
|
||||
**錯誤回應**
|
||||
|
||||
| 狀況 | 錯誤碼 | HTTP 狀態 |
|
||||
|------|--------|-----------|
|
||||
| 任務未完成 | `BAD_REQUEST` | 400 |
|
||||
| 無權限 | `UNAUTHORIZED` | 401 |
|
||||
| 任務不存在 | `JOB_NOT_FOUND` | 404 |
|
||||
| 檔案遺失 | `FILE_NOT_FOUND` | 404 |
|
||||
|
||||
---
|
||||
|
||||
#### `DELETE /api/v1/jobs/:job_id`
|
||||
|
||||
刪除任務及相關檔案。
|
||||
|
||||
**回應 (200)**
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Job deleted successfully",
|
||||
"job_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GraphQL API 規格
|
||||
|
||||
### Endpoint
|
||||
|
||||
- **URL**: `http://localhost:3001/graphql`
|
||||
- **Method**: POST
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
GraphQL Playground 可透過 GET 請求訪問同一 URL。
|
||||
|
||||
### Schema
|
||||
|
||||
```graphql
|
||||
# ========== Queries ==========
|
||||
|
||||
type Query {
|
||||
"""健康檢查(不需認證)"""
|
||||
health: Health!
|
||||
|
||||
"""列出所有可用引擎"""
|
||||
engines: [Engine!]!
|
||||
|
||||
"""取得特定引擎"""
|
||||
engine(id: ID!): Engine
|
||||
|
||||
"""列出當前使用者的所有任務"""
|
||||
jobs: [Job!]!
|
||||
|
||||
"""取得特定任務"""
|
||||
job(id: ID!): Job
|
||||
|
||||
"""驗證轉換是否支援"""
|
||||
validateConversion(
|
||||
engine: String!
|
||||
from: String!
|
||||
to: String!
|
||||
): CreateJobResult!
|
||||
|
||||
"""取得轉換建議"""
|
||||
suggestions(from: String!, to: String!): [Suggestion!]!
|
||||
}
|
||||
|
||||
# ========== Mutations ==========
|
||||
|
||||
type Mutation {
|
||||
"""建立轉檔任務"""
|
||||
createJob(
|
||||
filename: String!
|
||||
fileBase64: String!
|
||||
input: CreateJobInput!
|
||||
): CreateJobResult!
|
||||
|
||||
"""刪除任務"""
|
||||
deleteJob(id: ID!): Boolean!
|
||||
}
|
||||
|
||||
# ========== Input Types ==========
|
||||
|
||||
input CreateJobInput {
|
||||
"""轉換引擎 ID"""
|
||||
engine: String!
|
||||
|
||||
"""目標格式"""
|
||||
targetFormat: String!
|
||||
|
||||
"""轉換選項(JSON 字串)"""
|
||||
options: String
|
||||
}
|
||||
|
||||
# ========== Object Types ==========
|
||||
|
||||
type Health {
|
||||
status: String!
|
||||
version: String!
|
||||
timestamp: DateTime!
|
||||
}
|
||||
|
||||
type Engine {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String!
|
||||
supportedInputFormats: [String!]!
|
||||
supportedOutputFormats: [String!]!
|
||||
}
|
||||
|
||||
type Job {
|
||||
id: ID!
|
||||
originalFilename: String!
|
||||
sourceFormat: String!
|
||||
targetFormat: String!
|
||||
engine: String!
|
||||
status: JobStatus!
|
||||
outputFilename: String
|
||||
errorMessage: String
|
||||
downloadUrl: String
|
||||
createdAt: DateTime!
|
||||
completedAt: DateTime
|
||||
}
|
||||
|
||||
type Suggestion {
|
||||
engine: String!
|
||||
from: String!
|
||||
to: String!
|
||||
}
|
||||
|
||||
type CreateJobResult {
|
||||
success: Boolean!
|
||||
job: Job
|
||||
error: ConversionError
|
||||
}
|
||||
|
||||
type ConversionError {
|
||||
code: String!
|
||||
message: String!
|
||||
suggestions: [Suggestion!]!
|
||||
}
|
||||
|
||||
# ========== Enums ==========
|
||||
|
||||
enum JobStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
COMPLETED
|
||||
FAILED
|
||||
}
|
||||
|
||||
# ========== Scalars ==========
|
||||
|
||||
scalar DateTime
|
||||
```
|
||||
|
||||
### 使用範例
|
||||
|
||||
#### 列出所有引擎
|
||||
|
||||
```graphql
|
||||
query {
|
||||
engines {
|
||||
id
|
||||
name
|
||||
description
|
||||
supportedInputFormats
|
||||
supportedOutputFormats
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 驗證轉換是否支援
|
||||
|
||||
```graphql
|
||||
query {
|
||||
validateConversion(engine: "ffmpeg", from: "mp4", to: "webm") {
|
||||
success
|
||||
error {
|
||||
code
|
||||
message
|
||||
suggestions {
|
||||
engine
|
||||
from
|
||||
to
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 取得轉換建議
|
||||
|
||||
```graphql
|
||||
query {
|
||||
suggestions(from: "pdf", to: "docx") {
|
||||
engine
|
||||
from
|
||||
to
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 建立轉檔任務
|
||||
|
||||
```graphql
|
||||
mutation CreateConversionJob($filename: String!, $content: String!) {
|
||||
createJob(
|
||||
filename: $filename
|
||||
fileBase64: $content
|
||||
input: {
|
||||
engine: "imagemagick"
|
||||
targetFormat: "jpg"
|
||||
}
|
||||
) {
|
||||
success
|
||||
job {
|
||||
id
|
||||
status
|
||||
originalFilename
|
||||
}
|
||||
error {
|
||||
code
|
||||
message
|
||||
suggestions {
|
||||
engine
|
||||
from
|
||||
to
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Variables:
|
||||
```json
|
||||
{
|
||||
"filename": "image.png",
|
||||
"content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
|
||||
}
|
||||
```
|
||||
|
||||
#### 查詢任務狀態
|
||||
|
||||
```graphql
|
||||
query {
|
||||
job(id: "550e8400-e29b-41d4-a716-446655440000") {
|
||||
id
|
||||
status
|
||||
originalFilename
|
||||
sourceFormat
|
||||
targetFormat
|
||||
engine
|
||||
downloadUrl
|
||||
errorMessage
|
||||
createdAt
|
||||
completedAt
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 列出所有任務
|
||||
|
||||
```graphql
|
||||
query {
|
||||
jobs {
|
||||
id
|
||||
status
|
||||
originalFilename
|
||||
targetFormat
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 刪除任務
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
deleteJob(id: "550e8400-e29b-41d4-a716-446655440000")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 錯誤碼對照表
|
||||
|
||||
| 錯誤碼 | HTTP 狀態 | 說明 | 包含建議 |
|
||||
|--------|-----------|------|----------|
|
||||
| `UNAUTHORIZED` | 401 | 未授權存取 | ✗ |
|
||||
| `INVALID_TOKEN` | 401 | Token 無效 | ✗ |
|
||||
| `TOKEN_EXPIRED` | 401 | Token 已過期 | ✗ |
|
||||
| `MISSING_AUTH_HEADER` | 401 | 缺少認證標頭 | ✗ |
|
||||
| `BAD_REQUEST` | 400 | 請求格式錯誤 | ✗ |
|
||||
| `INVALID_FILE` | 400 | 無法辨識檔案格式 | ✗ |
|
||||
| `FILE_TOO_LARGE` | 400 | 檔案超過大小限制 | ✗ |
|
||||
| `ENGINE_NOT_FOUND` | 404 | 引擎不存在 | ✗ |
|
||||
| `JOB_NOT_FOUND` | 404 | 任務不存在 | ✗ |
|
||||
| `FILE_NOT_FOUND` | 404 | 檔案不存在 | ✗ |
|
||||
| `UNSUPPORTED_CONVERSION` | 422 | 不支援的轉換 | ✓ |
|
||||
| `CONVERSION_FAILED` | 500 | 轉換失敗 | ✗ |
|
||||
| `INTERNAL_ERROR` | 500 | 內部錯誤 | ✗ |
|
||||
319
api-server/docs/ARCHITECTURE.md
Normal file
319
api-server/docs/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
# ConvertX API 架構說明
|
||||
|
||||
## 系統架構圖
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ API Server │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ Auth Layer │◄──── JWT Token 驗證 │
|
||||
│ │ (JWT) │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Request Router │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌──────────────────┐ │ │
|
||||
│ │ │ REST API │ │ GraphQL API │ │ │
|
||||
│ │ │ /api/v1/* │ │ /graphql │ │ │
|
||||
│ │ └──────┬──────┘ └────────┬─────────┘ │ │
|
||||
│ │ │ │ │ │
|
||||
│ └──────────┼─────────────────────────┼──────────────────┘ │
|
||||
│ │ │ │
|
||||
│ └────────────┬────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌───────────────────────┐ │
|
||||
│ │ Conversion Service │ │
|
||||
│ │ (Domain Logic) │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────────┐ │ │
|
||||
│ │ │ Engine Registry │ │ │
|
||||
│ │ └─────────────────┘ │ │
|
||||
│ └───────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌───────────────────────┐ │
|
||||
│ │ External Converters │ │
|
||||
│ │ (FFmpeg, etc.) │ │
|
||||
│ └───────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────┐
|
||||
│ File Storage │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ data/uploads/ │ │
|
||||
│ │ data/output/ │ │
|
||||
│ └─────────────────┘ │
|
||||
└───────────────────────┘
|
||||
```
|
||||
|
||||
## 模組說明
|
||||
|
||||
### 1. Auth Layer (`src/auth.rs`)
|
||||
|
||||
負責所有請求的 JWT 驗證。
|
||||
|
||||
**職責**:
|
||||
- 解析 Authorization Header
|
||||
- 驗證 JWT Token 簽名
|
||||
- 檢查 Token 是否過期
|
||||
- 提取使用者資訊
|
||||
|
||||
**不負責**:
|
||||
- 產生 JWT Token(由外部認證服務處理)
|
||||
- 使用者管理
|
||||
|
||||
### 2. REST API (`src/rest.rs`)
|
||||
|
||||
提供 RESTful HTTP 端點。
|
||||
|
||||
**端點**:
|
||||
- `GET /health` - 健康檢查
|
||||
- `GET /api/v1/engines` - 列出引擎
|
||||
- `POST /api/v1/convert` - 建立轉檔任務
|
||||
- `GET /api/v1/jobs` - 列出任務
|
||||
- `GET /api/v1/jobs/:id` - 取得任務狀態
|
||||
- `GET /api/v1/jobs/:id/download` - 下載結果
|
||||
- `DELETE /api/v1/jobs/:id` - 刪除任務
|
||||
|
||||
### 3. GraphQL API (`src/graphql.rs`)
|
||||
|
||||
提供 GraphQL 查詢與變更。
|
||||
|
||||
**Queries**:
|
||||
- `health` - 健康檢查
|
||||
- `engines` - 列出引擎
|
||||
- `engine(id)` - 取得特定引擎
|
||||
- `jobs` - 列出任務
|
||||
- `job(id)` - 取得特定任務
|
||||
- `validateConversion` - 驗證轉換
|
||||
- `suggestions` - 取得建議
|
||||
|
||||
**Mutations**:
|
||||
- `createJob` - 建立任務
|
||||
- `deleteJob` - 刪除任務
|
||||
|
||||
### 4. Engine Registry (`src/engine.rs`)
|
||||
|
||||
管理所有可用的轉換引擎及其能力。
|
||||
|
||||
**職責**:
|
||||
- 註冊引擎及其支援的格式
|
||||
- 驗證轉換是否可行
|
||||
- 提供替代方案建議
|
||||
|
||||
**設計原則**:
|
||||
- 引擎必須明確指定,不自動選擇
|
||||
- 不支援的轉換必須回傳建議
|
||||
|
||||
### 5. Conversion Service (`src/conversion.rs`)
|
||||
|
||||
處理轉檔任務的核心邏輯。
|
||||
|
||||
**職責**:
|
||||
- 建立和管理轉檔任務
|
||||
- 儲存上傳的檔案
|
||||
- 呼叫外部轉換程式
|
||||
- 管理輸出檔案
|
||||
|
||||
### 6. Error Handling (`src/error.rs`)
|
||||
|
||||
統一的錯誤處理機制。
|
||||
|
||||
**特色**:
|
||||
- 結構化的錯誤回應
|
||||
- 錯誤碼分類
|
||||
- 轉換建議整合
|
||||
|
||||
## 資料流程
|
||||
|
||||
### 轉檔請求流程
|
||||
|
||||
```
|
||||
Client Request
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ JWT Validation │ ──────► 401 Unauthorized
|
||||
└────────┬────────┘
|
||||
│ Valid
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Parse Request │ ──────► 400 Bad Request
|
||||
│ (file, engine, │
|
||||
│ target_format) │
|
||||
└────────┬────────┘
|
||||
│ Valid
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Validate │ ──────► 422 Unsupported
|
||||
│ Conversion │ (with suggestions)
|
||||
│ (Engine Registry)│
|
||||
└────────┬────────┘
|
||||
│ Supported
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Create Job │
|
||||
│ Save File │
|
||||
│ Start Conversion│
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 201 Created │
|
||||
│ Return Job ID │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### 轉換執行流程
|
||||
|
||||
```
|
||||
Background Task
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Update Status │
|
||||
│ → Processing │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Execute │
|
||||
│ Converter │
|
||||
│ Command │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────┐ ┌───────┐
|
||||
│Success│ │Failed │
|
||||
└───┬───┘ └───┬───┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────┐ ┌───────┐
|
||||
│Update │ │Update │
|
||||
│Status │ │Status │
|
||||
│=Done │ │=Failed│
|
||||
└───────┘ └───────┘
|
||||
```
|
||||
|
||||
## 設計決策
|
||||
|
||||
### 1. 為什麼使用 Rust?
|
||||
|
||||
- **效能**:處理大量並發請求時表現優異
|
||||
- **記憶體安全**:避免常見的記憶體問題
|
||||
- **非同步支援**:Tokio 生態系統成熟
|
||||
- **類型安全**:編譯時檢查減少運行時錯誤
|
||||
|
||||
### 2. 為什麼同時支援 REST 和 GraphQL?
|
||||
|
||||
- **REST**:簡單直觀,適合檔案上傳/下載
|
||||
- **GraphQL**:靈活查詢,減少 over-fetching
|
||||
- **獨立使用**:使用者可選擇適合的 API
|
||||
|
||||
### 3. 為什麼採用「結果僅透過 API 下載」策略?
|
||||
|
||||
- **安全性**:不暴露檔案系統路徑
|
||||
- **權限控制**:下載時驗證身份
|
||||
- **彈性部署**:易於整合 CDN 或雲端儲存
|
||||
- **可擴展性**:未來可改用分散式儲存
|
||||
|
||||
### 4. 為什麼引擎必須明確指定?
|
||||
|
||||
- **可預測性**:使用者明確知道使用什麼工具
|
||||
- **一致性**:避免因自動選擇導致的結果差異
|
||||
- **透明度**:錯誤時能明確指出是哪個引擎的問題
|
||||
|
||||
## 擴展指南
|
||||
|
||||
### 新增轉換引擎
|
||||
|
||||
1. 在 `src/engine.rs` 的 `register_default_engines()` 中新增:
|
||||
|
||||
```rust
|
||||
let new_engine = Engine::new(
|
||||
"engine_id",
|
||||
"Engine Name",
|
||||
"Engine description"
|
||||
)
|
||||
.add_conversion("input_format", vec!["output1", "output2"]);
|
||||
self.register(new_engine);
|
||||
```
|
||||
|
||||
2. 在 `src/conversion.rs` 的 `run_converter_command()` 中新增命令:
|
||||
|
||||
```rust
|
||||
"engine_id" => (
|
||||
"command",
|
||||
vec!["arg1".to_string(), input, output],
|
||||
),
|
||||
```
|
||||
|
||||
### 新增 REST 端點
|
||||
|
||||
在 `src/rest.rs` 中:
|
||||
|
||||
```rust
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/api/v1/new-endpoint", get(new_handler))
|
||||
// ...
|
||||
}
|
||||
|
||||
async fn new_handler(
|
||||
State(state): State<AppState>,
|
||||
RequireAuth(user): RequireAuth,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 新增 GraphQL 查詢/變更
|
||||
|
||||
在 `src/graphql.rs` 中:
|
||||
|
||||
```rust
|
||||
#[Object]
|
||||
impl QueryRoot {
|
||||
async fn new_query(&self, ctx: &Context<'_>) -> GqlResult<SomeType> {
|
||||
let _user = validate_auth(ctx)?;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl MutationRoot {
|
||||
async fn new_mutation(&self, ctx: &Context<'_>, input: Input) -> GqlResult<Output> {
|
||||
let user = validate_auth(ctx)?;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 效能考量
|
||||
|
||||
### 並發處理
|
||||
|
||||
- 使用 Tokio 非同步運行時
|
||||
- 轉換任務在背景執行
|
||||
- 任務狀態儲存在記憶體(生產環境應使用資料庫)
|
||||
|
||||
### 檔案處理
|
||||
|
||||
- 大檔案直接寫入磁碟,不佔用過多記憶體
|
||||
- 輸出檔案串流下載
|
||||
|
||||
### 未來優化方向
|
||||
|
||||
1. 使用 Redis 進行任務佇列
|
||||
2. 使用 PostgreSQL 進行任務持久化
|
||||
3. 實作任務優先級
|
||||
4. 新增任務取消功能
|
||||
5. 實作 Webhook 回呼
|
||||
Loading…
Add table
Add a link
Reference in a new issue