当前位置: 首页 > 知识库问答 >
问题:

Laravel 6-使用嵌套租户设置多租户应用程序

邵锐
2023-03-14

我目前正试图找出为我的系统设置多租户的最佳方法。我面临的问题是,租户并不总是必须是子域,但可以作为子域的一部分进行设置,子域可以有多个租户。我似乎在网上找不到任何东西可以帮助我在Laravel 6中进行设置。

系统要求:

  • 一台服务器可以有许多子域

系统必须设置一个数据库,该数据库将使用tenant_id来确定哪些数据属于租户。

我目前正在以以下结构将所有子域数据存储在“subdomains”表中:

id 
subdomain (unique)
status 
nested_tenants (yes/no)

其中列nested_tenants确定子域本身是一个租户(=0)还是有多个租户(=1)。

如果子域确实有嵌套的租户,那么我们将所有这些都存储在具有结构的表中:

id
subdomain (the sub-domain it belongs to)
tenant (the tenant - unique field)
name
status

我们从这个表中设置tenant_id=tenant

如果我们有子域的嵌套租户,那么在用户登录之前,我们无法确定当前租户是什么。我们必须从用户详细信息中获取租户id。

我目前的设置:

我一直在关注这篇文章,并设置了以下内容:

我有两个模型子域,租户

路线/网络。php:

Route::group([
    'middleware' => \App\Http\Middleware\IdentifySubdomain::class,
    'as' => 'tenant:',
    'namespace' => 'Tenant'
], function () {
    // custom auth routes
    Route::get('/login', 'Auth\LoginController@index')->name('login');

    Route::post('/login', 'Auth\LoginController@login');

    Route::get('/home', 'HomeController@index')->name('home');
});

中间件识别子域:

class IdentifySubdomain
{

    protected $tenantManager;

    public function __construct(TenantManager $tenantManager) {
        $this->tenantManager = $tenantManager;
    }

    public function handle($request, Closure $next)
    {
        /** need to check whether subdomain is valid 
        * if subdomain is valid return the request page else error message. 
        * if subdomain is true it will check the nested_tenants value from db. 
        * if nested_tenants is false it will set the tenant to current subdomain 
        * else the tenant is not set yet. 
        */ 
        // get host domain and subdomain domain
        $host = $request->getHost();

        // get subdomain position
        $pos = strpos($host, env('TENANT_DOMAIN'));
        $subdomain = substr($host, 0, $pos - 1);

        if ($pos !== false && $this->tenantManager->checkSubdomain($subdomain)) {
            return $next($request);
        }

        throw new NotFoundHttpException;
    }
}

租户管理员:

class TenantManager {

    private $tenant;

    public function setTenant(?Tenant $tenant) {
        $this->tenant = $tenant;
        return $this;
    }

    public function getTenant(): ?Tenant {
        return $this->tenant;
    }

    public function loadTenant(string $identifier): bool {

        $tenant = Tenant::query()->where('tenant', '=', $identifier)->first();
        if ($tenant) {
            $this->setTenant($tenant);
            return true;
        }

        return false;
    }

    public function checkSubdomain(string $identifier) : bool {
        $subdomain = Subdomain::query()->where('subdomain', '=', $identifier)->first();

        if ($subdomain) {
            if ($subdomain->nested_tenants) {
                // tenant not found yet so do not set tenant
                return true;
            } else {
                return $this->loadTenant($identifier);

            }            
        } 

        return false;
    }
}

服务供应商

class TenantServiceProvider extends ServiceProvider
{
    public function register()
    {        
        $manager = new TenantManager;
        $this->app->instance(TenantManager::class, $manager);
        $this->app->bind(Tenant::class, function() use ($manager) {   
            $tenant = $manager->getTenant();
            if ($tenant === null) {
                return new Tenant;
            }        
            return $manager->getTenant();
        });

    }
}

登录控制器:

