You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单公司应用改造为多公司架构:实现创建公司生成独立库及动态连库

单公司应用改造多公司架构(独立数据库方案)

核心需求

  • 创建公司时,自动为该公司生成独立数据库及专属数据库用户
  • 用户完成认证并携带company_id时,系统自动切换至对应公司的数据库

现有代码的问题与优化方向

你当前的代码仅实现了数据库创建,但存在几个关键问题:

  1. SQL语句直接拼接,存在SQL注入风险
  2. 未创建专属数据库用户,所有公司共用超级权限账号,不符合安全规范
  3. 调用ActiveRecord::Base.establish_connection会全局修改数据库连接,影响其他请求
  4. 新创建的数据库没有初始化表结构(未执行迁移)

完整实现方案

1. 安全创建数据库与专属用户

修改create方法,使用参数化查询避免注入,同时创建专属数据库用户并赋予权限:

def create
  @datos_empresa = DatosEmpresa.new(datos_empresa_params)

  respond_to do |format|
    if @datos_empresa.save
      # 用超级账号连接数据库服务器
      admin_client = Mysql2::Client.new(
        host: 'localhost',
        username: ENV['DB_ADMIN_USER'],
        password: ENV['DB_ADMIN_PASS']
      )

      # 生成规范的数据库名和用户名(避免特殊字符)
      db_name = "company_#{@datos_empresa.id}_#{@datos_empresa.nombre.parameterize}"
      db_user = "user_#{@datos_empresa.id}"
      db_pass = SecureRandom.hex(16) # 生成随机密码

      begin
        # 创建数据库
        admin_client.query("CREATE DATABASE `#{db_name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
        # 创建专属用户并赋予仅当前数据库的权限
        admin_client.query("CREATE USER '#{db_user}'@'localhost' IDENTIFIED BY '#{db_pass}'")
        admin_client.query("GRANT ALL PRIVILEGES ON `#{db_name}`.* TO '#{db_user}'@'localhost'")
        admin_client.query("FLUSH PRIVILEGES")

        # 保存数据库连接信息到公司记录
        @datos_empresa.update!(
          db_name: db_name,
          db_username: db_user,
          db_password: db_pass
        )

        # 初始化新数据库的表结构(执行迁移)
        ActiveRecord::Base.establish_connection(
          adapter: 'mysql2',
          host: 'localhost',
          database: db_name,
          username: db_user,
          password: db_pass
        )
        # 推荐调用Rails迁移任务初始化表结构
        system("RAILS_ENV=#{Rails.env} DATABASE_URL=mysql2://#{db_user}:#{db_pass}@localhost/#{db_name} rails db:migrate")

        format.html { redirect_to @datos_empresa, notice: '公司创建成功,专属数据库已初始化。' }
        format.json { render :show, status: :created, location: @datos_empresa }
      rescue Mysql2::Error => e
        # 回滚操作:删除已创建的数据库和用户
        admin_client.query("DROP DATABASE IF EXISTS `#{db_name}`")
        admin_client.query("DROP USER IF EXISTS '#{db_user}'@'localhost'")
        @datos_empresa.destroy
        format.html { render :new, alert: "创建失败:#{e.message}" }
        format.json { render json: { error: e.message }, status: :unprocessable_entity }
      ensure
        admin_client.close
      end
    else
      format.html { render :new }
      format.json { render json: @datos_empresa.errors, status: :unprocessable_entity }
    end
  end
end

2. 认证后动态切换数据库

通过控制器前置钩子,在用户认证后根据company_id切换数据库,避免全局连接污染:

# 在ApplicationController中添加
before_action :switch_company_database, if: :current_user
after_action :restore_default_database, if: :current_user

private

def switch_company_database
  company = DatosEmpresa.find(current_user.company_id)
  # 切换当前请求的数据库连接
  ActiveRecord::Base.establish_connection(
    adapter: 'mysql2',
    host: 'localhost',
    database: company.db_name,
    username: company.db_username,
    password: company.db_password
  )
end

def restore_default_database
  # 请求结束后恢复默认数据库连接
  ActiveRecord::Base.establish_connection(Rails.env.to_sym)
end

关键注意事项

  • SQL注入防护:始终使用反引号包裹数据库名/用户名,避免直接拼接用户输入
  • 权限最小化:为每个公司创建仅能访问自身数据库的用户,禁止使用超级账号
  • 连接池管理:多公司场景下需调整Rails连接池大小,避免连接耗尽
  • 数据隔离:缓存、会话等资源需按公司隔离,防止跨公司数据泄露
  • 备份策略:为每个公司数据库单独配置备份,避免单库备份影响所有公司
  • 迁移同步:后续新增表结构时,需批量同步到所有公司的数据库

内容的提问来源于stack exchange,提问作者FelipeBP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 22:20:42