ActiveStorageSaas

ActiveStorageSaas 是一个 Rails gem,为 ActiveStorage 提供多租户存储服务支持。它允许每个租户配置自己的存储服务,并支持动态加载不同的存储服务。

功能特性

  • 🏢 多租户支持:每个租户可以配置独立的存储服务
  • 🔄 动态服务加载:根据 blob 的 service_name 动态解析并加载对应的存储服务
  • 🔌 灵活配置:支持通过数据库配置存储服务选项
  • 📦 完全兼容:与 Rails ActiveStorage API 完全兼容,无需修改现有代码
  • 🚀 直接上传支持:支持 ActiveStorage 的直接上传功能

安装

在 Gemfile 中添加:

gem 'activestorage_saas', '~> 7.2.3'

然后运行:

bundle install

运行安装生成器:

rails generate active_storage_saas:install

这将创建必要的迁移文件。运行迁移:

rails db:migrate

配置

1. 数据库迁移

安装生成器会创建一个 storage_service_configurations 表,用于存储每个租户的存储服务配置。

2. 存储配置 (config/storage.yml)

config/storage.yml 中配置一个 saas 服务作为包装器:

saas:
  service: Saas
  default_service: local  # 默认使用的存储服务

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

amazon:
  service: S3
  access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
  secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
  region: us-east-1
  bucket: my-bucket

3. 应用配置 (config/application.rb 或 config/environments/*.rb)

Rails.application.configure do
  # 可选:自定义服务解析器
  # config.active_storage_saas.service_resolver = ->(blob) {
  #   StorageServiceConfiguration.from_service_name(blob.service_name)&.to_service
  # }

  # 可选:自定义服务名称转换器(用于直接上传)
  # config.active_storage_saas.service_name_converter = ->(controller) {
  #   controller.send(:current_tenant).storage_service&.to_service_name
  # }

  # 可选:自定义服务名称验证器
  # config.active_storage_saas.service_name_validator = ->(service_name) {
  #   StorageServiceConfiguration.valid_service_name?(service_name)
  # }

  # 可选:直接上传时额外的 blob 参数
  # config.active_storage_saas.direct_upload_extra_blob_args = ->(controller) {
  #   { tenant: controller.send(:current_tenant) }
  # }
end

使用方法

创建存储服务配置

# 为租户创建存储服务配置
config = StorageServiceConfiguration.create!(
  service_name: 'amazon',  # 基础服务名称(在 storage.yml 中定义)
  service_options: {
    bucket: 'tenant-specific-bucket',
    region: 'us-west-2'
  }
)

# 获取服务名称(格式:StorageServiceConfiguration:123)
service_name = config.to_service_name

使用存储服务

# 创建 blob 时指定服务名称
blob = ActiveStorage::Blob.create!(
  filename: 'example.jpg',
  byte_size: 1024,
  checksum: 'abc123',
  content_type: 'image/jpeg',
  service_name: config.to_service_name  # 使用动态服务名称
)

# blob.service 会自动解析为对应的存储服务实例
blob.service  # => 返回配置的 S3 服务实例

在模型中关联文件

class User < ApplicationRecord
  has_one_attached :avatar
end

# 上传文件时指定服务
user.avatar.attach(
  io: file,
  filename: 'avatar.jpg',
  service_name: current_tenant.storage_config.to_service_name
)

工作原理

SaasService

SaasService 是一个包装器服务,它:

  1. 接收一个 blob 对象
  2. 根据 blob.service_name 动态解析对应的存储服务
  3. 将所有方法调用委托给实际的存储服务

服务解析流程

  1. 当访问 blob.service 时,会调用 ActiveStorageSaas.service_resolver
  2. 解析器根据 service_name 查找对应的 StorageServiceConfiguration
  3. 配置对象通过 to_service 方法创建实际的存储服务实例
  4. 如果解析失败,回退到默认服务

服务名称格式

动态服务的名称格式为:ClassName:ID,例如:

  • StorageServiceConfiguration:1
  • StorageServiceConfiguration:42

这种格式允许从服务名称中提取配置记录的 ID。

高级用法

自定义服务解析器

config.active_storage_saas.service_resolver = ->(blob) {
  tenant = blob.record&.tenant
  tenant&.storage_config&.to_service
}

多租户集成示例

class Tenant < ApplicationRecord
  has_one :storage_service_configuration, class_name: 'StorageServiceConfiguration'
end

class ApplicationController < ActionController::Base
  def current_tenant
    @current_tenant ||= Tenant.find(session[:tenant_id])
  end
end

# 在配置中
config.active_storage_saas.service_name_converter = ->(controller) {
  controller.current_tenant.storage_service_configuration.to_service_name
}

兼容性

  • Rails 版本:7.2.3
  • Ruby 版本:>= 2.6.0
  • ActiveStorage 版本:7.2.3

开发

运行测试:

bundle exec rspec

运行控制台:

bin/console

贡献

欢迎提交 Issue 和 Pull Request!

许可证

MIT License