The project is in a healthy, maintained state
Each tenant can set its own storage service, ActiveStorage service can be dynamically loaded.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

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