Skip to content

Latest commit

 

History

History
3253 lines (2737 loc) · 85.3 KB

File metadata and controls

3253 lines (2737 loc) · 85.3 KB

🔌 VisionFlow API 增強版文檔

企業級 RESTful API 完整指南

API Version OpenAPI Authentication Status

🇹🇼 中文 | 🇺🇸 Enhanced


📋 API 概覽

VisionFlow 提供完整的 REST API 套件,支援攝影機管理、物件辨識和通知服務。本文檔詳述增強版 API 端點,包含改進的錯誤處理和安全功能。

🌐 服務端點

服務 基礎 URL 用途 狀態
🌐 Web Service http://localhost:5000 主要 API 服務 ✅ Active
📹 Camera Controller http://localhost:8000 攝影機控制服務 ✅ Active
🤖 Object Recognition Internal Service AI 辨識服務 🔒 Internal

🔐 認證系統

所有受保護的端點都需要 JWT 認證。請在請求標頭中包含令牌:

Authorization: Bearer <your-jwt-token>

🔑 支援的認證方式:

  • 🎯 JWT Bearer Token (主要方式)
  • 🔄 Refresh Token (令牌更新)
  • 🔐 API Key (服務間通信)

⚠️ 統一錯誤格式

所有 API 錯誤都遵循一致的回應格式:

{
    "success": false,
    "error": "Authentication",
    "message": "Invalid or expired token",
    "code": "AUTH_TOKEN_INVALID",
    "timestamp": "2024-01-20T17:45:00Z",
    "request_id": "req_abc123def456",
    "documentation_url": "https://docs.visionflow.com/errors/AUTH_TOKEN_INVALID"
}

🏷️ 錯誤類別:

  • Authentication - 認證相關錯誤
  • Validation - 資料驗證錯誤
  • Permission - 權限不足錯誤
  • Resource - 資源不存在錯誤
  • System - 系統內部錯誤

🔐 認證 API

安全的使用者認證與授權管理

🔑 使用者註冊

POST /auth/register

創建新的使用者帳戶,支援角色權限分配。

📋 請求參數:

{
    "username": "string",          // 3-20 字元,唯一
    "email": "string",             // 有效的電子郵件格式
    "password": "string",          // 最少 8 字元,需包含數字和字母
    "confirm_password": "string",  // 確認密碼需一致
    "role": "user"                 // 可選: user, admin, operator
}

✅ 成功回應:

{
    "success": true,
    "data": {
        "user_id": 1,
        "username": "example_user",
        "email": "user@example.com",
        "role": "user",
        "created_at": "2024-01-20T10:30:00Z",
        "profile": {
            "status": "active",
            "last_login": null
        }
    },
    "message": "使用者註冊成功"
}

❌ 錯誤代碼:

代碼 說明 HTTP 狀態
VALIDATION_ERROR 輸入資料格式錯誤 400
USER_EXISTS 使用者名稱或信箱已存在 409
PASSWORD_MISMATCH 密碼確認不一致 400
EMAIL_INVALID 電子郵件格式無效 400
PASSWORD_WEAK 密碼強度不足 400
🚪 使用者登入

POST /auth/login

驗證使用者憑證並取得存取令牌。

📋 請求參數:

{
    "username": "string",     // 使用者名稱或電子郵件
    "password": "string",     // 使用者密碼
    "remember_me": false      // 可選: 延長令牌有效期
}

✅ 成功回應:

{
    "success": true,
    "data": {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "refresh_token": "def123abc456...",
        "token_type": "Bearer",
        "expires_in": 3600,
        "user": {
            "id": 1,
            "username": "admin",
            "email": "admin@visionflow.com",
            "role": "admin",
            "permissions": ["camera:read", "camera:write", "user:manage"]
        }
    },
    "message": "登入成功"
}

❌ 錯誤代碼:

代碼 說明 HTTP 狀態
INVALID_CREDENTIALS 使用者名稱或密碼錯誤 401
ACCOUNT_LOCKED 帳戶已被鎖定 423
ACCOUNT_DISABLED 帳戶已停用 403
TOO_MANY_ATTEMPTS 登入嘗試次數過多 429
🔄 令牌管理

POST /auth/refresh

使用 Refresh Token 取得新的存取令牌。

📋 請求參數:

{
    "refresh_token": "string"
}

POST /auth/logout

登出並撤銷令牌。

🔒 Headers: Authorization: Bearer <token>

✅ 成功回應:

{
    "success": true,
    "message": "成功登出"
}

GET /auth/profile

取得當前使用者資料。

🔒 Headers: Authorization: Bearer <token>

✅ 成功回應:

{
    "success": true,
    "data": {
        "id": 1,
        "username": "admin",
        "email": "admin@visionflow.com",
        "role": "admin",
        "created_at": "2024-01-01T00:00:00Z",
        "last_login": "2024-01-20T10:30:00Z",
        "settings": {
            "language": "zh-TW",
            "timezone": "Asia/Taipei",
            "notifications": true
        }
    }
}

POST /auth/login

Authenticate and receive access token.

Request Body:

{
    "username": "string",
    "password": "string"
}

Response:

{
    "success": true,
    "data": {
        "access_token": "jwt-token-here",
        "user": {
            "id": 1,
            "username": "example_user",
            "email": "user@example.com"
        }
    },
    "expires_in": 3600
}

Error Codes:

  • INVALID_CREDENTIALS: Wrong username or password
  • ACCOUNT_DISABLED: User account is disabled

POST /auth/logout

Logout and invalidate token.

Headers: Authorization: Bearer <token>

Response:

{
    "success": true,
    "message": "Logged out successfully"
}

Camera Management

GET /camera/status

Get all camera status information.

Headers: Authorization: Bearer <token>

Response:

{
    "success": true,
    "data": {
        "camera_1": {
            "status": "active",
            "fps": 25.5,
            "last_frame": "2024-01-01T00:00:00Z",
            "resolution": "1920x1080"
        }
    },
    "count": 1
}

GET /camera/list

Get list of configured cameras.

Headers: Authorization: Bearer <token>

Query Parameters:

  • active_only (boolean): Only return active cameras
  • page (integer): Page number for pagination
  • limit (integer): Items per page

Response:

{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Front Door Camera",
            "url": "rtsp://camera1.local/stream",
            "status": "active",
            "created_at": "2024-01-01T00:00:00Z"
        }
    ],
    "pagination": {
        "page": 1,
        "limit": 10,
        "total": 1,
        "pages": 1
    }
}

📹 攝影機管理 API

完整的攝影機控制與監控系統

📊 攝影機狀態查詢

GET /camera/status

取得所有攝影機的即時狀態資訊。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
active_only boolean 僅顯示啟用的攝影機
include_stats boolean 包含詳細統計資料

✅ 成功回應:

{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "前門攝影機",
            "url": "rtsp://camera1.local/stream",
            "status": "active",
            "health": {
                "fps": 25.5,
                "resolution": "1920x1080",
                "latency": 120,
                "quality": "excellent"
            },
            "statistics": {
                "uptime": "99.8%",
                "total_detections": 1247,
                "last_detection": "2024-01-20T14:30:00Z"
            },
            "settings": {
                "auto_recording": true,
                "motion_detection": true,
                "audio_enabled": false
            }
        }
    ],
    "summary": {
        "total_cameras": 4,
        "active_cameras": 3,
        "total_uptime": "99.2%"
    }
}
➕ 新增攝影機

POST /camera/add

新增攝影機配置到系統中。

🔒 Headers: Authorization: Bearer <token>

📋 請求參數:

{
    "name": "string",              // 攝影機名稱(必填)
    "url": "string",               // RTSP/HTTP 串流 URL(必填)
    "description": "string",       // 攝影機描述(可選)
    "location": {                  // 位置資訊(可選)
        "building": "主辦公大樓",
        "floor": "1F",
        "area": "前門入口"
    },
    "settings": {
        "resolution": "1920x1080", // 解析度設定
        "fps": 30,                 // 影格率
        "codec": "h264",           // 編碼格式
        "quality": "high",         // 畫質設定
        "night_vision": true       // 夜視模式
    },
    "detection_config": {
        "enabled": true,
        "sensitivity": 0.7,
        "classes": ["person", "vehicle"],
        "alert_zones": []
    }
}

✅ 成功回應:

{
    "success": true,
    "data": {
        "id": 5,
        "name": "後門攝影機",
        "url": "rtsp://camera5.local/stream",
        "status": "connecting",
        "created_at": "2024-01-20T15:00:00Z",
        "test_result": {
            "connection": "success",
            "stream_quality": "good",
            "estimated_bandwidth": "2.5 Mbps"
        }
    },
    "message": "攝影機新增成功,正在建立連線"
}

❌ 錯誤代碼:

代碼 說明 HTTP 狀態
INVALID_URL 攝影機 URL 格式錯誤 400
DUPLICATE_CAMERA 此 URL 的攝影機已存在 409
CONNECTION_FAILED 無法連線到攝影機 422
UNSUPPORTED_CODEC 不支援的影像編碼 400
BANDWIDTH_EXCEEDED 頻寬使用超過限制 507
✏️ 更新攝影機設定

