В настоящее время я пытаюсь найти наилучший способ настройки мультитенантности для моей системы. Проблема, с которой я сталкиваюсь, заключается в том, что арендатор не всегда должен быть поддоменом, но может быть настроен как часть поддомена, где у субдомена может быть несколько арендаторов. Кажется, я не могу найти в Интернете ничего, что помогло бы мне настроить это в 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
в методы контроллера, где мне нужны данные, связанные с арендатором, но есть ли лучший способ сделать это?
Любой совет о том, как я могу улучшить свои текущие настройки, был бы полезен.