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

8.4 KiB
Raw Permalink Blame History

Supabase Stack 运维指南

完整的 Supabase 对象存储运维手册


📋 目录

  1. 系统架构
  2. 服务启动与管理
  3. 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/

🚀 服务启动与管理

启动服务

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 存储后端(必需

查看状态

# 查看所有容器
docker compose ps

# 查看日志
docker compose logs -f storage
docker compose logs -f kong

停止/重启

# 停止服务
docker compose stop

# 重启特定服务
docker compose restart storage
docker compose restart minio

# 完全停止并删除容器(数据保留)
docker compose down

环境变量

关键密钥在 .env 文件中:

# 服务密钥(后端使用)
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

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 示例

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.pystorage_client.js


🔧 故障排查

常见问题

1. 服务无法启动

# 检查端口占用
docker compose ps
netstat -tulpn | grep -E ":(8000|5432|9000)"

# 查看日志
docker compose logs storage

2. API 返回 403

# 检查密钥是否正确
grep SERVICE_ROLE_KEY .env

# 测试 API
curl -I https://amiap.hzau.edu.cn/supa/storage/v1/bucket \
  -H "apikey: YOUR_KEY"

3. 上传失败

# 检查 MinIO 是否运行
docker compose ps minio

# 查看 Storage 日志
docker compose logs storage | tail -50

健康检查

# 快速测试脚本
python3 test_https_storage.py

# 手动测试
curl https://amiap.hzau.edu.cn/supa/rest/v1/

备份

# 备份数据库
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 集成指南

🎯 快速开始

# 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 了。