85 lines
2.7 KiB
Markdown
85 lines
2.7 KiB
Markdown
# 文件存储
|
||
|
||
## 创建存储桶
|
||
|
||
通过 SQL 迁移创建存储桶:
|
||
|
||
```sql
|
||
INSERT INTO storage.buckets (id, name, public)
|
||
VALUES ('avatars', 'avatars', true);
|
||
```
|
||
|
||
## 关键上传方法
|
||
|
||
**必须使用 ArrayBuffer**(通过 `base64-arraybuffer`)进行所有文件上传。使用 `Blob`、`File` 或 `FormData` 会导致 `400 InvalidRequest "No content provided"` 错误。
|
||
**注意**:必须通过友好的 Toast 交互提醒用户数据操作结果,特别是操作失败的情况。
|
||
**重要约束:必须在前端上传文件。禁止在云函数(Edge Functions)中上传文件**,因为云函数上传的文件无法获取公网访问 URL,会导致前端无法正常访问和显示文件。所有文件上传操作必须在前端(浏览器环境)中进行。
|
||
|
||
> 记得在 `package.json` 中添加 `base64-arraybuffer` 依赖。
|
||
|
||
```typescript
|
||
import { decode } from 'base64-arraybuffer';
|
||
|
||
// ✅ 从 base64 字符串上传(必须检查 error)
|
||
const { data, error } = await supabase.storage
|
||
.from('avatars')
|
||
.upload('public/avatar1.png', decode('base64FileData'), {
|
||
contentType: 'image/png'
|
||
});
|
||
|
||
if (error) throw new Error(`上传失败: ${error.message}`);
|
||
|
||
// 从文件输入上传(先转换为 ArrayBuffer)
|
||
const reader = new FileReader();
|
||
reader.onload = async (e) => {
|
||
const base64 = (e.target?.result as string).split(',')[1];
|
||
const { data, error } = await supabase.storage
|
||
.from('bucket-name')
|
||
.upload(`path/${file.name}`, decode(base64), {
|
||
contentType: file.type,
|
||
});
|
||
|
||
if (error) {
|
||
console.error('上传失败:', error.message);
|
||
return;
|
||
}
|
||
};
|
||
reader.readAsDataURL(file);
|
||
```
|
||
|
||
```typescript
|
||
// ❌ 错误做法 — 会导致 400 错误
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
await supabase.storage.from('bucket').upload('path', formData); // ❌
|
||
await supabase.storage.from('bucket').upload('path', blob); // ❌
|
||
await supabase.storage.from('bucket').upload('path', file); // ❌
|
||
```
|
||
|
||
## 存储 RLS 策略
|
||
|
||
```sql
|
||
-- 创建存储桶的 RLS 策略
|
||
CREATE POLICY anon_select_avatars ON storage.objects
|
||
FOR SELECT USING (bucket_id = 'avatars');
|
||
|
||
CREATE POLICY users_insert_avatars ON storage.objects
|
||
FOR INSERT WITH CHECK (
|
||
bucket_id = 'avatars' AND
|
||
auth.role() = 'authenticated'
|
||
);
|
||
```
|
||
|
||
## Edge Functions 存储限制
|
||
|
||
**重要**:存储操作应在前端执行,而非 Edge Functions。Edge Functions 使用私有网络 URL,无法提供文件的公开访问。
|
||
|
||
## 存储模式限制
|
||
|
||
不要在 `storage` 模式中执行以下操作:
|
||
- 创建自定义表或函数
|
||
- 删除现有表或函数
|
||
- 在现有存储表上创建索引
|
||
- 对 `storage.migrations` 执行破坏性操作
|
||
|
||
管理文件访问请在 `public` 模式中创建辅助函数。 |