You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

11 KiB

MinIO 迁移到 boto3(S3 兼容协议)

日期:2026-02-02
类型:架构升级
影响范围:后端 - 文件存储服务


变更概述

将文件存储服务从 MinIO SDK 迁移到 boto3(S3 兼容协议),实现对多种对象存储的统一支持,包括阿里云 OSS、AWS S3、MinIO、腾讯云 COS 等。


迁移原因

1. 通用性需求

  • 生产环境使用阿里云 OSS
  • 开发环境可能使用 MinIO
  • 未来可能需要支持 AWS S3 或其他云存储

2. S3 兼容协议优势

  • boto3 是最成熟的对象存储 SDK
  • 阿里云 OSS 完全支持 S3 兼容 API
  • 一套代码适配多种存储,只需修改配置

3. 灵活性

  • 开发/测试/生产环境可以使用不同存储
  • 便于迁移和多云部署
  • 降低供应商锁定风险

详细变更

1. 依赖更新

文件server/requirements.txt

移除

minio==7.2.0

添加

boto3==1.34.0
botocore==1.34.0

2. 配置更新

文件server/app/core/config.py

变更前

class Settings(BaseSettings):
    # MinIO 配置
    MINIO_ENDPOINT: str
    MINIO_ACCESS_KEY: str
    MINIO_SECRET_KEY: str
    MINIO_SECURE: bool = False
    MINIO_BUCKET_NAME: str = "jointo"
    MINIO_PUBLIC_URL: str
    
    STORAGE_PROVIDER: str = "minio"

变更后

class Settings(BaseSettings):
    # S3 兼容存储配置
    S3_ENDPOINT_URL: str
    S3_ACCESS_KEY_ID: str
    S3_SECRET_ACCESS_KEY: str
    S3_REGION: str = "cn-hangzhou"
    S3_BUCKET_NAME: str = "jointo"
    S3_PUBLIC_URL: str = ""  # 可选,用于 CDN
    
    STORAGE_PROVIDER: str = "oss"  # oss/s3/minio

3. 环境变量更新

文件server/.env.example

变更前

MINIO_ENDPOINT=localhost:6185
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_SECURE=false
MINIO_BUCKET_NAME=jointo
MINIO_PUBLIC_URL=http://localhost:6185
STORAGE_PROVIDER=minio

变更后

# 生产环境(阿里云 OSS)
S3_ENDPOINT_URL=https://oss-cn-hangzhou.aliyuncs.com
S3_ACCESS_KEY_ID=your_access_key_id
S3_SECRET_ACCESS_KEY=your_secret_access_key
S3_REGION=cn-hangzhou
S3_BUCKET_NAME=jointo
S3_PUBLIC_URL=https://jointo.oss-cn-hangzhou.aliyuncs.com
STORAGE_PROVIDER=oss

# 开发环境(本地 MinIO)
# S3_ENDPOINT_URL=http://localhost:9000
# S3_ACCESS_KEY_ID=minioadmin
# S3_SECRET_ACCESS_KEY=minioadmin
# S3_REGION=us-east-1
# S3_BUCKET_NAME=jointo-dev
# S3_PUBLIC_URL=http://localhost:9000/jointo-dev
# STORAGE_PROVIDER=minio

4. StorageService 重写

文件server/app/core/storage.py

核心变更

4.1 导入更新

# 变更前
from minio import Minio
from minio.error import S3Error

# 变更后
import boto3
from botocore.exceptions import ClientError
from botocore.client import Config

4.2 客户端初始化

# 变更前
self.client = Minio(
    settings.MINIO_ENDPOINT,
    access_key=settings.MINIO_ACCESS_KEY,
    secret_key=settings.MINIO_SECRET_KEY,
    secure=settings.MINIO_SECURE
)

# 变更后
self.s3_client = boto3.client(
    's3',
    endpoint_url=settings.S3_ENDPOINT_URL,
    aws_access_key_id=settings.S3_ACCESS_KEY_ID,
    aws_secret_access_key=settings.S3_SECRET_ACCESS_KEY,
    region_name=settings.S3_REGION,
    config=Config(
        signature_version='s3v4',
        s3={'addressing_style': 'virtual'}
    )
)