class LoginController extends Controller
{    
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
    ...
    public function login(Request $request, Tenant $tenant) {
        $request->validate([
            'email' => ['required', 'email', 'max:255'],
            'password' => ['required'],
        ]);
        $credentials = $request->only('email', 'password');
        $credentials['status'] = 1;
        if ($tenant->id) {    
            $credentials['tenant_id'] = $tenant->tenant;            
        } 
        if (Auth::attempt($credentials)) {                           
            return redirect()->intended('home');           
        } 

        return Redirect::to('login')->withSuccess('Login Failed! You entered invalid credentials');
    }
    ...
}

问题

我主要担心的是,我不觉得这是跟踪房客的最佳方法。我需要它,这样一旦租户设置好,我就可以在整个应用程序中使用它,而不总是首先检查用户是否首先经过身份验证,然后获取租户。我目前添加租户$租户到控制器方法中,我需要租户相关的数据,但是有更好的方法吗?

任何关于如何改进当前设置的建议都会有所帮助。

共有1个答案

洪俊捷
2023-03-14

我认为应该实现Traits来添加租户约束,例如:在模型中:

BelongsToTenantModelTrait{

public static function bootBelongsToTenantModelTrait(){
       static::addGlobalScope(function ($model){
            model->where('tenant_id',auth()->user()->tenant->id);
            //Or any similar logic
        });
} 

如果需要的话,向控制者提供其他特征
如果需要,还可以添加AuthTenant等中间软件
我认为这种方式应该尽可能地解耦与租户相关的逻辑<让我知道你的想法。

 类似资料:
  • 我必须在j2ee中开发一个多租户SaaS应用程序,从Iaas和PaaS开始实现三种云模型,我选择了openstack和openshift origin。SaaS应用程序的第一个标准是多租户,我知道有三种方法来实现它——单独的数据库——共享数据库,单独的模式——共享数据库,共享模式。我在这里迷失了方向,因为许多框架,比如ATHENA,ORM,比如hibernate,还有TOPLINK。我需要帮助了解

  • 我已经安装了keycloak-angular包,我使用它的方式如下:https://www.npmjs.com/package/keycloak-angular 问题是,在我的应用程序中,我希望有多租户。这意味着在应用程序加载期间不知道领域名。 在说明中,它说“KeycloakService应该在应用程序加载期间使用APP_INITIALIZER标记初始化”,问题是该领域是由用户给定的,在应用程序

  • 当我们使用 Tenant2 登录系统,并尝试创建一个新用户 User2。 你不会收到任何错误,但令人惊讶的是,你不能在列表中看到新创建的用户, User2 发生了什么? 由于我们在迁移类中,把 TenantId 的值设置为 1,现在 User2 的 TenantId 也是 1,并且它也是 主租客。 我们需要在已登录的用户中把新用户的 TenantId 设置为同样的值。 修改 UserReposit

  • 我正在使用Java、Spring、Struts2和Hibernate设计一个多租户SaaS Web应用程序。经过一些研究,我选择在共享数据库、共享模式、共享表的方法中实现多租户。并用tenantid标记每个db行。 我已经重写了我的应用程序,所以管理者和DAO将把tenantId作为一个参数,只为正确的数据库资源服务。 当获取信息时,这对所有视图来说都是完美的。也用于创建新的东西(使用登录的用户t

  • 问题内容: 在Spring 3应用程序中,我试图通过Hibernate 4的本机MultiTenantConnectionProvider和CurrentTenantIdentifierResolver实现多租户。我发现在Hibernate 4.1.3 中存在此问题,但是我正在运行4.1.9并仍收到类似的异常: 以下是相关代码。在I中,我现在只写了一些简单的代码,每次都只返回一个新的连接,并且在这

  • 我正在使用spring 3.0开发一个多租户应用程序,需要根据租户将文件上传到FTP服务器或从FTP服务器下载文件。对于每个租户,我们在同一个FTP服务器中有不同的FTP位置。您能帮助我根据租户使用spring 3配置/更改FTP位置吗。?