kstools-license-manager-test / docs /SUPABASE_STORAGE_GUIDE.md
KyrosDev's picture
整理文件結構移至 docs 和 schemas 資料夾
0bca400
# 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)