4.3 Bucket 检查

# 变更前
if not self.client.bucket_exists(self.bucket_name):
    self.client.make_bucket(self.bucket_name)

# 变更后
try:
    self.s3_client.head_bucket(Bucket=self.bucket_name)
except ClientError as e:
    if e.response['Error']['Code'] == '404':
        self.s3_client.create_bucket(Bucket=self.bucket_name)

4.4 文件上传

# 变更前
self.client.put_object(
    self.bucket_name,
    object_name,
    BytesIO(data),
    len(data),
    content_type=content_type
)

# 变更后
extra_args = {}
if content_type:
    extra_args['ContentType'] = content_type

self.s3_client.put_object(
    Bucket=self.bucket_name,
    Key=object_name,
    Body=data,
    **extra_args
)

4.5 预签名 URL

# 变更前
return self.client.presigned_get_object(
    self.bucket_name,
    object_name,
    expires=timedelta(seconds=expires)
)

# 变更后
url = self.s3_client.generate_presigned_url(
    'get_object',
    Params={
        'Bucket': self.bucket_name,
        'Key': object_name
    },
    ExpiresIn=expires
)
return url

4.6 文件删除

# 变更前
self.client.remove_object(self.bucket_name, object_name)

# 变更后
self.s3_client.delete_object(
    Bucket=self.bucket_name,
    Key=object_name
)

4.7 文件存在检查

# 变更前
try:
    self.client.stat_object(self.bucket_name, object_name)
    return True
except S3Error:
    return False

# 变更后
try:
    self.s3_client.head_object(
        Bucket=self.bucket_name,
        Key=object_name
    )
    return True
except ClientError:
    return False

5. Docker Compose 更新(可选)

文件server/docker-compose.yml

如果不再需要本地 MinIO 服务,可以移除:

# 移除 MinIO 服务
# minio:
#   image: minio/minio:latest
#   container_name: jointo-server-minio
#   ...

注意:如果开发环境仍需要本地对象存储,建议保留 MinIO 服务,只需修改环境变量配置即可。


6. 文档更新

文件docs/requirements/backend/04-services/resource/file-storage-service.md

  • 更新服务概述(S3 兼容协议)
  • 更新支持的存储提供商列表
  • 更新 StorageService 实现代码
  • 更新配置示例(不同环境)
  • 更新环境变量说明
  • 更新文档版本(v2.1 → v3.0)

迁移步骤

1. 更新依赖

# 在容器内执行
docker exec jointo-server-app pip uninstall minio -y
docker exec jointo-server-app pip install boto3==1.34.0

或更新 requirements.txt 后重新构建镜像:

docker-compose build app

2. 更新环境变量

编辑 server/.env 文件,将 MinIO 配置替换为 S3 配置:

# 阿里云 OSS 配置
S3_ENDPOINT_URL=https://oss-cn-hangzhou.aliyuncs.com
S3_ACCESS_KEY_ID=your_oss_access_key_id
S3_SECRET_ACCESS_KEY=your_oss_secret_access_key
S3_REGION=cn-hangzhou
S3_BUCKET_NAME=jointo-prod
S3_PUBLIC_URL=https://jointo-prod.oss-cn-hangzhou.aliyuncs.com
STORAGE_PROVIDER=oss

3. 更新代码

  • 更新 app/core/config.py
  • 更新 app/core/storage.py

4. 重启服务

docker-compose restart app

5. 验证功能

# 测试文件上传
curl -X POST http://localhost:6170/api/v1/file-storage/upload \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@test.jpg" \
  -F "category=test"

兼容性说明

API 接口

  • 无变更:所有 API 接口保持不变
  • 无影响:前端代码无需修改

数据库

  • 无变更:数据库表结构无需修改
  • 兼容:已存储的文件记录完全兼容

业务逻辑

  • 无变更:FileStorageService 接口保持不变
  • 透明:其他服务(AttachmentService、ProjectResourceService 等)无需修改

配置对照表