PUT /camera/{camera_id}

更新指定攝影機的配置設定。

🔒 Headers: Authorization: Bearer <token>

📋 請求參數:

{
    "name": "string",              // 攝影機名稱(可選)
    "url": "string",               // 串流 URL(可選)
    "description": "string",       // 描述(可選)
    "settings": {
        "resolution": "1920x1080",
        "fps": 25,
        "quality": "medium"
    },
    "detection_config": {
        "enabled": true,
        "sensitivity": 0.8,
        "classes": ["person", "vehicle", "package"]
    }
}

✅ 成功回應:

{
    "success": true,
    "data": {
        "id": 1,
        "name": "前門攝影機(已更新)",
        "changes_applied": [
            "resolution updated to 1920x1080",
            "detection sensitivity increased to 0.8",
            "added package detection class"
        ],
        "restart_required": false
    },
    "message": "攝影機設定更新成功"
}
🗑️ 移除攝影機

DELETE /camera/{camera_id}

從系統中移除攝影機配置。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
force boolean 強制刪除(即使攝影機正在錄影)
backup_data boolean 在刪除前備份歷史資料

✅ 成功回應:

{
    "success": true,
    "data": {
        "deleted_camera_id": 3,
        "backup_created": true,
        "backup_location": "/backups/camera_3_2024-01-20.zip",
        "cleanup_completed": true
    },
    "message": "攝影機已成功移除"
}
📸 即時快照與串流

GET /camera/{camera_id}/snapshot

取得攝影機的最新快照圖像。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
quality string 圖像品質:low, medium, high
format string 圖像格式:jpeg, png
width integer 圖像寬度(像素)
height integer 圖像高度(像素)

✅ 成功回應:

  • Content-Type: image/jpegimage/png
  • X-Frame-Timestamp: 2024-01-20T15:30:00Z
  • X-Camera-Status: active

GET /camera/{camera_id}/stream

取得攝影機的即時影像串流。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
format string 串流格式:mjpeg, webm, hls
quality string 串流品質:low, medium, high

✅ 成功回應:

  • Content-Type: multipart/x-mixed-replace (MJPEG)
  • X-Stream-Info: 包含串流的詳細資訊

❌ 錯誤代碼:

代碼 說明 HTTP 狀態
CAMERA_NOT_FOUND 攝影機 ID 不存在 404
NO_FRAME_AVAILABLE 無可用的影像框架 503
CAMERA_OFFLINE 攝影機離線中 503
STREAM_LIMIT_EXCEEDED 串流連線數超過限制 429

🎯 偵測區域管理 API

智慧偵測區域配置與多邊形管理

📍 查詢偵測區域

GET /camera/{camera_id}/areas

取得指定攝影機的所有偵測區域設定。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
active_only boolean 僅顯示啟用的偵測區域
include_stats boolean 包含偵測統計資料

✅ 成功回應:

{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "主要入口區域",
            "description": "監控主要出入口的人員活動",
            "type": "polygon",
            "points": [
                {"x": 100, "y": 100},
                {"x": 300, "y": 100},
                {"x": 300, "y": 250},
                {"x": 100, "y": 250}
            ],
            "properties": {
                "active": true,
                "sensitivity": 0.8,
                "min_object_size": 50,
                "detection_classes": ["person", "vehicle"],
                "color": "#FF0000",
                "line_width": 2
            },
            "statistics": {
                "total_detections": 156,
                "last_detection": "2024-01-20T14:45:00Z",
                "average_daily_detections": 12.3
            },
            "schedule": {
                "enabled": true,
                "time_ranges": [
                    {
                        "start": "08:00",
                        "end": "18:00",
                        "days": ["monday", "tuesday", "wednesday", "thursday", "friday"]
                    }
                ]
            }
        },
        {
            "id": 2,
            "name": "停車區監控",
            "description": "監控停車場車輛進出",
            "type": "polygon",
            "points": [
                {"x": 400, "y": 200},
                {"x": 600, "y": 200},
                {"x": 600, "y": 400},
                {"x": 400, "y": 400}
            ],
            "properties": {
                "active": true,
                "sensitivity": 0.6,
                "detection_classes": ["car", "truck", "motorcycle"],
                "color": "#00FF00"
            }
        }
    ],
    "camera_info": {
        "camera_id": 1,
        "resolution": "1920x1080",
        "total_areas": 2,
        "active_areas": 2
    }
}
➕ 建立偵測區域

POST /camera/{camera_id}/areas

為指定攝影機建立新的偵測區域。

🔒 Headers: Authorization: Bearer <token>

📋 請求參數:

{
    "name": "string",                    // 區域名稱(必填)
    "description": "string",             // 區域描述(可選)
    "type": "polygon",                   // 區域類型:polygon, rectangle, circle
    "points": [                          // 多邊形座標點(必填)
        {"x": 150, "y": 150},
        {"x": 250, "y": 150},
        {"x": 250, "y": 300},
        {"x": 150, "y": 300}
    ],
    "properties": {
        "active": true,                  // 是否啟用偵測
        "sensitivity": 0.7,              // 偵測靈敏度 (0.0-1.0)
        "min_object_size": 30,           // 最小物件大小(像素)
        "max_object_size": 500,          // 最大物件大小(像素)
        "detection_classes": [           // 偵測物件類別
            "person", 
            "vehicle", 
            "package"
        ],
        "visual": {
            "color": "#FF6B35",          // 區域邊框顏色
            "line_width": 3,             // 邊框寬度
            "fill_opacity": 0.2          // 填充透明度
        }
    },
    "alerts": {
        "enabled": true,                 // 啟用警報
        "cooldown": 300,                 // 警報冷卻時間(秒)
        "notification_types": [          // 通知類型
            "email", 
            "line", 
            "webhook"
        ]
    },
    "schedule": {                        // 偵測時間排程
        "enabled": true,
        "time_ranges": [
            {
                "start": "06:00",
                "end": "22:00",
                "days": ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
            }
        ],
        "timezone": "Asia/Taipei"
    }
}

✅ 成功回應:

{
    "success": true,
    "data": {
        "id": 3,
        "name": "新建偵測區域",
        "camera_id": 1,
        "status": "active",
        "created_at": "2024-01-20T16:00:00Z",
        "validation": {
            "geometry_valid": true,
            "coverage_percentage": 12.5,
            "overlaps_with": []
        }
    },
    "message": "偵測區域建立成功"
}

❌ 錯誤代碼:

代碼 說明 HTTP 狀態
INVALID_POLYGON 多邊形座標格式錯誤 400
AREA_TOO_SMALL 偵測區域過小 400
AREA_TOO_LARGE 偵測區域過大 400
OVERLAPPING_AREAS 與現有區域重疊過多 409
MAX_AREAS_EXCEEDED 超過最大區域數量限制 429
✏️ 更新偵測區域

PUT /camera/{camera_id}/areas/{area_id}

更新指定偵測區域的設定。

🔒 Headers: Authorization: Bearer <token>

📋 請求參數:

{
    "name": "更新的區域名稱",
    "properties": {
        "active": true,
        "sensitivity": 0.9,
        "detection_classes": ["person", "bicycle", "package"]
    },
    "schedule": {
        "enabled": true,
        "time_ranges": [
            {
                "start": "07:00",
                "end": "19:00",
                "days": ["monday", "tuesday", "wednesday", "thursday", "friday"]
            }
        ]
    }
}

✅ 成功回應:

{
    "success": true,
    "data": {
        "id": 1,
        "changes_applied": [
            "sensitivity updated to 0.9",
            "added bicycle detection class",
            "schedule time range updated"
        ],
        "restart_required": false,
        "estimated_impact": "improved detection accuracy by 15%"
    },
    "message": "偵測區域設定更新成功"
}
🗑️ 刪除偵測區域

DELETE /camera/{camera_id}/areas/{area_id}

刪除指定的偵測區域配置。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
backup_data boolean 在刪除前備份區域資料

✅ 成功回應:

{
    "success": true,
    "data": {
        "deleted_area_id": 2,
        "backup_created": true,
        "affected_schedules": 1,
        "cleanup_completed": true
    },
    "message": "偵測區域已成功刪除"
}
📊 區域偵測統計

GET /camera/{camera_id}/areas/{area_id}/statistics

取得偵測區域的詳細統計資料。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
period string 統計週期:hour, day, week, month
start_date string 開始日期 (ISO 8601)
end_date string 結束日期 (ISO 8601)

✅ 成功回應:

{
    "success": true,
    "data": {
        "area_info": {
            "id": 1,
            "name": "主要入口區域",
            "camera_id": 1
        },
        "statistics": {
            "total_detections": 1247,
            "detection_rate": 3.2,
            "accuracy": 0.94,
            "false_positives": 23,
            "object_breakdown": {
                "person": 1156,
                "vehicle": 68,
                "package": 23
            },
            "hourly_distribution": [
                {"hour": 8, "count": 45},
                {"hour": 9, "count": 78},
                {"hour": 10, "count": 92}
            ],
            "peak_detection_time": "09:30-10:30",
            "average_confidence": 0.87
        },
        "performance": {
            "detection_latency": "120ms",
            "processing_time": "45ms",
            "system_load": "12%"
        }
    }
}

