| # Supabase Storage 檔案儲存使用指南 | |
| ## 概述 | |
| KSTools License Manager 使用 Supabase Storage 來儲存和管理插件發布檔案。系統支援 ZIP、EXE、MSI 三種檔案格式,並自動建立 `plugin-releases` bucket 用於存放版本檔案。 | |
| ## 系統架構流程圖 | |
| ```mermaid | |
| graph TD | |
| A[開發者] --> B[準備發布檔案] | |
| B --> C{選擇檔案格式} | |
| C --> D[.zip 壓縮包] | |
| C --> E[.exe 可執行檔] | |
| C --> F[.msi 安裝程式] | |
| D --> G[上傳到 Supabase Storage] | |
| E --> G | |
| F --> G | |
| G --> H[獲取公開 URL] | |
| H --> I[更新 versions 表] | |
| I --> J[用戶可下載] | |
| subgraph "Supabase Storage" | |
| K[plugin-releases bucket] | |
| L[RLS 政策] | |
| M[公開 URL] | |
| end | |
| G --> K | |
| K --> L | |
| L --> M | |
| M --> H | |
| subgraph "前端系統" | |
| N[版本管理頁面] | |
| O[下載統計] | |
| P[檔案列表] | |
| end | |
| J --> N | |
| N --> O | |
| N --> P | |
| %% 精簡配色 - 只標記關鍵步驟 | |
| style A fill:#e3f2fd | |
| style G fill:#f3e5f5 | |
| style J fill:#d4edda | |
| ``` | |
| ## 發布流程圖 | |
| ```mermaid | |
| sequenceDiagram | |
| participant Dev as 開發者 | |
| participant UI as 前端介面 | |
| participant API as 後端 API | |
| participant Storage as Supabase Storage | |
| participant DB as Supabase DB | |
| participant User as 用戶 | |
| Dev->>+UI: 1. 選擇檔案上傳 (.zip/.exe/.msi) | |
| UI->>UI: 2. 檢測檔案格式 | |
| UI->>+API: 3. POST /api/admin/release (FormData) | |
| API->>API: 4. 驗證檔案格式和大小 | |
| API->>+Storage: 5. upload(file, filename) | |
| Storage-->>-API: 6. 返回上傳結果 | |
| API->>+Storage: 7. getPublicUrl(filename) | |
| Storage-->>-API: 8. 返回公開 URL | |
| API->>+DB: 9. 插入版本記錄 (含 title, file_type) | |
| DB-->>-API: 10. 確認記錄已建立 | |
| API-->>-UI: 11. 返回完整發布結果 | |
| UI-->>-Dev: 12. 發布成功通知 | |
| User->>+UI: 13. 查看版本列表 | |
| UI->>+DB: 14. 查詢版本記錄 | |
| DB-->>-UI: 15. 返回版本資料 (含檔案類型) | |
| UI-->>-User: 16. 顯示版本列表 (顯示檔案格式圖示) | |
| User->>+Storage: 17. 點擊下載 | |
| Storage-->>-User: 18. 直接下載檔案 | |
| %% 關鍵步驟高亮 | |
| rect rgb(255, 243, 205) | |
| note over UI,API: 檔案上傳驗證階段 | |
| end | |
| rect rgb(243, 229, 245) | |
| note over API,Storage: Storage 處理階段 | |
| end | |
| rect rgb(212, 237, 218) | |
| note over API,DB: 資料庫記錄階段 | |
| end | |
| ``` | |
| ## 檔案格式決策流程圖 | |
| ```mermaid | |
| flowchart TD | |
| Start([開始發布]) --> Choose{選擇檔案格式} | |
| Choose -->|開發版本| ZIP[ZIP 壓縮包] | |
| Choose -->|便攜版本| EXE[EXE 可執行檔] | |
| Choose -->|正式安裝| MSI[MSI 安裝程式] | |
| ZIP --> CheckZip{檔案完整性} | |
| EXE --> CheckExe{可執行性測試} | |
| MSI --> CheckMsi{安裝程式測試} | |
| CheckZip -->|通過| Upload1[上傳 .zip] | |
| CheckExe -->|通過| Upload2[上傳 .exe] | |
| CheckMsi -->|通過| Upload3[上傳 .msi] | |
| CheckZip -->|失敗| Error[修復並重新打包] | |
| CheckExe -->|失敗| Error | |
| CheckMsi -->|失敗| Error | |
| Upload1 --> Success[發布成功] | |
| Upload2 --> Success | |
| Upload3 --> Success | |
| Error --> Start | |
| %% 精簡配色 - 只標記關鍵步驟 | |
| style Start fill:#e3f2fd | |
| style Success fill:#d4edda | |
| style Error fill:#f8d7da | |
| ``` | |
| ## 檔案格式說明 | |
| ### 支援的檔案格式 | |
| | 格式 | 用途 | 檔案大小 | 建議使用場景 | | |
| |-----|------|---------|-------------| | |
| | `.zip` | 壓縮包 | 小 | 開發版本、源碼發布 | | |
| | `.exe` | 可執行檔 | 中 | 便攜版本、免安裝版 | | |
| | `.msi` | 安裝程式 | 大 | 正式發布、企業部署 | | |
| ### 檔案命名規範 | |
| ``` | |
| KSTools-v{版本號}.{格式} | |
| 範例: | |
| - KSTools-v1.2.0.zip | |
| - KSTools-v1.2.0.exe | |
| - KSTools-v1.2.0.msi | |
| ``` | |
| ## 設定步驟 | |
| ### 1. 執行 SQL Schema | |
| 在你的 Supabase 版本專案中執行 `supabase-version-schema.sql`,這會: | |
| - 建立 `plugin-releases` bucket | |
| - 建立支援多格式的 `versions` 表 | |
| - 設定適當的 RLS 政策 | |
| - 允許公開讀取檔案 | |
| - 允許認證用戶上傳檔案 | |
| ### 2. 驗證 Storage 設定 | |
| 1. 登入 Supabase Dashboard | |
| 2. 進入你的版本專案 | |
| 3. 點選 "Storage" 頁面 | |
| 4. 確認看到 `plugin-releases` bucket | |
| ## 檔案上傳方式 | |
| ### 透過後端 API 上傳 | |
| 系統採用統一的後端 API 上傳方式,確保安全性和一致性。 | |
| ```javascript | |
| // 前端版本發布表單提交 | |
| async function handleRelease(formData, file) { | |
| const releaseData = new FormData(); | |
| releaseData.append('version', formData.version); | |
| releaseData.append('title', formData.title); | |
| releaseData.append('changelog', formData.changelog); | |
| releaseData.append('file', file); | |
| const response = await fetch('/api/admin/release', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${session.access_token}` | |
| }, | |
| body: releaseData | |
| }); | |
| if (!response.ok) { | |
| throw new Error('發布失敗'); | |
| } | |
| return await response.json(); | |
| } | |
| ``` | |
| ### 檔案上傳流程 | |
| 1. **前端驗證** - 檔案格式、大小檢查 | |
| 2. **API 呼叫** - 傳送 FormData 到 `/api/admin/release` | |
| 3. **後端處理** - 檔案上傳到 Supabase Storage | |
| 4. **資料庫更新** - 儲存版本記錄 | |
| 5. **返回結果** - 完整的發布資訊 | |
| ## 後端 API 實作詳情 | |
| ### FastAPI 端點:`/api/admin/release` | |
| ```python | |
| @router.post("/admin/release") | |
| async def release_version( | |
| version: str = Form(...), | |
| title: Optional[str] = Form(None), | |
| changelog: str = Form(...), | |
| min_revit_version: Optional[str] = Form(None), | |
| file: Optional[UploadFile] = File(None), | |
| current_user = Depends(verify_admin) | |
| ): | |
| """ | |
| 發布新版本 - 統一的檔案上傳端點 | |
| 支援 .zip, .exe, .msi 三種格式 | |
| """ | |
| # 1. 驗證檔案格式 | |
| allowed_extensions = ['.zip', '.exe', '.msi'] | |
| file_extension = None | |
| for ext in allowed_extensions: | |
| if file.filename.lower().endswith(ext): | |
| file_extension = ext | |
| break | |
| if not file_extension: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"不支援的檔案格式。支援格式:{', '.join(allowed_extensions)}" | |
| ) | |
| # 2. 上傳到 Supabase Storage | |
| file_content = await file.read() | |
| file_size = len(file_content) | |
| file_path = f"KSTools-v{version}{file_extension}" | |
| # 設定正確的 MIME 類型 | |
| content_types = { | |
| '.zip': 'application/zip', | |
| '.exe': 'application/octet-stream', | |
| '.msi': 'application/x-msi' | |
| } | |
| content_type = content_types.get(file_extension, 'application/octet-stream') | |
| final_download_url = await version_db.upload_file(file_path, file_content, content_type) | |
| # 3. 儲存版本記錄 | |
| version_data = { | |
| 'version': version, | |
| 'title': title, | |
| 'changelog': changelog, | |
| 'min_revit_version': min_revit_version, | |
| 'download_url': final_download_url, | |
| 'file_size': file_size, | |
| 'file_type': file_extension.lstrip('.') if file_extension else None, | |
| 'file_name': f"KSTools-v{version}{file_extension}" if file_extension else None, | |
| 'released_by': current_user.get('email', 'Unknown'), | |
| 'is_active': True | |
| } | |
| result = client.table('versions').insert(version_data).execute() | |
| return {"success": True, "version": result.data[0]} | |
| ``` | |
| ## 自動化流程 | |
| 使用此 API 端點的優勢: | |
| - ✅ **統一入口** - 所有檔案上傳都通過同一個端點 | |
| - ✅ **自動驗證** - 檔案格式、大小自動檢查 | |
| - ✅ **安全控制** - 管理員權限驗證 | |
| - ✅ **完整記錄** - 自動建立資料庫記錄 | |
| - ✅ **錯誤處理** - 統一的錯誤回應 | |
| ## 資料庫記錄範例 | |
| 系統會自動建立以下格式的版本記錄: | |
| ```sql | |
| -- 新增版本記錄(包含檔案格式和標題) | |
| INSERT INTO versions ( | |
| version, | |
| title, | |
| release_date, | |
| is_active, | |
| download_url, | |
| file_size, | |
| file_type, | |
| file_name, | |
| changelog, | |
| min_revit_version, | |
| released_by | |
| ) VALUES ( | |
| '1.2.0', | |
| '重大功能更新', -- 版本標題 | |
| NOW(), | |
| true, | |
| 'https://你的專案ID.supabase.co/storage/v1/object/public/plugin-releases/KSTools-v1.2.0.msi', | |
| 25600000, -- 檔案大小(位元組) | |
| 'msi', -- 檔案類型 | |
| 'KSTools-v1.2.0.msi', -- 檔案名稱 | |
| '- 新功能說明\n- 錯誤修復', | |
| '2020', | |
| 'KyrosDev' | |
| ); | |
| ``` | |
| ## 前端顯示檔案類型 | |
| ```javascript | |
| // 在版本列表中顯示檔案格式圖示 | |
| function getFileTypeIcon(fileType) { | |
| const icons = { | |
| 'zip': '📦', | |
| 'exe': '⚙️', | |
| 'msi': '💾' | |
| }; | |
| return icons[fileType] || '📄'; | |
| } | |
| function renderVersionList(versions) { | |
| return versions.map(version => ` | |
| <div class="version-item"> | |
| <span class="version-number">${version.version}</span> | |
| <span class="file-type"> | |
| ${getFileTypeIcon(version.file_type)} | |
| ${version.file_type.toUpperCase()} | |
| </span> | |
| <span class="file-size">${formatFileSize(version.file_size)}</span> | |
| <a href="${version.download_url}" class="download-btn"> | |
| 下載 ${version.file_name} | |
| </a> | |
| </div> | |
| `).join(''); | |
| } | |
| ``` | |
| ## 檔案管理操作流程圖 | |
| ```mermaid | |
| flowchart TD | |
| A[列出檔案] --> B{選擇操作} | |
| B --> C[下載] | |
| B --> D[刪除] | |
| B --> E[更新] | |
| B --> F[獲取資訊] | |
| A --> G[檢查 .zip] | |
| A --> H[檢查 .exe] | |
| A --> I[檢查 .msi] | |
| A --> J[storage.list API] | |
| C --> K[storage.download API] | |
| D --> L[storage.remove API] | |
| E --> M[storage.update API] | |
| F --> N[storage.getPublicUrl API] | |
| K --> O[檔案下載完成] | |
| L --> P[檔案刪除完成] | |
| M --> Q[檔案更新完成] | |
| N --> R[獲取URL完成] | |
| %% 精簡配色 - 只標記關鍵步驟 | |
| style A fill:#e3f2fd | |
| style D fill:#f8d7da | |
| style J fill:#f3e5f5 | |
| style K fill:#f3e5f5 | |
| style L fill:#f3e5f5 | |
| style M fill:#f3e5f5 | |
| style N fill:#f3e5f5 | |
| style O fill:#d4edda | |
| style P fill:#d4edda | |
| style Q fill:#d4edda | |
| style R fill:#d4edda | |
| ``` | |
| ## 檔案管理 | |
| ### 檢視所有檔案(按格式篩選) | |
| ```javascript | |
| // 列出所有檔案 | |
| const { data, error } = await supabaseVersion.storage | |
| .from('plugin-releases') | |
| .list(); | |
| // 按檔案格式篩選 | |
| const zipFiles = data.filter(file => file.name.endsWith('.zip')); | |
| const exeFiles = data.filter(file => file.name.endsWith('.exe')); | |
| const msiFiles = data.filter(file => file.name.endsWith('.msi')); | |
| ``` | |
| ### 批量操作不同格式檔案 | |
| ```javascript | |
| // 刪除特定版本的所有格式檔案 | |
| async function deleteVersionAllFormats(version) { | |
| const formats = ['zip', 'exe', 'msi']; | |
| const filesToDelete = formats.map(format => `KSTools-v${version}.${format}`); | |
| const { data, error } = await supabaseVersion.storage | |
| .from('plugin-releases') | |
| .remove(filesToDelete); | |
| return { success: !error, error }; | |
| } | |
| ``` | |
| ### 檢查檔案完整性 | |
| ```javascript | |
| // 驗證檔案是否完整上傳 | |
| async function verifyFileIntegrity(fileName, expectedSize) { | |
| const { data, error } = await supabaseVersion.storage | |
| .from('plugin-releases') | |
| .list('', { search: fileName }); | |
| if (error || !data.length) { | |
| return { valid: false, reason: '檔案不存在' }; | |
| } | |
| const file = data[0]; | |
| if (file.metadata?.size !== expectedSize) { | |
| return { valid: false, reason: '檔案大小不符' }; | |
| } | |
| return { valid: true }; | |
| } | |
| ``` | |
| ## 權限管理架構圖 | |
| ```mermaid | |
| graph TB | |
| subgraph "Supabase RLS 政策" | |
| A[Public Read Policy] | |
| B[Authenticated Upload Policy] | |
| C[Service Role Manage Policy] | |
| end | |
| subgraph "用戶角色" | |
| D[匿名用戶] | |
| E[登入用戶] | |
| F[Service Role] | |
| end | |
| subgraph "操作權限" | |
| G[只能下載] | |
| H[可以上傳+下載] | |
| I[完整管理權限] | |
| end | |
| subgraph "檔案格式支援" | |
| J[所有格式下載] | |
| K[所有格式上傳] | |
| L[所有格式管理] | |
| end | |
| D --> A --> G --> J | |
| E --> B --> H --> K | |
| F --> C --> I --> L | |
| %% 精簡配色 - 只標記關鍵權限 | |
| style C fill:#f3e5f5 | |
| style I fill:#f8d7da | |
| ``` | |
| ## 安全建議 | |
| ### 檔案上傳安全 | |
| 1. **檔案格式驗證**:確保只允許 .zip、.exe、.msi 格式 | |
| 2. **檔案大小限制**:設定合理的檔案大小上限 | |
| 3. **病毒掃描**:建議對 .exe 和 .msi 檔案進行安全掃描 | |
| 4. **數位簽章**:.exe 和 .msi 檔案應使用程式碼簽章 | |
| ### 權限控制 | |
| 1. 不要在前端暴露 Service Role Key | |
| 2. 上傳功能建議透過後端 API 處理 | |
| 3. 定期檢查並清理舊版本檔案 | |
| 4. 對敏感操作實施雙重驗證 | |
| ## 疑難排解 | |
| ### 常見問題 | |
| **Q: 版本發布失敗** | |
| A: 檢查 API 權限、檔案格式是否正確,查看瀏覽器開發者工具錯誤訊息 | |
| **Q: 檔案上傳後找不到** | |
| A: 確認 Supabase Storage bucket `plugin-releases` 已正確建立並設定 RLS 政策 | |
| **Q: 檔案大小限制** | |
| A: 系統設定為 100MB 上限,Supabase 免費版單檔限制為 50MB,付費版可達 5GB | |
| **Q: 權限驗證失敗** | |
| A: 確認登入狀態和管理員權限,檢查 Bearer Token 是否正確傳送 | |
| ### 檢查清單 | |
| - [ ] SQL Schema 已執行(包含 file_type 欄位) | |
| - [ ] Storage bucket 已建立 | |
| - [ ] RLS 政策已設定 | |
| - [ ] 檔案命名符合規範 | |
| - [ ] 檔案格式驗證功能正常 | |
| - [ ] 版本記錄包含檔案類型資訊 | |
| - [ ] 下載連結可正常使用 | |
| - [ ] 前端顯示檔案格式圖示 | |
| ## 相關連結 | |
| - [Supabase Storage 官方文件](https://supabase.com/docs/guides/storage) | |
| - [Supabase Storage API 參考](https://supabase.com/docs/reference/javascript/storage-createbucket) | |
| - [檔案 MIME 類型參考](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) |