【问题标题】:Multi-tenant rails application with a domain/subdomain architecture like Shopify具有域/子域架构的多租户 Rails 应用程序,例如 Shopify
【发布时间】:2021-08-08 10:34:04
【问题描述】:

我正在构建一个类似于 Shopify 的多租户 rails 应用程序。每当客户注册Account 时,他们将在customer.myapp.com 创建一个子域。此子域返回一个包含与其帐户相关的数据的视图(包括 /admin 区域)。

现在,在某些情况下,客户希望使用自己的自定义域,而不是为他们创建的子域。我需要如何调整我的 rails 路由和控制器以返回包含与客户帐户相关的数据的视图,不仅基于子域,而且基于自定义域或子域?

这就是我设置config/routes.rb 来处理子域的方式:

# config/routes.rb
Rails.application.routes.draw do
  ...
  constraints(SubdomainRequired) do
    scope module: :accounts do
      ...
    end
  end
end

app/constraints/subdomain_required.rb 看起来像这样:

# app/constraints/subdomain_required.rb
class SubdomainRequired
  def self.matches?(request)
    request.subdomain.present? && request.subdomain != "www"
  end
end

最后我的app/controllers/accounts/base_controller.rb 根据请求子域设置当前帐户:

# app/controllers/accounts/base_controller.rb
module Accounts
  class BaseController < ApplicationController
    before_action :set_current_account
    ...
    def set_current_account
      if request.subdomain.present?
        @current_account = Account.find_by(subdomain: request.subdomain)
        render_404 if !@current_account
      end
    end

    def render_404
      render file: "#{Rails.root}/public/404.html", status: 404
    end
    ...
  end
end

对于 DNS 设置,我采用了 Shopify 等公司的方法。看起来他们让他们的客户创建了一个指向他们应用程序的主 IP 地址的 A 记录。他们让他们为“www”子域创建一个 CNAME,它指向一个子域 (sites.myapp.com),设置为应用程序子域的 CNAME(在我的情况下由 DigitalOcean 的应用程序平台管理)。

如果 DNS 设置按预期工作,我想知道当客户登陆我的应用程序时如何将请求指向正确的方向。因此,例如,如果流量达到customer.com 并且它有一条指向我的服务器主 IP 的 A 记录,加上一个用于 sites.myapp.com 的 CNAME,我如何处理请求并将其重定向到正确的子域,同时让用户保持自定义域?

这些是我目前检查过的资源:

感谢您的帮助!

【问题讨论】:

  • 欢迎来到 StackOverflow。不幸的是,我无法帮助您,但我认为您的问题问得很好并且格式正确(大多数新成员并非如此)。继续!
  • 感谢@Hecke29 欢迎我。我很高兴我的问题问得很好并且格式正确。事实上,在此期间,我自己解决了大部分问题,我会在测试完我的解决方案后立即对其进行更新。

标签: ruby-on-rails subdomain multi-tenant


【解决方案1】:

好的,这就是我在此期间想到的。

首先,如上所述,我离开了我的config/routes.rb。那里的一切似乎都按预期工作。

然后我调整app/constraints/subdomain_required.rb 不仅检查子域,还检查请求的域部分:

# app/constraints/subdomain_required.rb
class SubdomainRequired
  def self.matches?(request)
    request.subdomain.present? && request.subdomain != "www" || 
      request.domain.present? && request.domain != ENV["APP_DOMAIN"]
  end
end

最后但同样重要的是,我将app/controllers/accounts/base_controller.rb 中的set_current_account 方法更改为if 语句的怪物:

# app/controllers/accounts/base_controller.rb
module Accounts
  class BaseController < ApplicationController
    before_action :set_current_account
    ...
    def set_current_account
      @current_account = if request.domain.present? && request.domain != ENV["APP_DOMAIN"]
                           # Do custom domain stuff
                           if request.subdomain == "www" || !request.subdomain.present?
                             # Set current account by custom domain, e.g. custom.com
                             Account.find_by(domain: request.domain)
                           else
                             # Set current account by custom subdomain, e.g. site.custom.com
                             Account.find_by(domain: "#{request.subdomain + "." + request.domain}")
                           end
                         elsif request.subdomain.present? && request.subdomain != "www"
                           # Set current account by subdomain, e.g. site.myapp.com
                           Account.find_by(subdomain: request.subdomain)
                         end
      render_404 unless @current_account
    end
    ...
  end
end

到目前为止,我仍在测试我的解决方案。可能需要更多的调整,肯定需要一点重构,但它似乎正在工作。

我必须解决的另一个(意外)问题是整个 DNS 设置。与 heroku 类似,DigitalOcean 的应用平台不支持静态 IP 地址,因此客户端无法创建指向我的应用 IP 地址的 A 记录,因为它可能随时更改。

现在我已经使用 NGINX 设置了一个转发代理(你是这么称呼它的吗?),基于以下 Heroku 示例: https://help.heroku.com/YTWRHLVH/how-do-i-make-my-nginx-proxy-connect-to-a-heroku-app-behind-heroku-ssl

希望对寻求类似解决方案的任何人有所帮助。

如果我需要详细说明,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-31
    • 2011-08-17
    • 1970-01-01
    • 2015-01-13
    • 1970-01-01
    • 1970-01-01
    • 2021-04-04
    • 1970-01-01
    相关资源
    最近更新 更多