🔔 通知系統 API

多元化通知管理與訊息分發系統

⚙️ 通知設定管理

GET /notification/settings

取得當前的通知系統設定。

🔒 Headers: Authorization: Bearer <token>

✅ 成功回應:

{
    "success": true,
    "data": {
        "general": {
            "enabled": true,
            "global_cooldown": 300,
            "max_notifications_per_hour": 50,
            "priority_override": true
        },
        "channels": {
            "email": {
                "enabled": true,
                "smtp_configured": true,
                "daily_limit": 100,
                "template": "professional"
            },
            "line": {
                "enabled": true,
                "tokens_configured": 3,
                "rate_limit": "30/minute"
            },
            "slack": {
                "enabled": false,
                "webhook_configured": false
            },
            "webhook": {
                "enabled": true,
                "endpoints": 2,
                "retry_attempts": 3
            }
        },
        "notification_schedule": {
            "enabled": true,
            "quiet_hours": {
                "start": "22:00",
                "end": "06:00",
                "timezone": "Asia/Taipei"
            },
            "active_days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
            "emergency_override": true
        },
        "filtering": {
            "min_confidence": 0.7,
            "detection_types": {
                "person": true,
                "vehicle": true,
                "package": false,
                "animal": false
            },
            "area_specific": true
        }
    }
}

PUT /notification/settings

更新通知系統設定。

🔒 Headers: Authorization: Bearer <token>

📋 請求參數:

{
    "general": {
        "enabled": true,
        "global_cooldown": 180,
        "max_notifications_per_hour": 75
    },
    "channels": {
        "email": {
            "enabled": true,
            "template": "minimal",
            "include_snapshots": true
        },
        "line": {
            "enabled": true,
            "message_format": "detailed"
        }
    },
    "notification_schedule": {
        "enabled": true,
        "quiet_hours": {
            "start": "23:00",
            "end": "07:00"
        },
        "active_days": ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]
    },
    "filtering": {
        "min_confidence": 0.8,
        "detection_types": {
            "person": true,
            "vehicle": true,
            "package": true
        }
    }
}

✅ 成功回應:

{
    "success": true,
    "data": {
        "updated_settings": [
            "global_cooldown reduced to 180 seconds",
            "email template changed to minimal",
            "added package detection notifications",
            "extended active days to include Saturday"
        ],
        "restart_required": false,
        "estimated_notification_increase": "15%"
    },
    "message": "通知設定更新成功"
}
📋 通知歷史查詢

GET /notification/history

查詢通知發送歷史記錄。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
start_date string 開始日期 (ISO 8601)
end_date string 結束日期 (ISO 8601)
type string 通知類型:email, line, slack, webhook
status string 發送狀態:sent, failed, pending
camera_id integer 特定攝影機 ID
page integer 頁碼(預設: 1)
limit integer 每頁筆數(預設: 20,最大: 100)

✅ 成功回應:

{
    "success": true,
    "data": [
        {
            "id": 1001,
            "type": "object_detected",
            "channel": "email",
            "camera_id": 1,
            "camera_name": "前門攝影機",
            "detection_info": {
                "object_class": "person",
                "confidence": 0.92,
                "detection_area": "主要入口區域",
                "snapshot_url": "https://api.visionflow.com/snapshots/1001.jpg"
            },
            "message": "在主要入口區域偵測到人員活動",
            "recipients": [
                {
                    "address": "security@company.com",
                    "status": "delivered",
                    "delivered_at": "2024-01-20T14:32:05Z"
                }
            ],
            "sent_at": "2024-01-20T14:32:00Z",
            "status": "delivered",
            "delivery_time": "4.2s",
            "metadata": {
                "priority": "normal",
                "retry_count": 0,
                "template_used": "detection_alert"
            }
        },
        {
            "id": 1002,
            "type": "system_alert",
            "channel": "line",
            "message": "攝影機 3 連線中斷",
            "status": "failed",
            "error": "LINE token expired",
            "sent_at": "2024-01-20T14:25:00Z",
            "retry_scheduled": "2024-01-20T14:35:00Z"
        }
    ],
    "pagination": {
        "page": 1,
        "limit": 20,
        "total": 156,
        "pages": 8,
        "has_next": true,
        "has_prev": false
    },
    "statistics": {
        "total_notifications": 156,
        "delivery_rate": 94.2,
        "average_delivery_time": "3.8s",
        "channel_breakdown": {
            "email": 89,
            "line": 45,
            "webhook": 22
        }
    }
}
📧 電子郵件收件人管理

GET /email/recipients

取得電子郵件收件人清單。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
active_only boolean 僅顯示啟用的收件人
group string 依群組篩選

✅ 成功回應:

{
    "success": true,
    "data": [
        {
            "id": 1,
            "email": "security@company.com",
            "name": "安全管理員",
            "active": true,
            "group": "security",
            "notification_types": ["detection", "system_alert", "maintenance"],
            "schedule": {
                "enabled": true,
                "time_ranges": [
                    {
                        "start": "08:00",
                        "end": "18:00",
                        "days": ["monday", "tuesday", "wednesday", "thursday", "friday"]
                    }
                ]
            },
            "statistics": {
                "total_sent": 245,
                "delivery_rate": 98.4,
                "last_sent": "2024-01-20T14:30:00Z"
            },
            "created_at": "2024-01-01T00:00:00Z"
        }
    ],
    "summary": {
        "total_recipients": 8,
        "active_recipients": 6,
        "groups": ["security", "management", "it"]
    }
}

POST /email/recipients

新增電子郵件收件人。

🔒 Headers: Authorization: Bearer <token>

📋 請求參數:

{
    "email": "manager@company.com",      // 電子郵件地址(必填)
    "name": "部門主管",                   // 收件人姓名(必填)
    "group": "management",               // 群組分類(可選)
    "active": true,                      // 是否啟用(預設: true)
    "notification_types": [              // 接收的通知類型
        "detection",
        "system_alert"
    ],
    "schedule": {                        // 接收時間排程
        "enabled": true,
        "time_ranges": [
            {
                "start": "09:00",
                "end": "17:00",
                "days": ["monday", "tuesday", "wednesday", "thursday", "friday"]
            }
        ],
        "timezone": "Asia/Taipei"
    },
    "preferences": {
        "include_snapshots": true,
        "digest_mode": false,
        "priority_only": false
    }
}

PUT /email/recipients/{recipient_id}

更新收件人設定。

DELETE /email/recipients/{recipient_id}

刪除收件人。

📱 LINE 通知管理

GET /line/tokens

取得 LINE Notify 令牌清單。

🔒 Headers: Authorization: Bearer <token>

✅ 成功回應:

{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "安全群組",
            "token": "***************abc123",  // 部分遮蔽的令牌
            "description": "主要安全人員通知群組",
            "active": true,
            "target_type": "group",
            "last_used": "2024-01-20T14:30:00Z",
            "statistics": {
                "total_sent": 89,
                "success_rate": 100,
                "last_error": null
            },
            "created_at": "2024-01-01T00:00:00Z"
        }
    ],
    "summary": {
        "total_tokens": 3,
        "active_tokens": 3,
        "daily_usage": 23
    }
}

POST /line/tokens

新增 LINE Notify 令牌。

🔒 Headers: Authorization: Bearer <token>

📋 請求參數:

{
    "token": "abcdef123456789...",        // LINE Notify 令牌(必填)
    "name": "IT 部門群組",                // 令牌名稱(必填)
    "description": "IT 部門系統通知",      // 描述(可選)
    "target_type": "group",              // 目標類型:user, group
    "notification_types": [              // 接收的通知類型
        "system_alert",
        "maintenance",
        "detection"
    ],
    "schedule": {
        "enabled": true,
        "quiet_hours": {
            "start": "22:00",
            "end": "08:00"
        }
    }
}

✅ 成功回應:

{
    "success": true,
    "data": {
        "id": 4,
        "name": "IT 部門群組",
        "token_preview": "***************789",
        "validation": {
            "token_valid": true,
            "target_accessible": true,
            "rate_limit": "1000/hour"
        },
        "test_message_sent": true
    },
    "message": "LINE 令牌新增成功,測試訊息已發送"
}

PUT /line/tokens/{token_id}

更新 LINE 令牌設定。

DELETE /line/tokens/{token_id}

刪除 LINE 令牌。


🏥 健康檢查與監控 API

系統健康狀態監控與效能診斷

💊 基礎健康檢查

GET /health

基礎系統健康檢查端點(無需認證)。

🔓 無需認證

✅ 成功回應:

{
    "success": true,
    "data": {
        "status": "healthy",
        "timestamp": "2024-01-20T16:45:00Z",
        "uptime": "7 days, 14 hours, 23 minutes",
        "version": "2.1.0",
        "environment": "production",
        "services": {
            "database": {
                "status": "healthy",
                "response_time": "12ms",
                "connections": "5/50"
            },
            "redis": {
                "status": "healthy",
                "response_time": "3ms",
                "memory_usage": "245MB"
            },
            "camera_controller": {
                "status": "healthy",
                "active_streams": 4,
                "processing_load": "23%"
            },
            "object_recognition": {
                "status": "healthy",
                "model_loaded": true,
                "gpu_usage": "67%",
                "queue_size": 0
            },
            "notification_service": {
                "status": "healthy",
                "pending_notifications": 0,
                "delivery_rate": "98.5%"
            }
        },
        "quick_stats": {
            "total_cameras": 4,
            "active_cameras": 4,
            "detections_today": 147,
            "system_load": "low"
        }
    }
}

