Files
labweb/supabase-stack/docs/OPERATIONS_GUIDE.md
zly 9fa602f21b feat(supabase): 整理 Storage 文档和示例代码
- 创建 docs/ 目录存放所有文档
  - QUICK_START.md: 快速入门指南
  - OPERATIONS_GUIDE.md: 完整运维指南
  - VUE_API_INTEGRATION.md: Vue 集成文档
  - DEPLOYMENT_INFO.md: 部署配置信息
  - versions.md: 版本信息

- 创建 examples/ 目录存放示例代码
  - storage_client.py: Python 完整客户端
  - storage_client.js: JavaScript 完整客户端
  - test_https_storage.py: 功能测试脚本

- 新增 README_STORAGE.md 作为 Storage 使用指南

- 修复签名 URL 生成问题(需要 /storage/v1 前缀)
- 测试脚本支持资源已存在的情况
- 所有客户端代码已验证可用

功能特性:
✓ 公网 HTTPS 访问
✓ 文件上传/下载
✓ 生成临时下载链接
✓ 完整的 REST API 客户端
✓ 支持 Python 和 JavaScript
2025-11-22 21:03:00 +08:00

351 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Supabase Stack 运维指南
完整的 Supabase 对象存储运维手册
---
## 📋 目录
1. [系统架构](#系统架构)
2. [服务启动与管理](#服务启动与管理)
3. [Storage API 使用](#storage-api-使用)
4. [故障排查](#故障排查)
---
## 🏗️ 系统架构
### 组件说明
```
外部用户 (HTTPS)
Traefik (:443)
Kong Gateway (:18000)
┌────────────────────────────────┐
│ Supabase Services │
├────────────────────────────────┤
│ • Auth (GoTrue) - 用户认证 │
│ • REST (PostgREST) - 数据API │
│ • Storage - 对象存储 │
│ • Realtime - 实时订阅 │
└────────────────────────────────┘
PostgreSQL + MinIO
```
### 访问地址
| 服务 | 地址 | 用途 |
|------|------|------|
| 公网 API | https://amiap.hzau.edu.cn/supa | 所有 API 入口 |
| Storage API | https://amiap.hzau.edu.cn/supa/storage/v1 | 对象存储 |
| Dashboard | http://100.64.0.2:18000 | 管理后台(内网) |
### 数据存储
- **PostgreSQL 数据**: `./volumes/db/data/`
- **对象存储数据**: `/vol1/1000/s3/stub/`
---
## 🚀 服务启动与管理
### 启动服务
```bash
cd /vol1/1000/docker_server/traefik/supabase-stack
# 启动所有服务(包含 MinIO 对象存储后端)
docker compose -f docker-compose.yml -f docker-compose.s3.yml up -d
```
**说明**
- `docker-compose.yml` - Supabase 核心服务
- `docker-compose.s3.yml` - MinIO 存储后端(**必需**
### 查看状态
```bash
# 查看所有容器
docker compose ps
# 查看日志
docker compose logs -f storage
docker compose logs -f kong
```
### 停止/重启
```bash
# 停止服务
docker compose stop
# 重启特定服务
docker compose restart storage
docker compose restart minio
# 完全停止并删除容器(数据保留)
docker compose down
```
### 环境变量
关键密钥在 `.env` 文件中:
```bash
# 服务密钥(后端使用)
SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3NjM4MDI2NjksImV4cCI6MjA3OTE2MjY2OX0.gQWUaTkZ6mjjlv2TED0cODp2meqqWuCGKZR1ptIbovg
# 匿名密钥(前端使用)
ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzYzODAyNjY5LCJleHAiOjIwNzkxNjI2Njl9.ltGXvQKpguLaf8Vzomn310hLgOZbrjqZT-F3rR00ulg
```
---
## 📦 Storage API 使用
### API 端点
```
Base URL: https://amiap.hzau.edu.cn/supa
Storage: https://amiap.hzau.edu.cn/supa/storage/v1
```
### 完整 Python 示例
保存为 `storage_client.py`
```python
import requests
from pathlib import Path
class StorageClient:
def __init__(self, base_url, api_key):
self.base_url = base_url
self.headers = {
'apikey': api_key,
'Authorization': f'Bearer {api_key}'
}
def create_bucket(self, name, public=False):
"""创建 bucket"""
response = requests.post(
f'{self.base_url}/storage/v1/bucket',
headers=self.headers,
json={'name': name, 'public': public}
)
return response.ok
def upload_file(self, bucket, file_path, storage_path=None):
"""上传文件"""
if not storage_path:
storage_path = Path(file_path).name
with open(file_path, 'rb') as f:
response = requests.post(
f'{self.base_url}/storage/v1/object/{bucket}/{storage_path}',
headers=self.headers,
files={'file': f}
)
return response.json() if response.ok else None
def download_file(self, bucket, storage_path, local_path):
"""下载文件"""
response = requests.get(
f'{self.base_url}/storage/v1/object/{bucket}/{storage_path}',
headers=self.headers
)
if response.ok:
with open(local_path, 'wb') as f:
f.write(response.content)
return True
return False
def create_signed_url(self, bucket, storage_path, expires_in=3600):
"""生成临时下载链接"""
response = requests.post(
f'{self.base_url}/storage/v1/object/sign/{bucket}/{storage_path}',
headers=self.headers,
json={'expiresIn': expires_in}
)
if response.ok:
return self.base_url + response.json()['signedURL']
return None
# 使用示例
if __name__ == '__main__':
client = StorageClient(
'https://amiap.hzau.edu.cn/supa',
'your-service-role-key'
)
# 创建 bucket
client.create_bucket('my-bucket')
# 上传文件
client.upload_file('my-bucket', 'photo.jpg', 'uploads/photo.jpg')
# 生成临时链接
url = client.create_signed_url('my-bucket', 'uploads/photo.jpg')
print(f'下载链接: {url}')
```
### 完整 JavaScript 示例
```javascript
class StorageClient {
constructor(baseUrl, apiKey) {
this.baseUrl = baseUrl;
this.headers = {
'apikey': apiKey,
'Authorization': `Bearer ${apiKey}`
};
}
async createBucket(name, isPublic = false) {
const response = await fetch(`${this.baseUrl}/storage/v1/bucket`, {
method: 'POST',
headers: {
...this.headers,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, public: isPublic })
});
return response.ok;
}
async uploadFile(bucket, file, storagePath) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(
`${this.baseUrl}/storage/v1/object/${bucket}/${storagePath}`,
{
method: 'POST',
headers: this.headers,
body: formData
}
);
return response.ok ? await response.json() : null;
}
async createSignedUrl(bucket, storagePath, expiresIn = 3600) {
const response = await fetch(
`${this.baseUrl}/storage/v1/object/sign/${bucket}/${storagePath}`,
{
method: 'POST',
headers: {
...this.headers,
'Content-Type': 'application/json'
},
body: JSON.stringify({ expiresIn })
}
);
if (response.ok) {
const data = await response.json();
return this.baseUrl + data.signedURL;
}
return null;
}
}
// 使用示例
const client = new StorageClient(
'https://amiap.hzau.edu.cn/supa',
'your-service-role-key'
);
// 上传文件
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
await client.uploadFile('my-bucket', file, `uploads/${file.name}`);
});
```
完整代码示例请查看:`storage_client.py``storage_client.js`
---
## 🔧 故障排查
### 常见问题
**1. 服务无法启动**
```bash
# 检查端口占用
docker compose ps
netstat -tulpn | grep -E ":(8000|5432|9000)"
# 查看日志
docker compose logs storage
```
**2. API 返回 403**
```bash
# 检查密钥是否正确
grep SERVICE_ROLE_KEY .env
# 测试 API
curl -I https://amiap.hzau.edu.cn/supa/storage/v1/bucket \
-H "apikey: YOUR_KEY"
```
**3. 上传失败**
```bash
# 检查 MinIO 是否运行
docker compose ps minio
# 查看 Storage 日志
docker compose logs storage | tail -50
```
### 健康检查
```bash
# 快速测试脚本
python3 test_https_storage.py
# 手动测试
curl https://amiap.hzau.edu.cn/supa/rest/v1/
```
### 备份
```bash
# 备份数据库
docker exec supabase-db pg_dump -U postgres > backup.sql
# 备份对象存储
tar -czf s3_backup.tar.gz /vol1/1000/s3/
```
---
## 📚 相关文档
- `storage_client.py` - Python 完整客户端代码
- `storage_client.js` - JavaScript 完整客户端代码
- `test_https_storage.py` - 测试脚本
- `VUE_API_INTEGRATION.md` - Vue 集成指南
---
## 🎯 快速开始
```bash
# 1. 启动服务
docker compose -f docker-compose.yml -f docker-compose.s3.yml up -d
# 2. 测试
python3 test_https_storage.py
# 3. 查看文档
cat storage_client.py
```
完成!现在可以开始使用 Supabase Storage 了。