新增錯誤排查與支援文件,提供常見問題解決方案;新增開發與貢獻指南,說明專案結構與開發流程;新增授權說明文件,詳述AGPL-3.0授權條款及第三方元件使用情況。
This commit is contained in:
parent
11d751250b
commit
caecb2e001
13 changed files with 3269 additions and 369 deletions
547
docs/05-API文件.md
Normal file
547
docs/05-API文件.md
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
# API 文件
|
||||
|
||||
ConvertX-CN 提供選用的 API Server,支援 REST 和 GraphQL 兩種 API 介面。
|
||||
|
||||
---
|
||||
|
||||
## 目錄
|
||||
|
||||
- [快速啟用](#快速啟用)
|
||||
- [認證機制](#認證機制)
|
||||
- [REST API 端點](#rest-api-端點)
|
||||
- [GraphQL API](#graphql-api)
|
||||
- [錯誤碼說明](#錯誤碼說明)
|
||||
- [使用範例](#使用範例)
|
||||
|
||||
---
|
||||
|
||||
## 快速啟用
|
||||
|
||||
API Server 是**選用功能**,不影響 Web UI 使用。
|
||||
|
||||
### 啟用方式
|
||||
|
||||
```bash
|
||||
docker compose --profile api up -d
|
||||
```
|
||||
|
||||
### 服務端口
|
||||
|
||||
| 服務 | 端口 | 說明 |
|
||||
|------|------|------|
|
||||
| Web UI | 3000 | 網頁介面 |
|
||||
| API Server | 3001 | REST & GraphQL |
|
||||
|
||||
### 環境變數
|
||||
|
||||
| 變數 | 說明 | 預設值 |
|
||||
|------|------|--------|
|
||||
| `API_HOST` | 監聽地址 | `0.0.0.0` |
|
||||
| `API_PORT` | 監聽埠 | `3001` |
|
||||
| `JWT_SECRET` | JWT 驗證密鑰 | (需自行設定) |
|
||||
| `UPLOAD_DIR` | 上傳目錄 | `./data/uploads` |
|
||||
| `OUTPUT_DIR` | 輸出目錄 | `./data/output` |
|
||||
| `MAX_FILE_SIZE` | 最大檔案大小(bytes) | `104857600` |
|
||||
|
||||
---
|
||||
|
||||
## 認證機制
|
||||
|
||||
所有 API 請求(除健康檢查外)都需要 JWT Bearer Token:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
### Token 結構
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "user-id",
|
||||
"exp": 1234567890,
|
||||
"iat": 1234567890,
|
||||
"email": "user@example.com",
|
||||
"roles": ["user"]
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:API Server 只負責驗證 JWT,不負責產生 JWT。Token 應由獨立的認證服務產生。
|
||||
|
||||
---
|
||||
|
||||
## REST API 端點
|
||||
|
||||
**Base URL**: `http://localhost:3001/api/v1`
|
||||
|
||||
### 健康檢查
|
||||
|
||||
檢查 API Server 運行狀態。
|
||||
|
||||
**請求**:
|
||||
|
||||
```http
|
||||
GET /health
|
||||
```
|
||||
|
||||
**回應**:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"version": "0.1.0",
|
||||
"timestamp": "2026-01-25T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 取得支援格式
|
||||
|
||||
取得所有支援的輸入/輸出格式。
|
||||
|
||||
**請求**:
|
||||
|
||||
```http
|
||||
GET /api/v1/formats
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**回應**:
|
||||
|
||||
```json
|
||||
{
|
||||
"converters": [
|
||||
{
|
||||
"name": "ffmpeg",
|
||||
"inputFormats": ["mp4", "mkv", "avi", "..."],
|
||||
"outputFormats": ["mp4", "webm", "mp3", "..."]
|
||||
},
|
||||
{
|
||||
"name": "imagemagick",
|
||||
"inputFormats": ["png", "jpg", "heic", "..."],
|
||||
"outputFormats": ["png", "jpg", "webp", "..."]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 上傳檔案
|
||||
|
||||
上傳待轉換的檔案。
|
||||
|
||||
**請求**:
|
||||
|
||||
```http
|
||||
POST /api/v1/upload
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
file: <binary>
|
||||
```
|
||||
|
||||
**回應**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"fileId": "abc123",
|
||||
"filename": "document.docx",
|
||||
"size": 1048576,
|
||||
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 開始轉換
|
||||
|
||||
對已上傳的檔案進行轉換。
|
||||
|
||||
**請求**:
|
||||
|
||||
```http
|
||||
POST /api/v1/convert
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"fileId": "abc123",
|
||||
"outputFormat": "pdf",
|
||||
"options": {
|
||||
"quality": "high"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**回應**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"jobId": "job456",
|
||||
"status": "processing",
|
||||
"estimatedTime": 30
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 查詢轉換狀態
|
||||
|
||||
取得轉換任務的當前狀態。
|
||||
|
||||
**請求**:
|
||||
|
||||
```http
|
||||
GET /api/v1/jobs/{jobId}
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**回應(處理中)**:
|
||||
|
||||
```json
|
||||
{
|
||||
"jobId": "job456",
|
||||
"status": "processing",
|
||||
"progress": 45,
|
||||
"message": "Converting page 3 of 10..."
|
||||
}
|
||||
```
|
||||
|
||||
**回應(完成)**:
|
||||
|
||||
```json
|
||||
{
|
||||
"jobId": "job456",
|
||||
"status": "completed",
|
||||
"progress": 100,
|
||||
"result": {
|
||||
"fileId": "result789",
|
||||
"filename": "document.pdf",
|
||||
"size": 524288,
|
||||
"downloadUrl": "/api/v1/download/result789"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 下載結果
|
||||
|
||||
下載轉換完成的檔案。
|
||||
|
||||
**請求**:
|
||||
|
||||
```http
|
||||
GET /api/v1/download/{fileId}
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**回應**:
|
||||
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/pdf
|
||||
Content-Disposition: attachment; filename="document.pdf"
|
||||
|
||||
<binary content>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 刪除檔案
|
||||
|
||||
刪除已上傳或轉換完成的檔案。
|
||||
|
||||
**請求**:
|
||||
|
||||
```http
|
||||
DELETE /api/v1/files/{fileId}
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**回應**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "File deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GraphQL API
|
||||
|
||||
**Endpoint**: `http://localhost:3001/graphql`
|
||||
|
||||
### Schema 概覽
|
||||
|
||||
```graphql
|
||||
type Query {
|
||||
health: Health!
|
||||
formats: [Converter!]!
|
||||
job(id: ID!): Job
|
||||
jobs: [Job!]!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
upload(file: Upload!): UploadResult!
|
||||
convert(input: ConvertInput!): ConvertResult!
|
||||
deleteFile(fileId: ID!): DeleteResult!
|
||||
}
|
||||
|
||||
type Health {
|
||||
status: String!
|
||||
version: String!
|
||||
timestamp: String!
|
||||
}
|
||||
|
||||
type Converter {
|
||||
name: String!
|
||||
inputFormats: [String!]!
|
||||
outputFormats: [String!]!
|
||||
}
|
||||
|
||||
type Job {
|
||||
id: ID!
|
||||
status: JobStatus!
|
||||
progress: Int!
|
||||
message: String
|
||||
result: ConvertedFile
|
||||
}
|
||||
|
||||
enum JobStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
COMPLETED
|
||||
FAILED
|
||||
}
|
||||
```
|
||||
|
||||
### 查詢範例
|
||||
|
||||
**取得所有格式**:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
formats {
|
||||
name
|
||||
inputFormats
|
||||
outputFormats
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**查詢任務狀態**:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
job(id: "job456") {
|
||||
status
|
||||
progress
|
||||
message
|
||||
result {
|
||||
filename
|
||||
size
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 變更範例
|
||||
|
||||
**開始轉換**:
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
convert(input: {
|
||||
fileId: "abc123"
|
||||
outputFormat: "pdf"
|
||||
options: { quality: "high" }
|
||||
}) {
|
||||
jobId
|
||||
status
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 錯誤碼說明
|
||||
|
||||
### HTTP 狀態碼
|
||||
|
||||
| 狀態碼 | 說明 | 常見原因 |
|
||||
|--------|------|---------|
|
||||
| 200 | 成功 | 請求正常處理 |
|
||||
| 400 | 錯誤請求 | 參數錯誤、格式不支援 |
|
||||
| 401 | 未授權 | Token 無效或過期 |
|
||||
| 403 | 禁止存取 | 權限不足 |
|
||||
| 404 | 找不到 | 檔案或任務不存在 |
|
||||
| 413 | 檔案太大 | 超過上傳限制 |
|
||||
| 415 | 格式不支援 | 不支援的檔案類型 |
|
||||
| 500 | 伺服器錯誤 | 內部錯誤 |
|
||||
| 503 | 服務不可用 | 伺服器過載 |
|
||||
|
||||
### 錯誤回應格式
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "UNSUPPORTED_FORMAT",
|
||||
"message": "The format 'xyz' is not supported",
|
||||
"details": {
|
||||
"inputFormat": "xyz",
|
||||
"supportedFormats": ["pdf", "docx", "png"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 常見錯誤碼
|
||||
|
||||
| 錯誤碼 | 說明 | 解決方法 |
|
||||
|--------|------|---------|
|
||||
| `INVALID_TOKEN` | Token 無效 | 重新取得有效 Token |
|
||||
| `TOKEN_EXPIRED` | Token 過期 | 刷新 Token |
|
||||
| `FILE_NOT_FOUND` | 檔案不存在 | 確認檔案 ID 正確 |
|
||||
| `UNSUPPORTED_FORMAT` | 格式不支援 | 查看支援格式列表 |
|
||||
| `FILE_TOO_LARGE` | 檔案過大 | 壓縮或分割檔案 |
|
||||
| `CONVERSION_FAILED` | 轉換失敗 | 檢查檔案是否損壞 |
|
||||
| `RATE_LIMITED` | 請求過頻繁 | 降低請求頻率 |
|
||||
|
||||
---
|
||||
|
||||
## 使用範例
|
||||
|
||||
### cURL 範例
|
||||
|
||||
**上傳並轉換檔案**:
|
||||
|
||||
```bash
|
||||
# 1. 上傳檔案
|
||||
FILE_RESPONSE=$(curl -X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@document.docx" \
|
||||
http://localhost:3001/api/v1/upload)
|
||||
|
||||
FILE_ID=$(echo $FILE_RESPONSE | jq -r '.fileId')
|
||||
|
||||
# 2. 開始轉換
|
||||
JOB_RESPONSE=$(curl -X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"fileId\": \"$FILE_ID\", \"outputFormat\": \"pdf\"}" \
|
||||
http://localhost:3001/api/v1/convert)
|
||||
|
||||
JOB_ID=$(echo $JOB_RESPONSE | jq -r '.jobId')
|
||||
|
||||
# 3. 等待完成並下載
|
||||
sleep 10
|
||||
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
http://localhost:3001/api/v1/download/$FILE_ID \
|
||||
-o output.pdf
|
||||
```
|
||||
|
||||
### Python 範例
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:3001/api/v1"
|
||||
TOKEN = "your-jwt-token"
|
||||
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
|
||||
|
||||
# 上傳檔案
|
||||
with open("document.docx", "rb") as f:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/upload",
|
||||
headers=HEADERS,
|
||||
files={"file": f}
|
||||
)
|
||||
file_id = response.json()["fileId"]
|
||||
|
||||
# 開始轉換
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/convert",
|
||||
headers=HEADERS,
|
||||
json={"fileId": file_id, "outputFormat": "pdf"}
|
||||
)
|
||||
job_id = response.json()["jobId"]
|
||||
|
||||
# 輪詢狀態
|
||||
import time
|
||||
while True:
|
||||
response = requests.get(f"{BASE_URL}/jobs/{job_id}", headers=HEADERS)
|
||||
status = response.json()["status"]
|
||||
if status == "completed":
|
||||
break
|
||||
time.sleep(2)
|
||||
|
||||
# 下載結果
|
||||
result_id = response.json()["result"]["fileId"]
|
||||
response = requests.get(f"{BASE_URL}/download/{result_id}", headers=HEADERS)
|
||||
with open("output.pdf", "wb") as f:
|
||||
f.write(response.content)
|
||||
```
|
||||
|
||||
### JavaScript 範例
|
||||
|
||||
```javascript
|
||||
const BASE_URL = 'http://localhost:3001/api/v1';
|
||||
const TOKEN = 'your-jwt-token';
|
||||
|
||||
async function convertFile(file, outputFormat) {
|
||||
// 上傳檔案
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const uploadResponse = await fetch(`${BASE_URL}/upload`, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': `Bearer ${TOKEN}` },
|
||||
body: formData
|
||||
});
|
||||
const { fileId } = await uploadResponse.json();
|
||||
|
||||
// 開始轉換
|
||||
const convertResponse = await fetch(`${BASE_URL}/convert`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ fileId, outputFormat })
|
||||
});
|
||||
const { jobId } = await convertResponse.json();
|
||||
|
||||
// 輪詢狀態
|
||||
let result;
|
||||
while (true) {
|
||||
const statusResponse = await fetch(`${BASE_URL}/jobs/${jobId}`, {
|
||||
headers: { 'Authorization': `Bearer ${TOKEN}` }
|
||||
});
|
||||
const job = await statusResponse.json();
|
||||
if (job.status === 'completed') {
|
||||
result = job.result;
|
||||
break;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
// 下載結果
|
||||
const downloadResponse = await fetch(`${BASE_URL}/download/${result.fileId}`, {
|
||||
headers: { 'Authorization': `Bearer ${TOKEN}` }
|
||||
});
|
||||
return await downloadResponse.blob();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[⬆️ 回到頂部](#api-文件) | [📚 回到目錄](00-專案總覽.md)
|
||||
Loading…
Add table
Add a link
Reference in a new issue