⚠️ 部分服務異常回應:

{
    "success": true,
    "data": {
        "status": "degraded",
        "timestamp": "2024-01-20T16:45:00Z",
        "services": {
            "database": {
                "status": "healthy",
                "response_time": "15ms"
            },
            "redis": {
                "status": "warning",
                "response_time": "45ms",
                "issue": "high memory usage",
                "memory_usage": "890MB"
            },
            "camera_controller": {
                "status": "error",
                "error": "Camera 3 connection lost",
                "active_streams": 3,
                "failed_streams": 1
            }
        },
        "alerts": [
            {
                "level": "warning",
                "service": "redis",
                "message": "Memory usage approaching limit"
            },
            {
                "level": "error",
                "service": "camera_controller",
                "message": "Camera 3 offline for 5 minutes"
            }
        ]
    }
}
🔬 詳細健康檢查

GET /health/detailed

取得系統的詳細健康狀態資訊。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
include_metrics boolean 包含效能指標
include_logs boolean 包含最近錯誤日誌

✅ 成功回應:

{
    "success": true,
    "data": {
        "system": {
            "status": "healthy",
            "uptime": 604823,
            "boot_time": "2024-01-13T02:22:00Z",
            "load_average": [0.23, 0.25, 0.28],
            "cpu_count": 8,
            "architecture": "x86_64"
        },
        "resources": {
            "memory": {
                "total": "16GB",
                "used": "8.2GB",
                "free": "7.8GB",
                "percentage": 51.2,
                "swap_used": "0MB"
            },
            "disk": {
                "total": "500GB",
                "used": "125GB",
                "free": "375GB",
                "percentage": 25.0,
                "mount_point": "/"
            },
            "network": {
                "interfaces": {
                    "eth0": {
                        "status": "up",
                        "ip": "192.168.1.100",
                        "rx_bytes": "2.3TB",
                        "tx_bytes": "1.8TB"
                    }
                },
                "active_connections": 28
            }
        },
        "services_detailed": {
            "web_server": {
                "status": "healthy",
                "process_id": 1234,
                "memory_usage": "256MB",
                "cpu_usage": "12%",
                "active_requests": 3,
                "total_requests": 89247,
                "average_response_time": "45ms"
            },
            "database": {
                "status": "healthy",
                "version": "PostgreSQL 14.9",
                "connections": {
                    "active": 5,
                    "idle": 15,
                    "max": 50
                },
                "performance": {
                    "queries_per_second": 23.5,
                    "slow_queries": 0,
                    "cache_hit_ratio": 0.94
                },
                "storage": {
                    "size": "2.3GB",
                    "tables": 15,
                    "indexes": 28
                }
            },
            "redis": {
                "status": "healthy",
                "version": "7.0.5",
                "memory_usage": "245MB",
                "memory_peak": "312MB",
                "keys": 1247,
                "operations_per_second": 156,
                "keyspace_hits": 98.7
            },
            "ai_service": {
                "status": "healthy",
                "model_info": {
                    "name": "YOLOv8n",
                    "version": "8.0.196",
                    "loaded_at": "2024-01-20T08:30:00Z"
                },
                "gpu": {
                    "device": "NVIDIA RTX 4090",
                    "memory_used": "6.2GB",
                    "memory_total": "24GB",
                    "utilization": 67,
                    "temperature": "72°C"
                },
                "processing": {
                    "frames_processed": 234567,
                    "average_fps": 28.5,
                    "queue_size": 0,
                    "inference_time": "34ms"
                }
            }
        },
        "cameras": {
            "total": 4,
            "active": 4,
            "status_breakdown": {
                "healthy": 3,
                "warning": 1,
                "error": 0
            },
            "performance": {
                "total_fps": 98.5,
                "average_latency": "125ms",
                "dropped_frames": 0.02
            }
        },
        "recent_errors": [
            {
                "timestamp": "2024-01-20T15:30:00Z",
                "level": "warning",
                "service": "camera_controller",
                "message": "Camera 2 frame rate dropped to 20 FPS",
                "resolved": true
            }
        ],
        "alerts": {
            "active": 0,
            "last_24h": 3,
            "severity_breakdown": {
                "critical": 0,
                "warning": 0,
                "info": 0
            }
        }
    }
}
📊 系統效能指標

GET /health/metrics

取得系統效能指標和統計資料。

🔒 Headers: Authorization: Bearer <token>

🔍 查詢參數:

參數 類型 必填 說明
period string 統計週期:1h, 6h, 24h, 7d
format string 回應格式:json, prometheus

✅ 成功回應:

{
    "success": true,
    "data": {
        "collection_time": "2024-01-20T16:45:00Z",
        "period": "24h",
        "system_metrics": {
            "cpu": {
                "average_usage": 23.5,
                "peak_usage": 67.2,
                "cores": [
                    {"core": 0, "usage": 25.3},
                    {"core": 1, "usage": 21.7}
                ]
            },
            "memory": {
                "average_usage": 52.1,
                "peak_usage": 78.9,
                "swap_usage": 0.0
            },
            "disk": {
                "read_iops": 125.3,
                "write_iops": 89.7,
                "read_throughput": "15.2 MB/s",
                "write_throughput": "8.9 MB/s"
            },
            "network": {
                "rx_bandwidth": "50.2 Mbps",
                "tx_bandwidth": "32.1 Mbps",
                "packet_loss": 0.01,
                "latency": "1.2ms"
            }
        },
        "application_metrics": {
            "api": {
                "total_requests": 89247,
                "requests_per_minute": 156.7,
                "average_response_time": "45ms",
                "error_rate": 0.02,
                "status_codes": {
                    "200": 87456,
                    "400": 789,
                    "401": 234,
                    "500": 12
                }
            },
            "detections": {
                "total_objects": 5678,
                "objects_per_hour": 236.6,
                "confidence_distribution": {
                    "high (>0.9)": 4234,
                    "medium (0.7-0.9)": 1234,
                    "low (<0.7)": 210
                },
                "class_breakdown": {
                    "person": 4567,
                    "vehicle": 890,
                    "package": 123,
                    "animal": 98
                }
            },
            "notifications": {
                "total_sent": 234,
                "success_rate": 98.3,
                "channel_breakdown": {
                    "email": 145,
                    "line": 67,
                    "webhook": 22
                },
                "average_delivery_time": "3.8s"
            }
        },
        "camera_metrics": [
            {
                "camera_id": 1,
                "name": "前門攝影機",
                "fps": 25.2,
                "resolution": "1920x1080",
                "bitrate": "2.3 Mbps",
                "dropped_frames": 0.01,
                "latency": "120ms",
                "detections": 89,
                "uptime": 99.8
            }
        ]
    }
}
⚡ 即時系統狀態

GET /health/realtime

取得即時系統狀態資訊(適用於監控儀表板)。

🔒 Headers: Authorization: Bearer <token>

✅ 成功回應:

{
    "success": true,
    "data": {
        "timestamp": "2024-01-20T16:45:23Z",
        "overall_status": "healthy",
        "live_metrics": {
            "cpu_usage": 24.7,
            "memory_usage": 52.3,
            "active_cameras": 4,
            "total_fps": 98.2,
            "detection_rate": 3.2,
            "notification_queue": 0,
            "api_response_time": "42ms"
        },
        "service_status": {
            "web_service": "✅ healthy",
            "database": "✅ healthy", 
            "redis": "✅ healthy",
            "ai_service": "✅ healthy",
            "camera_controller": "⚠️ degraded",
            "notification_service": "✅ healthy"
        },
        "recent_activity": [
            {
                "time": "16:45:20",
                "event": "Person detected",
                "camera": "前門攝影機",
                "confidence": 0.94
            },
            {
                "time": "16:45:15",
                "event": "Email sent",
                "recipient": "security@company.com",
                "status": "delivered"
            }
        ],
        "alerts": [
            {
                "level": "warning",
                "message": "Camera 3 frame rate below threshold",
                "since": "16:40:00"
            }
        ]
    }
}

⚡ 速率限制與配額

API 呼叫頻率限制:

端點類別 限制 時間窗口 超出處理
🔐 認證端點 5 requests per minute 429 Too Many Requests
📹 攝影機管理 30 requests per minute Rate limit header 提示
🎯 偵測區域 20 requests per minute 自動延遲重試
🔔 通知端點 60 requests per minute 佇列處理
📸 串流端點 無限制 - 頻寬管理
🏥 健康檢查 100 requests per minute 無限制

⚠️ 超出限制回應:

{
    "success": false,
    "error": "RateLimit",
    "message": "API rate limit exceeded",
    "code": "RATE_LIMIT_EXCEEDED",
    "details": {
        "limit": 30,
        "period": "minute",
        "reset_at": "2024-01-20T17:01:00Z",
        "retry_after": 15
    }
}

