具有 domain/subdomain 架构的多租户 rails 应用程序,例如 Shopify

Multi-tenant rails application with a domain/subdomain architecture like Shopify

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

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

这就是我设置 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,我如何处理请求并将其重定向到正确的子域,同时保持用户在他们的自定义域上?

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

感谢您的帮助!

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

首先,我如上所述离开了 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 地址,因此客户端无法仅创建 A 记录,指向我的应用程序的 IP 地址,因为它可能随时更改。

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

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

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