MinIO 配置 boto3 配置 说明
MINIO_ENDPOINT S3_ENDPOINT_URL 需要添加协议前缀(http:// 或 https://)
MINIO_ACCESS_KEY S3_ACCESS_KEY_ID 访问密钥 ID
MINIO_SECRET_KEY S3_SECRET_ACCESS_KEY 访问密钥 Secret
MINIO_SECURE - 通过 endpoint URL 的协议判断(https = secure)
MINIO_BUCKET_NAME S3_BUCKET_NAME Bucket 名称
MINIO_PUBLIC_URL S3_PUBLIC_URL 公开访问 URL(可选)
- S3_REGION 区域(新增,OSS 需要)

不同存储提供商配置示例

阿里云 OSS

S3_ENDPOINT_URL=https://oss-cn-hangzhou.aliyuncs.com
S3_ACCESS_KEY_ID=LTAI5t...
S3_SECRET_ACCESS_KEY=xxx...
S3_REGION=cn-hangzhou
S3_BUCKET_NAME=jointo-prod
S3_PUBLIC_URL=https://cdn.jointo.com  # CDN 加速
STORAGE_PROVIDER=oss

AWS S3

S3_ENDPOINT_URL=https://s3.amazonaws.com
S3_ACCESS_KEY_ID=AKIA...
S3_SECRET_ACCESS_KEY=xxx...
S3_REGION=us-east-1
S3_BUCKET_NAME=jointo-prod
S3_PUBLIC_URL=https://jointo-prod.s3.amazonaws.com
STORAGE_PROVIDER=s3

MinIO(本地开发)

S3_ENDPOINT_URL=http://localhost:9000
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_REGION=us-east-1
S3_BUCKET_NAME=jointo-dev
S3_PUBLIC_URL=http://localhost:9000/jointo-dev
STORAGE_PROVIDER=minio

腾讯云 COS

S3_ENDPOINT_URL=https://cos.ap-guangzhou.myqcloud.com
S3_ACCESS_KEY_ID=AKID...
S3_SECRET_ACCESS_KEY=xxx...
S3_REGION=ap-guangzhou
S3_BUCKET_NAME=jointo-prod-1234567890
S3_PUBLIC_URL=https://jointo-prod-1234567890.cos.ap-guangzhou.myqcloud.com
STORAGE_PROVIDER=cos

测试建议

1. 单元测试

# tests/unit/test_storage_service.py
import pytest
from app.core.storage import StorageService

@pytest.mark.asyncio
async def test_upload_bytes():
    storage = StorageService()
    data = b"test content"
    url = await storage.upload_bytes(data, "test/file.txt", "text/plain")
    assert url is not None
    assert "test/file.txt" in url

2. 集成测试

# 上传测试
curl -X POST http://localhost:6170/api/v1/file-storage/upload \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@test.jpg" \
  -F "category=test"

# 获取预签名 URL
curl -X GET "http://localhost:6170/api/v1/file-storage/presigned-url?storage_path=test/file.txt" \
  -H "Authorization: Bearer $TOKEN"

3. 性能测试

  • 上传 1MB 文件:< 1s
  • 上传 10MB 文件:< 5s
  • 生成预签名 URL:< 100ms

回滚方案

如果迁移后出现问题,可以快速回滚:

1. 恢复依赖

docker exec jointo-server-app pip uninstall boto3 -y
docker exec jointo-server-app pip install minio==7.2.0

2. 恢复配置

恢复 .env 文件中的 MinIO 配置

3. 恢复代码

git checkout HEAD~1 server/app/core/storage.py
git checkout HEAD~1 server/app/core/config.py

4. 重启服务

docker-compose restart app

注意事项

1. 阿里云 OSS 特殊配置

  • 使用虚拟主机风格(virtual hosted-style)
  • Endpoint 格式:https://oss-<region>.aliyuncs.com
  • Bucket 域名:https://<bucket-name>.oss-<region>.aliyuncs.com

2. 签名版本

  • boto3 默认使用 Signature Version 4
  • 阿里云 OSS 完全支持 S3v4 签名

3. CDN 加速

  • 生产环境建议配置 S3_PUBLIC_URL 为 CDN 域名
  • 提升文件访问速度

4. 权限配置

  • 确保 AccessKey 有 Bucket 的读写权限
  • 建议使用 RAM 子账号,遵循最小权限原则

相关文档


作者

AI Assistant

审核状态

待审核