🔄 WebSocket 即時事件

即時雙向通訊與事件推送系統

🔌 WebSocket 連線

建立連線

使用 JWT 令牌建立 WebSocket 連線以接收即時事件。

連線 URL:

ws://localhost:5000/ws?token=<your-jwt-token>

JavaScript 範例:

const token = 'your-jwt-token-here';
const ws = new WebSocket(`ws://localhost:5000/ws?token=${token}`);

ws.onopen = function(event) {
    console.log('✅ WebSocket 連線已建立');
    
    // 發送訂閱請求
    ws.send(JSON.stringify({
        action: 'subscribe',
        events: ['camera_status', 'object_detection', 'system_alerts']
    }));
};

ws.onmessage = function(event) {
    const data = JSON.parse(event.data);
    console.log('📨 收到事件:', data);
    handleRealtimeEvent(data);
};

ws.onclose = function(event) {
    console.log('❌ WebSocket 連線已關閉:', event.code, event.reason);
    if (event.code !== 1000) {
        // 異常關閉,嘗試重新連線
        setTimeout(reconnectWebSocket, 3000);
    }
};

ws.onerror = function(error) {
    console.error('🔥 WebSocket 錯誤:', error);
};

連線狀態管理:

class VisionFlowWebSocket {
    constructor(token) {
        this.token = token;
        this.ws = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
        this.reconnectInterval = 3000;
        this.subscriptions = [];
    }
    
    connect() {
        try {
            this.ws = new WebSocket(`ws://localhost:5000/ws?token=${this.token}`);
            this.setupEventHandlers();
        } catch (error) {
            console.error('WebSocket 連線失敗:', error);
            this.scheduleReconnect();
        }
    }
    
    setupEventHandlers() {
        this.ws.onopen = () => {
            console.log('✅ WebSocket 已連線');
            this.reconnectAttempts = 0;
            this.resubscribe();
        };
        
        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.handleEvent(data);
        };
        
        this.ws.onclose = (event) => {
            console.log('❌ WebSocket 已斷線');
            if (event.code !== 1000) {
                this.scheduleReconnect();
            }
        };
    }
    
    subscribe(events) {
        this.subscriptions = events;
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify({
                action: 'subscribe',
                events: events
            }));
        }
    }
    
    scheduleReconnect() {
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
            this.reconnectAttempts++;
            setTimeout(() => this.connect(), this.reconnectInterval);
        }
    }
}
📡 支援的事件類型

camera_status_update

攝影機狀態變更時觸發。

事件結構:

{
    "event": "camera_status_update",
    "timestamp": "2024-01-20T17:00:00Z",
    "data": {
        "camera_id": 1,
        "camera_name": "前門攝影機",
        "previous_status": "inactive",
        "current_status": "active",
        "health_metrics": {
            "fps": 25.5,
            "resolution": "1920x1080",
            "latency": 120,
            "quality": "excellent"
        },
        "error_info": null
    }
}

object_detected

物件偵測事件觸發。

事件結構:

{
    "event": "object_detected",
    "timestamp": "2024-01-20T17:00:15Z",
    "data": {
        "detection_id": "det_abc123",
        "camera_id": 1,
        "camera_name": "前門攝影機",
        "detection_area": {
            "id": 1,
            "name": "主要入口區域"
        },
        "objects": [
            {
                "id": "obj_001",
                "class": "person",
                "confidence": 0.95,
                "bbox": {
                    "x": 100,
                    "y": 100,
                    "width": 80,
                    "height": 200
                },
                "attributes": {
                    "age_group": "adult",
                    "gender": "unknown",
                    "clothing_color": "blue"
                }
            }
        ],
        "frame_info": {
            "frame_id": "frame_789",
            "timestamp": "2024-01-20T17:00:15.123Z",
            "size": "1920x1080"
        },
        "snapshot_url": "https://api.visionflow.com/snapshots/det_abc123.jpg",
        "notification_sent": true
    }
}

system_alert

系統警報事件。

事件結構:

{
    "event": "system_alert",
    "timestamp": "2024-01-20T17:05:00Z",
    "data": {
        "alert_id": "alert_456",
        "level": "warning",
        "category": "performance",
        "source": "camera_controller",
        "title": "攝影機效能警告",
        "message": "攝影機 3 的影格率降至 15 FPS,低於設定閾值",
        "details": {
            "camera_id": 3,
            "metric": "fps",
            "current_value": 15,
            "threshold": 20,
            "duration": "5 minutes"
        },
        "suggested_actions": [
            "檢查網路連線品質",
            "重新啟動攝影機串流",
            "檢查攝影機硬體狀態"
        ],
        "auto_resolved": false
    }
}

notification_update

通知發送狀態更新。

事件結構:

{
    "event": "notification_update",
    "timestamp": "2024-01-20T17:00:20Z",
    "data": {
        "notification_id": 1001,
        "type": "object_detected",
        "channel": "email",
        "status": "delivered",
        "recipient": "security@company.com",
        "delivery_time": "4.2s",
        "retry_count": 0,
        "related_detection": "det_abc123"
    }
}

recording_status

錄影狀態變更事件。

事件結構:

{
    "event": "recording_status",
    "timestamp": "2024-01-20T17:10:00Z",
    "data": {
        "camera_id": 1,
        "recording_id": "rec_789",
        "status": "started",
        "trigger": "motion_detected",
        "estimated_duration": 300,
        "storage_path": "/recordings/2024/01/20/camera_1_17-10-00.mp4",
        "quality": "high"
    }
}
🎛️ 事件訂閱管理

訂閱特定事件

// 訂閱所有事件
ws.send(JSON.stringify({
    action: 'subscribe',
    events: ['*']
}));

// 訂閱特定事件
ws.send(JSON.stringify({
    action: 'subscribe',
    events: [
        'camera_status_update',
        'object_detected',
        'system_alert'
    ]
}));

// 訂閱特定攝影機的事件
ws.send(JSON.stringify({
    action: 'subscribe',
    events: ['object_detected'],
    filters: {
        camera_ids: [1, 2, 3]
    }
}));

取消訂閱

ws.send(JSON.stringify({
    action: 'unsubscribe',
    events: ['system_alert']
}));

查詢訂閱狀態

ws.send(JSON.stringify({
    action: 'get_subscriptions'
}));

// 回應
{
    "action": "subscriptions_info",
    "data": {
        "active_subscriptions": [
            "camera_status_update",
            "object_detected"
        ],
        "filters": {
            "camera_ids": [1, 2]
        }
    }
}

💻 SDK 與程式碼範例

多語言 SDK 與實用程式碼範例

🐍 Python SDK

完整 Python 客戶端

import requests
import websocket
import json
import threading
from typing import Dict, List, Optional, Callable
from datetime import datetime

