在開發應用程式時,檔案儲存是一個不可避免的需求。無論是用戶頭像、產品圖片、文件下載,都需要安全且高效的檔案儲存方案。
Supabase Storage 是一個 S3 相容的物件儲存服務,專為現代應用程式設計。它不只是單純的檔案儲存,更提供了完整的檔案管理生態系統,包括權限控制、即時圖片轉換、CDN 加速等功能。
想像 Supabase Storage 就像是一個倉庫管理員。這個管理員不只幫你存放東西(檔案),還會:
惡魔城的鑰匙:透過盒子提供玩家想要的東西
儲存桶就像是檔案的分類資料夾,你可以為不同類型的檔案建立不同的儲存桶。
進入 Storage 頁面
建立新儲存桶
點擊「New bucket」按鈕
→ 輸入儲存桶名稱(如:avatars、documents、images)
→ 選擇是否為公開儲存桶
→ 點擊「Save」
也可以使用 SQL 來建立儲存桶:
-- 建立公開儲存桶(任何人都可以存取檔案)
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);
-- 建立私人儲存桶(需要權限才能存取)
INSERT INTO storage.buckets (id, name, public)
VALUES ('documents', 'documents', false);
const { data, error } = await supabase.storage.createBucket('avatars')
儲存桶分為兩種類型:
https://your-project.supabase.co/storage/v1/object/public/avatars/user1.jpg
即使是公開儲存桶,也可能需要控制誰可以上傳、修改或刪除檔案。
-- 允許認證用戶上傳頭像
CREATE POLICY "Users can upload their own avatar" ON storage.objects
FOR INSERT WITH CHECK (
bucket_id = 'avatars'
AND auth.uid()::text = (storage.foldername(name))[1]
);
-- 允許認證用戶查看所有頭像
CREATE POLICY "Anyone can view avatars" ON storage.objects
FOR SELECT USING (bucket_id = 'avatars');
-- 允許用戶更新自己的頭像
CREATE POLICY "Users can update their own avatar" ON storage.objects
FOR UPDATE USING (
bucket_id = 'avatars'
AND auth.uid()::text = (storage.foldername(name))[1]
);
-- 允許用戶刪除自己的頭像
CREATE POLICY "Users can delete their own avatar" ON storage.objects
FOR DELETE USING (
bucket_id = 'avatars'
AND auth.uid()::text = (storage.foldername(name))[1]
);
詳細操作 Supabase Storage 的 API ,可以使用上一篇提到的 JavaScript 客戶端函式庫。
完整的客戶端函式庫:
Supabase Client Libraries | Supabase Docs
import { supabase } from './supabaseClient'
// 上傳用戶頭像
async function uploadAvatar(file, userId) {
const fileExt = file.name.split('.').pop()
const fileName = `${userId}/avatar.${fileExt}`
const { data, error } = await supabase.storage
.from('avatars')
.upload(fileName, file, {
cacheControl: '3600',
upsert: true // 如果檔案已存在則覆蓋
})
if (error) {
console.error('上傳失敗:', error)
return null
}
return data
}
有多種方式可以取得檔案:
// 方法一:取得公開 URL(適用於公開儲存桶)
function getPublicUrl(bucket, path) {
const { data } = supabase.storage
.from(bucket)
.getPublicUrl(path)
return data.publicUrl
}
// 方法二:取得簽名 URL(適用於私人儲存桶)
async function getSignedUrl(bucket, path, expiresIn = 3600) {
const { data, error } = await supabase.storage
.from(bucket)
.createSignedUrl(path, expiresIn)
if (error) {
console.error('取得簽名 URL 失敗:', error)
return null
}
return data.signedUrl
}
// 方法三:直接下載檔案
async function downloadFile(bucket, path) {
const { data, error } = await supabase.storage
.from(bucket)
.download(path)
if (error) {
console.error('下載失敗:', error)
return null
}
return data
}
// 列出儲存桶中的所有檔案
async function listFiles(bucket, folder = '') {
const { data, error } = await supabase.storage
.from(bucket)
.list(folder, {
limit: 100,
offset: 0,
sortBy: { column: 'name', order: 'asc' }
})
if (error) {
console.error('列出檔案失敗:', error)
return []
}
return data
}
// 刪除單一檔案
async function deleteFile(bucket, path) {
const { error } = await supabase.storage
.from(bucket)
.remove([path])
if (error) {
console.error('刪除失敗:', error)
return false
}
return true
}
// 批量刪除檔案
async function deleteFiles(bucket, paths) {
const { error } = await supabase.storage
.from(bucket)
.remove(paths)
if (error) {
console.error('批量刪除失敗:', error)
return false
}
return true
}
Supabase Storage 提供強大的即時圖片轉換功能,讓你可以動態調整圖片大小和格式:
// 取得不同尺寸的圖片
function getResizedImageUrl(bucket, path, width, height) {
const { data } = supabase.storage
.from(bucket)
.getPublicUrl(path, {
transform: {
width: width,
height: height,
resize: 'cover', // 或 'contain', 'fill'
quality: 80
}
})
return data.publicUrl
}
// 範例:取得不同尺寸的頭像
const avatarUrl = getResizedImageUrl('avatars', 'user1/avatar.jpg', 150, 150)
const thumbnailUrl = getResizedImageUrl('avatars', 'user1/avatar.jpg', 50, 50)
// 好的命名方式:包含用戶 ID 和時間戳
const fileName = `${userId}/${Date.now()}_${originalFileName}`
// 避免的命名方式:可能造成衝突
const fileName = originalFileName
// 在前端檢查檔案大小
function validateFileSize(file, maxSizeMB = 5) {
const maxSize = maxSizeMB * 1024 * 1024
if (file.size > maxSize) {
throw new Error(`檔案大小不能超過 ${maxSizeMB}MB`)
}
}
async function uploadWithRetry(bucket, path, file, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file)
if (!error) return data
// 如果是網路錯誤,重試
if (error.message.includes('network')) {
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
continue
}
throw error
} catch (error) {
if (i === maxRetries - 1) throw error
}
}
}
// 使用適當的快取設定
const { data, error } = await supabase.storage
.from('images')
.upload(fileName, file, {
cacheControl: '31536000', // 快取一年
contentType: file.type
})
Supabase Storage 提供了一個完整且強大的檔案儲存解決方案,從基本的檔案上傳下載,到進階的權限控制和即時圖片轉換,都能輕鬆實現。
... to be continued
有任何想討論歡迎留言,或需要指正的地方請鞭大力一點,歡迎訂閱、按讚加分享,分享給想要提升開發效率的朋友