Laravel 6 - настройка мультитенантного приложения с вложенными арендаторами - PullRequest
0 голосов
/ 05 марта 2020

В настоящее время я пытаюсь найти наилучший способ настройки мультитенантности для моей системы. Проблема, с которой я сталкиваюсь, заключается в том, что арендатор не всегда должен быть поддоменом, но может быть настроен как часть поддомена, где у субдомена может быть несколько арендаторов. Кажется, я не могу найти в Интернете ничего, что помогло бы мне настроить это в Laravel 6 .

Системные требования:

  • Сервер может иметь много поддоменов
  • Субдомен может быть арендатором
  • Субдомен может иметь много арендаторов
  • Арендатор может иметь много пользователей
  • арендатор может иметь различные функции

Система должна быть настроена на одну базу данных, которая будет использовать tenant_id для определения того, какие данные принадлежат арендатору.

В настоящее время я храню все данные поддоменов в таблице «поддоменов» со следующей структурой:

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

, где столбец nested_tenants определяет, является ли поддомен арендатором (= 0 ) или имеет несколько арендаторов (= 1). если у субдомена нет вложенных арендаторов, тогда мы устанавливаем tenant_id=subdomain

, если у субдомена действительно есть вложенные арендаторы, то мы сохраняем все это в таблице со структурой:

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

и мы устанавливаем tenant_id=tenant из этой таблицы.

если у нас есть вложенные арендаторы для субдомена, то мы не можем определить, каким является текущий арендатор, пока пользователь не войдет в систему. Нам нужно будет получить 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');
});

Middleware IdentifySubdomain :

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;
    }
}

TenantManager :

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');
    }
    ...
}

Проблемы

Моя главная проблема в том, что я не чувствую, что это лучший подход к отслеживанию арендатора. Мне нужно, чтобы после установки арендатора я мог использовать его во всем приложении, не всегда сначала проверяя, прошел ли пользователь проверку подлинности, а затем получил арендатора. В настоящее время я добавляю Tenant $tenant в методы контроллера, где мне нужны данные, связанные с арендатором, но есть ли лучший способ сделать это?

Любой совет о том, как я могу улучшить свои текущие настройки, был бы полезен.

1 Ответ

0 голосов
/ 05 марта 2020

Я думаю, вы должны реализовать черты, чтобы добавить ограничения арендатора, например: в моделях:

BelongsToTenantModelTrait{

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

и другие черты для контроллеров, если это необходимо. Вы также можете добавить промежуточное программное обеспечение, например AuthTenant, если это необходимо. Я думаю, что этот способ должен максимально отделить логику, связанную с арендатором c. Дайте мне знать, что вы думаете.

...