class VisionFlowClient:
    """VisionFlow API Python 客戶端"""
    
    def __init__(self, base_url: str, token: str):
        self.base_url = base_url.rstrip('/')
        self.token = token
        self.headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }
        self.ws = None
        self.event_handlers = {}
    
    def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict:
        """發送 HTTP 請求的通用方法"""
        url = f"{self.base_url}{endpoint}"
        response = requests.request(method, url, headers=self.headers, **kwargs)
        
        if response.status_code == 429:
            raise Exception(f"Rate limit exceeded. Retry after: {response.headers.get('Retry-After')}s")
        
        response.raise_for_status()
        return response.json()
    
    # 認證相關方法
    def get_profile(self) -> Dict:
        """取得使用者資料"""
        return self._make_request('GET', '/auth/profile')
    
    def refresh_token(self, refresh_token: str) -> Dict:
        """更新存取令牌"""
        return self._make_request('POST', '/auth/refresh', 
                                json={'refresh_token': refresh_token})
    
    # 攝影機管理
    def get_camera_status(self, active_only: bool = False) -> Dict:
        """取得攝影機狀態"""
        params = {'active_only': active_only} if active_only else {}
        return self._make_request('GET', '/camera/status', params=params)
    
    def add_camera(self, name: str, url: str, **kwargs) -> Dict:
        """新增攝影機"""
        data = {'name': name, 'url': url, **kwargs}
        return self._make_request('POST', '/camera/add', json=data)
    
    def update_camera(self, camera_id: int, **kwargs) -> Dict:
        """更新攝影機設定"""
        return self._make_request('PUT', f'/camera/{camera_id}', json=kwargs)
    
    def delete_camera(self, camera_id: int, force: bool = False) -> Dict:
        """刪除攝影機"""
        params = {'force': force} if force else {}
        return self._make_request('DELETE', f'/camera/{camera_id}', params=params)
    
    def get_camera_snapshot(self, camera_id: int, quality: str = 'high') -> bytes:
        """取得攝影機快照"""
        params = {'quality': quality}
        response = requests.get(
            f"{self.base_url}/camera/{camera_id}/snapshot",
            headers=self.headers,
            params=params
        )
        response.raise_for_status()
        return response.content
    
    # 偵測區域管理
    def get_detection_areas(self, camera_id: int) -> Dict:
        """取得偵測區域"""
        return self._make_request('GET', f'/camera/{camera_id}/areas')
    
    def create_detection_area(self, camera_id: int, name: str, points: List[Dict], **kwargs) -> Dict:
        """建立偵測區域"""
        data = {'name': name, 'points': points, **kwargs}
        return self._make_request('POST', f'/camera/{camera_id}/areas', json=data)
    
    def update_detection_area(self, camera_id: int, area_id: int, **kwargs) -> Dict:
        """更新偵測區域"""
        return self._make_request('PUT', f'/camera/{camera_id}/areas/{area_id}', json=kwargs)
    
    def delete_detection_area(self, camera_id: int, area_id: int) -> Dict:
        """刪除偵測區域"""
        return self._make_request('DELETE', f'/camera/{camera_id}/areas/{area_id}')
    
    # 通知管理
    def get_notification_settings(self) -> Dict:
        """取得通知設定"""
        return self._make_request('GET', '/notification/settings')
    
    def update_notification_settings(self, **kwargs) -> Dict:
        """更新通知設定"""
        return self._make_request('PUT', '/notification/settings', json=kwargs)
    
    def get_notification_history(self, **kwargs) -> Dict:
        """取得通知歷史"""
        return self._make_request('GET', '/notification/history', params=kwargs)
    
    # 健康檢查
    def get_system_health(self, detailed: bool = False) -> Dict:
        """取得系統健康狀態"""
        endpoint = '/health/detailed' if detailed else '/health'
        return self._make_request('GET', endpoint)
    
    # WebSocket 相關方法
    def connect_websocket(self, event_handlers: Dict[str, Callable] = None):
        """建立 WebSocket 連線"""
        if event_handlers:
            self.event_handlers = event_handlers
        
        ws_url = f"ws://{self.base_url.replace('http://', '').replace('https://', '')}/ws?token={self.token}"
        
        def on_message(ws, message):
            data = json.loads(message)
            event_type = data.get('event')
            if event_type in self.event_handlers:
                self.event_handlers[event_type](data)
        
        def on_error(ws, error):
            print(f"WebSocket 錯誤: {error}")
        
        def on_close(ws, close_status_code, close_msg):
            print("WebSocket 連線已關閉")
        
        def on_open(ws):
            print("WebSocket 連線已建立")
        
        self.ws = websocket.WebSocketApp(
            ws_url,
            on_open=on_open,
            on_message=on_message,
            on_error=on_error,
            on_close=on_close
        )
        
        # 在背景執行緒中運行
        ws_thread = threading.Thread(target=self.ws.run_forever)
        ws_thread.daemon = True
        ws_thread.start()
    
    def subscribe_events(self, events: List[str], camera_ids: List[int] = None):
        """訂閱事件"""
        if self.ws:
            message = {
                'action': 'subscribe',
                'events': events
            }
            if camera_ids:
                message['filters'] = {'camera_ids': camera_ids}
            
            self.ws.send(json.dumps(message))
    
    def disconnect_websocket(self):
        """斷開 WebSocket 連線"""
        if self.ws:
            self.ws.close()

# 使用範例
if __name__ == "__main__":
    # 初始化客戶端
    client = VisionFlowClient('http://localhost:5000', 'your-token-here')
    
    try:
        # 取得系統健康狀態
        health = client.get_system_health()
        print(f"系統狀態: {health['data']['status']}")
        
        # 取得攝影機狀態
        cameras = client.get_camera_status()
        print(f"攝影機數量: {len(cameras['data'])}")
        
        # 新增攝影機
        new_camera = client.add_camera(
            name="測試攝影機",
            url="rtsp://test.local/stream",
            description="API 測試攝影機"
        )
        print(f"新增攝影機 ID: {new_camera['data']['id']}")
        
        # 建立偵測區域
        area = client.create_detection_area(
            camera_id=1,
            name="測試區域",
            points=[
                {"x": 100, "y": 100},
                {"x": 300, "y": 100},
                {"x": 300, "y": 300},
                {"x": 100, "y": 300}
            ],
            properties={
                "active": True,
                "sensitivity": 0.8,
                "detection_classes": ["person", "vehicle"]
            }
        )
        print(f"建立偵測區域 ID: {area['data']['id']}")
        
        # 設定 WebSocket 事件處理器
        def handle_detection(data):
            detection = data['data']
            print(f"偵測到物件: {detection['objects'][0]['class']}")
            print(f"信心度: {detection['objects'][0]['confidence']}")
        
        def handle_camera_status(data):
            status = data['data']
            print(f"攝影機 {status['camera_id']} 狀態變更: {status['current_status']}")
        
        # 建立 WebSocket 連線
        client.connect_websocket({
            'object_detected': handle_detection,
            'camera_status_update': handle_camera_status
        })
        
        # 訂閱事件
        client.subscribe_events(['object_detected', 'camera_status_update'])
        
        # 保持程式運行
        input("按 Enter 鍵結束程式...")
        
    except requests.exceptions.HTTPError as e:
        print(f"HTTP 錯誤: {e}")
    except Exception as e:
        print(f"錯誤: {e}")
    finally:
        client.disconnect_websocket()

簡化版本

import requests
from datetime import datetime

class SimpleVisionFlowClient:
    """簡化版 VisionFlow 客戶端"""
    
    def __init__(self, base_url: str, username: str, password: str):
        self.base_url = base_url.rstrip('/')
        self.token = self._login(username, password)
        self.headers = {'Authorization': f'Bearer {self.token}'}
    
    def _login(self, username: str, password: str) -> str:
        """登入並取得令牌"""
        response = requests.post(
            f"{self.base_url}/auth/login",
            json={'username': username, 'password': password}
        )
        response.raise_for_status()
        return response.json()['data']['access_token']
    
    def get_cameras(self):
        """取得所有攝影機"""
        response = requests.get(
            f"{self.base_url}/camera/status",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()['data']
    
    def capture_snapshot(self, camera_id: int, filename: str = None):
        """擷取快照"""
        response = requests.get(
            f"{self.base_url}/camera/{camera_id}/snapshot",
            headers=self.headers
        )
        response.raise_for_status()
        
        if not filename:
            filename = f"camera_{camera_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
        
        with open(filename, 'wb') as f:
            f.write(response.content)
        
        print(f"快照已儲存至: {filename}")
        return filename

# 使用範例
client = SimpleVisionFlowClient(
    'http://localhost:5000',
    'admin',
    'password'
)

cameras = client.get_cameras()
for camera in cameras:
    print(f"攝影機: {camera['name']} - 狀態: {camera['status']}")
    if camera['status'] == 'active':
        client.capture_snapshot(camera['id'])
🟨 JavaScript/Node.js SDK

完整 JavaScript 客戶端

const axios = require('axios');
const WebSocket = require('ws');
const EventEmitter = require('events');
const fs = require('fs');

class VisionFlowClient extends EventEmitter {
    constructor(baseUrl, token) {
        super();
        this.baseUrl = baseUrl.replace(/\/$/, '');
        this.token = token;
        this.headers = {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        };
        this.ws = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
    }

    // HTTP 請求的通用方法
    async _request(method, endpoint, options = {}) {
        const config = {
            method,
            url: `${this.baseUrl}${endpoint}`,
            headers: this.headers,
            ...options
        };

        try {
            const response = await axios(config);
            return response.data;
        } catch (error) {
            if (error.response?.status === 429) {
                const retryAfter = error.response.headers['retry-after'];
                throw new Error(`Rate limit exceeded. Retry after: ${retryAfter}s`);
            }
            throw error;
        }
    }

    // 認證相關
    async getProfile() {
        return this._request('GET', '/auth/profile');
    }

    async refreshToken(refreshToken) {
        return this._request('POST', '/auth/refresh', {
            data: { refresh_token: refreshToken }
        });
    }

    // 攝影機管理
    async getCameraStatus(activeOnly = false) {
        const params = activeOnly ? { active_only: true } : {};
        return this._request('GET', '/camera/status', { params });
    }

    async addCamera(name, url, options = {}) {
        const data = { name, url, ...options };
        return this._request('POST', '/camera/add', { data });
    }

    async updateCamera(cameraId, options) {
        return this._request('PUT', `/camera/${cameraId}`, { data: options });
    }

    async deleteCamera(cameraId, force = false) {
        const params = force ? { force: true } : {};
        return this._request('DELETE', `/camera/${cameraId}`, { params });
    }

    async getCameraSnapshot(cameraId, quality = 'high') {
        const response = await axios({
            method: 'GET',
            url: `${this.baseUrl}/camera/${cameraId}/snapshot`,
            headers: this.headers,
            params: { quality },
            responseType: 'arraybuffer'
        });
        return response.data;
    }

    async saveCameraSnapshot(cameraId, filename, quality = 'high') {
        const imageData = await this.getCameraSnapshot(cameraId, quality);
        fs.writeFileSync(filename, imageData);
        console.log(`快照已儲存至: ${filename}`);
        return filename;
    }

    // 偵測區域管理
    async getDetectionAreas(cameraId) {
        return this._request('GET', `/camera/${cameraId}/areas`);
    }

    async createDetectionArea(cameraId, name, points, options = {}) {
        const data = { name, points, ...options };
        return this._request('POST', `/camera/${cameraId}/areas`, { data });
    }

    async updateDetectionArea(cameraId, areaId, options) {
        return this._request('PUT', `/camera/${cameraId}/areas/${areaId}`, { data: options });
    }

    async deleteDetectionArea(cameraId, areaId) {
        return this._request('DELETE', `/camera/${cameraId}/areas/${areaId}`);
    }

    // 通知管理
    async getNotificationSettings() {
        return this._request('GET', '/notification/settings');
    }

    async updateNotificationSettings(settings) {
        return this._request('PUT', '/notification/settings', { data: settings });
    }

    async getNotificationHistory(options = {}) {
        return this._request('GET', '/notification/history', { params: options });
    }

    // WebSocket 相關
    connectWebSocket() {
        const wsUrl = `ws://${this.baseUrl.replace(/^https?:\/\//, '')}/ws?token=${this.token}`;
        
        this.ws = new WebSocket(wsUrl);

        this.ws.on('open', () => {
            console.log('✅ WebSocket 連線已建立');
            this.reconnectAttempts = 0;
            this.emit('connected');
        });

        this.ws.on('message', (data) => {
            try {
                const message = JSON.parse(data);
                this.emit('message', message);
                this.emit(message.event, message.data);
            } catch (error) {
                console.error('解析 WebSocket 訊息失敗:', error);
            }
        });

        this.ws.on('close', (code, reason) => {
            console.log(`❌ WebSocket 連線已關閉: ${code} ${reason}`);
            this.emit('disconnected', { code, reason });
            
            if (code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) {
                this.scheduleReconnect();
            }
        });

        this.ws.on('error', (error) => {
            console.error('🔥 WebSocket 錯誤:', error);
            this.emit('error', error);
        });
    }

    scheduleReconnect() {
        this.reconnectAttempts++;
        const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
        
        console.log(`嘗試重新連線... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
        
        setTimeout(() => {
            this.connectWebSocket();
        }, delay);
    }

    subscribeEvents(events, filters = {}) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            const message = {
                action: 'subscribe',
                events,
                ...filters
            };
            this.ws.send(JSON.stringify(message));
        }
    }

    unsubscribeEvents(events) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            const message = {
                action: 'unsubscribe',
                events
            };
            this.ws.send(JSON.stringify(message));
        }
    }

    disconnectWebSocket() {
        if (this.ws) {
            this.ws.close(1000, 'Client disconnecting');
            this.ws = null;
        }
    }

    // 工具方法
    async waitForCameraActive(cameraId, timeout = 30000) {
        const startTime = Date.now();
        
        while (Date.now() - startTime < timeout) {
            try {
                const status = await this.getCameraStatus();
                const camera = status.data.find(c => c.id === cameraId);
                
                if (camera && camera.status === 'active') {
                    return true;
                }
                
                await new Promise(resolve => setTimeout(resolve, 1000));
            } catch (error) {
                console.error('檢查攝影機狀態時發生錯誤:', error);
            }
        }
        
        throw new Error(`攝影機 ${cameraId}${timeout}ms 內未變為啟用狀態`);
    }

    async monitorDetections(cameraIds = [], callback) {
        this.connectWebSocket();
        
        this.on('connected', () => {
            const filters = cameraIds.length > 0 ? { filters: { camera_ids: cameraIds } } : {};
            this.subscribeEvents(['object_detected'], filters);
        });

        this.on('object_detected', (data) => {
            if (callback) {
                callback(data);
            }
        });
    }
}

// 使用範例
async function example() {
    const client = new VisionFlowClient('http://localhost:5000', 'your-token-here');

    try {
        // 取得攝影機狀態
        const cameras = await client.getCameraStatus();
        console.log(`發現 ${cameras.data.length} 台攝影機`);

        // 監控物件偵測
        await client.monitorDetections([1, 2], (detection) => {
            console.log(`偵測到 ${detection.objects[0].class},信心度: ${detection.objects[0].confidence}`);
        });

        // 擷取所有啟用攝影機的快照
        for (const camera of cameras.data) {
            if (camera.status === 'active') {
                const filename = `snapshot_${camera.id}_${Date.now()}.jpg`;
                await client.saveCameraSnapshot(camera.id, filename);
            }
        }

    } catch (error) {
        console.error('執行時發生錯誤:', error);
    }
}

module.exports = VisionFlowClient;

React Hook 範例

import { useState, useEffect, useCallback } from 'react';

export function useVisionFlow(baseUrl, token) {
    const [client, setClient] = useState(null);
    const [connected, setConnected] = useState(false);
    const [cameras, setCameras] = useState([]);
    const [detections, setDetections] = useState([]);

    useEffect(() => {
        if (!token) return;

        const vfClient = new VisionFlowClient(baseUrl, token);
        setClient(vfClient);

        // 連接 WebSocket
        vfClient.connectWebSocket();

        vfClient.on('connected', () => {
            setConnected(true);
            vfClient.subscribeEvents(['object_detected', 'camera_status_update']);
        });

        vfClient.on('disconnected', () => {
            setConnected(false);
        });

        vfClient.on('object_detected', (detection) => {
            setDetections(prev => [detection, ...prev.slice(0, 99)]); // 保留最新100筆
        });

        vfClient.on('camera_status_update', (status) => {
            setCameras(prev => prev.map(camera => 
                camera.id === status.camera_id 
                    ? { ...camera, status: status.current_status }
                    : camera
            ));
        });

        // 初始載入攝影機資料
        vfClient.getCameraStatus().then(response => {
            setCameras(response.data);
        });

        return () => {
            vfClient.disconnectWebSocket();
        };
    }, [baseUrl, token]);

    const addCamera = useCallback(async (name, url, options) => {
        if (!client) return;
        
        try {
            const result = await client.addCamera(name, url, options);
            const updatedCameras = await client.getCameraStatus();
            setCameras(updatedCameras.data);
            return result;
        } catch (error) {
            throw error;
        }
    }, [client]);

    const captureSnapshot = useCallback(async (cameraId) => {
        if (!client) return;
        
        return await client.getCameraSnapshot(cameraId);
    }, [client]);

    return {
        client,
        connected,
        cameras,
        detections,
        addCamera,
        captureSnapshot
    };
}

// React 元件使用範例
function CameraMonitor() {
    const { connected, cameras, detections, addCamera } = useVisionFlow(
        'http://localhost:5000',
        localStorage.getItem('visionflow_token')
    );

    return (
        <div>
            <h1>攝影機監控 {connected ? '🟢' : '🔴'}</h1>
            
            <div>
                <h2>攝影機清單</h2>
                {cameras.map(camera => (
                    <div key={camera.id}>
                        {camera.name} - {camera.status}
                    </div>
                ))}
            </div>

            <div>
                <h2>最新偵測</h2>
                {detections.slice(0, 5).map((detection, index) => (
                    <div key={index}>
                        {new Date(detection.timestamp).toLocaleTimeString()} - 
                        攝影機 {detection.camera_name}: {detection.objects[0].class}
                    </div>
                ))}
            </div>
        </div>
    );
}
---

🧪 測試與除錯

API 測試工具與除錯指南

🔧 Postman 集合

匯入 Postman 集合

下載並匯入我們的 Postman 集合,包含所有端點的測試案例。

快速測試流程:

  1. 設定環境變數

    {
      "baseUrl": "http://localhost:5000",
      "token": "{{your-jwt-token}}"
    }
  2. 登入取得令牌

    // 在 Tests 標籤中加入此腳本
    const response = pm.response.json();
    if (response.success) {
        pm.environment.set("token", response.data.access_token);
    }
  3. 執行完整測試套件

    • 使用 Collection Runner 執行所有測試
    • 檢查測試結果和覆蓋率
    • 匯出測試報告
🐚 cURL 命令範例

認證測試

# 登入
curl -X POST http://localhost:5000/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "admin",
    "password": "password"
  }'

# 取得使用者資料
curl -X GET http://localhost:5000/auth/profile \
  -H "Authorization: Bearer <your-token>"

攝影機管理測試

# 取得攝影機狀態
curl -X GET http://localhost:5000/camera/status \
  -H "Authorization: Bearer <your-token>"

# 新增攝影機
curl -X POST http://localhost:5000/camera/add \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "測試攝影機",
    "url": "rtsp://test.local/stream",
    "description": "API 測試用攝影機",
    "settings": {
      "resolution": "1920x1080",
      "fps": 30,
      "quality": "high"
    }
  }'

# 更新攝影機設定
curl -X PUT http://localhost:5000/camera/1 \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "更新的攝影機名稱",
    "settings": {
      "fps": 25,
      "quality": "medium"
    }
  }'

# 取得攝影機快照
curl -X GET http://localhost:5000/camera/1/snapshot \
  -H "Authorization: Bearer <your-token>" \
  --output snapshot.jpg

# 刪除攝影機
curl -X DELETE http://localhost:5000/camera/1?force=true \
  -H "Authorization: Bearer <your-token>"

偵測區域測試

# 取得偵測區域
curl -X GET http://localhost:5000/camera/1/areas \
  -H "Authorization: Bearer <your-token>"

# 建立偵測區域
curl -X POST http://localhost:5000/camera/1/areas \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "入口監控區",
    "type": "polygon",
    "points": [
      {"x": 100, "y": 100},
      {"x": 300, "y": 100},
      {"x": 300, "y": 300},
      {"x": 100, "y": 300}
    ],
    "properties": {
      "active": true,
      "sensitivity": 0.8,
      "detection_classes": ["person", "vehicle"]
    }
  }'

通知系統測試

# 取得通知設定
curl -X GET http://localhost:5000/notification/settings \
  -H "Authorization: Bearer <your-token>"

# 更新通知設定
curl -X PUT http://localhost:5000/notification/settings \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "channels": {
      "email": {
        "enabled": true,
        "template": "professional"
      },
      "line": {
        "enabled": true
      }
    },
    "filtering": {
      "min_confidence": 0.8
    }
  }'

# 查詢通知歷史
curl -X GET "http://localhost:5000/notification/history?start_date=2024-01-01&limit=50" \
  -H "Authorization: Bearer <your-token>"

健康檢查測試

# 基礎健康檢查(無需認證)
curl -X GET http://localhost:5000/health

# 詳細健康檢查
curl -X GET http://localhost:5000/health/detailed \
  -H "Authorization: Bearer <your-token>"

# 系統效能指標
curl -X GET "http://localhost:5000/health/metrics?period=24h" \
  -H "Authorization: Bearer <your-token>"
🔍 除錯技巧

常見錯誤診斷

1. 認證失敗 (401)

# 檢查令牌是否有效
curl -X GET http://localhost:5000/auth/profile \
  -H "Authorization: Bearer <token>" \
  -v

# 常見原因:
# - 令牌已過期
# - 令牌格式錯誤
# - 權限不足

2. 速率限制 (429)

# 檢查回應標頭中的重試時間
curl -X GET http://localhost:5000/camera/status \
  -H "Authorization: Bearer <token>" \
  -I

# X-RateLimit-Limit: 30
# X-RateLimit-Remaining: 0
# X-RateLimit-Reset: 1642694400
# Retry-After: 60

3. 攝影機連線問題

# 檢查攝影機網路連線
ping camera-ip-address

# 測試 RTSP 串流
ffprobe -v quiet -print_format json -show_streams rtsp://camera-url

# 檢查防火牆設定
telnet camera-ip 554

偵錯日誌

啟用偵錯模式:

# 設定環境變數
export VISIONFLOW_DEBUG=true
export LOG_LEVEL=debug

# 重新啟動服務
systemctl restart visionflow

查看日誌:

# 即時查看日誌
tail -f /var/log/visionflow/app.log

# 搜尋特定錯誤
grep "ERROR" /var/log/visionflow/app.log | tail -20

# 分析 API 請求
grep "api_request" /var/log/visionflow/app.log | jq '.'

效能分析

監控 API 回應時間:

# 使用 curl 測量回應時間
curl -w "@curl-format.txt" -o /dev/null -s http://localhost:5000/camera/status

# curl-format.txt 內容:
# time_namelookup:  %{time_namelookup}\n
# time_connect:     %{time_connect}\n
# time_appconnect:  %{time_appconnect}\n
# time_pretransfer: %{time_pretransfer}\n
# time_redirect:    %{time_redirect}\n
# time_starttransfer: %{time_starttransfer}\n
# time_total:       %{time_total}\n

使用 Apache Bench 進行負載測試:

# 測試 API 端點效能
ab -n 1000 -c 10 -H "Authorization: Bearer <token>" \
   http://localhost:5000/camera/status

# 測試結果分析:
# - Requests per second
# - Time per request
# - Transfer rate
# - Connection Times

📚 更新記錄

API 版本歷史與功能演進

🆕 v2.1.0 (2024-01-20) - 當前版本

✨ 新功能

  • 🔒 增強安全性: 新增 API Key 認證方式,支援服務間安全通訊
  • 🎯 智慧偵測區域: 支援複雜多邊形區域,提升偵測精度
  • 📊 即時效能監控: 新增系統效能指標 API,提供詳細監控資料
  • 🔄 WebSocket 改進: 優化連線穩定性,支援事件過濾和訂閱管理
  • 📱 多平台通知: 新增 Slack、Webhook 通知支援
  • 🚀 批次操作: 支援批次攝影機管理和區域設定

🛠️ 功能改進

  • 效能優化: API 回應時間平均提升 35%
  • 📈 擴展性: 支援最多 50 台攝影機同時管理
  • 🔍 搜尋功能: 新增通知歷史和偵測記錄的進階搜尋
  • 📊 統計資料: 提供詳細的偵測統計和分析報表
  • 🎨 UI 改進: 更新 API 文檔格式,提升可讀性

🐛 錯誤修復

  • 修復攝影機斷線重連的記憶體洩漏問題
  • 解決高並發情況下的資料庫鎖定問題
  • 修正 WebSocket 連線在網路不穩時的異常行為
  • 修復偵測區域重疊計算的準確性問題

⚠️ 重大變更

  • GET /camera/status 回應格式調整,新增 health_metrics 欄位
  • 統一錯誤回應格式,所有 API 都採用一致的錯誤結構
  • WebSocket 事件命名調整:detectionobject_detected
📦 v2.0.0 (2024-01-01) - 主要版本更新

🎉 重大功能

  • 🤖 AI 模型升級: 採用 YOLOv8 物件偵測模型,準確度提升 25%
  • 🏗️ 架構重構: 微服務架構設計,提升系統穩定性和擴展性
  • 🔐 JWT 認證: 完整的 JWT 令牌系統,支援 Refresh Token
  • 📡 即時通訊: WebSocket 支援,提供即時事件推送
  • 🎯 多邊形偵測: 支援自定義多邊形偵測區域

🛠️ API 變更

  • 全新的 RESTful API 設計
  • 統一的回應格式和錯誤處理
  • 新增速率限制和配額管理
  • 支援 API 版本控制

💾 資料遷移

  • 自動從 v1.x 版本遷移資料
  • 提供資料備份和還原工具
  • 保持向後相容性(有限支援)
🔧 v1.2.0 (2023-12-01)

✨ 新功能

  • 📧 電子郵件通知: 支援 SMTP 郵件發送
  • 📱 LINE 通知整合: 完整的 LINE Notify 支援
  • 🎥 錄影功能: 偵測觸發自動錄影
  • 📊 基礎統計: 偵測數量和頻率統計

🐛 錯誤修復

  • 修復攝影機連線超時問題
  • 解決並發偵測時的效能問題
  • 修正通知重複發送的問題
🚀 v1.1.0 (2023-11-01)

✨ 新功能

  • 🎯 區域偵測: 基礎的矩形偵測區域
  • 🔧 設定管理: 攝影機參數調整
  • 📋 日誌系統: 基本的操作日誌記錄

🛠️ 改進

  • 提升偵測穩定性
  • 優化記憶體使用
  • 改善錯誤處理
🎯 v1.0.0 (2023-10-01) - 初始版本

🎉 首次發布

  • 🏠 基礎功能: 攝影機管理和狀態監控
  • 🤖 物件偵測: 基於 YOLO 的人員和車輛偵測
  • 📧 基礎通知: 簡單的郵件通知系統
  • 🌐 Web 介面: 基本的管理界面

🎯 支援功能

  • 最多 10 台攝影機管理
  • RTSP 串流支援
  • 基礎的移動偵測
  • SQLite 資料庫存儲

🔮 未來版本規劃

📅 v2.2.0 (計劃中 - 2024年2月)

🎯 規劃功能

  • 🧠 智慧分析: 行為分析和異常偵測
  • 🌍 多語言支援: 支援英文、中文、日文介面
  • 📱 行動應用: iOS/Android 原生應用程式
  • ☁️ 雲端整合: AWS/Azure 雲端儲存支援
  • 🔐 SSO 整合: Active Directory 和 LDAP 支援
🚀 v3.0.0 (遠期規劃 - 2024年6月)

🎯 重大更新

  • 🤖 AI 平台: 支援自定義 AI 模型訓練
  • 🏢 企業功能: 多租戶架構,權限管理
  • 📊 商業智慧: 詳細的分析報表和儀表板
  • 🔄 自動化: 規則引擎和工作流程自動化

🤝 技術支援

📞 聯絡資訊

🔧 技術支援

📋 支援層級

層級 回應時間 可用時間 聯絡方式
🆓 社群支援 1-3 工作天 工作時間 論壇、GitHub
💼 標準支援 24 小時 工作時間 Email、電話
🚀 企業支援 4 小時 24/7 專屬窗口

🔄 支援流程

  1. 問題描述: 詳細描述遇到的問題和錯誤訊息
  2. 環境資訊: 提供系統版本、配置和日誌檔案
  3. 重現步驟: 說明如何重現問題的具體步驟
  4. 期望結果: 描述預期的正確行為

🏆 API 文檔完成 ✨

感謝使用 VisionFlow API!

文檔品質 API 覆蓋率 範例完整性

📖 相關文檔 📋 API 基礎版 | 🚀 部署指南 | 📊 專案摘要


💝 如果這個 API 對您有幫助,請考慮給我們一個 ⭐ Star!