Laravel 6 - 使用嵌套租户设置多租户应用程序
Laravel 6 - setting up a multi-tenant application with nested tenants
我目前正在尝试找出为我的系统设置多租户的最佳方法。我面临的问题是,租户并不总是必须是子域,但可以设置为子域的一部分,其中子域可以有多个租户。我似乎无法在网上找到任何可以帮助我在 Laravel 6.
中进行设置的内容
系统要求:
- 一个服务器可以有多个子域
- 一个子域可以是一个租户
- 一个子域可以有多个租户
- 一个租户可以有多个用户
- 一个租户可以有不同的功能
系统必须使用单个数据库设置,该数据库将使用 tenant_id 确定哪些数据属于租户。
我目前将所有子域数据存储在具有以下结构的 table "subdomains" 中:
id
subdomain (unique)
status
nested_tenants (yes/no)
其中 nested_tenants
列确定子域本身是租户 (=0) 还是有多个租户 (=1)。如果子域没有嵌套租户,那么我们设置 tenant_id=subdomain
如果子域确实有嵌套租户,那么我们将所有这些存储在 table 中,结构为:
id
subdomain (the sub-domain it belongs to)
tenant (the tenant - unique field)
name
status
然后我们从这个 table.
设置 tenant_id=tenant
如果我们有子域的嵌套租户,那么在用户登录之前我们无法确定当前租户是什么。我们必须从用户详细信息中获取 tenant_id。
我当前的设置:
我一直在关注这个 article 并设置了以下内容:
我有两个模型
子域名,
租户
Routes/web.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');
});
中间件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;
}
}
租户管理器:
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
添加到我需要租户相关数据的控制器方法中,但是有没有更好的方法来解决这个问题?
任何关于如何改进当前设置的建议都会对我有所帮助。
我认为您应该实施 Traits 来添加租户约束,例如:
在型号中:
BelongsToTenantModelTrait{
public static function bootBelongsToTenantModelTrait(){
static::addGlobalScope(function ($model){
model->where('tenant_id',auth()->user()->tenant->id);
//Or any similar logic
});
}
如果需要,控制器的其他特征。
如果需要,您还可以添加 AuthTenant 等中间件。
我觉得这种方式应该尽可能去耦合租户相关的逻辑。
让我知道你的想法。
我目前正在尝试找出为我的系统设置多租户的最佳方法。我面临的问题是,租户并不总是必须是子域,但可以设置为子域的一部分,其中子域可以有多个租户。我似乎无法在网上找到任何可以帮助我在 Laravel 6.
中进行设置的内容系统要求:
- 一个服务器可以有多个子域
- 一个子域可以是一个租户
- 一个子域可以有多个租户
- 一个租户可以有多个用户
- 一个租户可以有不同的功能
系统必须使用单个数据库设置,该数据库将使用 tenant_id 确定哪些数据属于租户。
我目前将所有子域数据存储在具有以下结构的 table "subdomains" 中:
id
subdomain (unique)
status
nested_tenants (yes/no)
其中 nested_tenants
列确定子域本身是租户 (=0) 还是有多个租户 (=1)。如果子域没有嵌套租户,那么我们设置 tenant_id=subdomain
如果子域确实有嵌套租户,那么我们将所有这些存储在 table 中,结构为:
id
subdomain (the sub-domain it belongs to)
tenant (the tenant - unique field)
name
status
然后我们从这个 table.
设置tenant_id=tenant
如果我们有子域的嵌套租户,那么在用户登录之前我们无法确定当前租户是什么。我们必须从用户详细信息中获取 tenant_id。
我当前的设置:
我一直在关注这个 article 并设置了以下内容:
我有两个模型 子域名, 租户
Routes/web.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');
});
中间件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;
}
}
租户管理器:
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
添加到我需要租户相关数据的控制器方法中,但是有没有更好的方法来解决这个问题?
任何关于如何改进当前设置的建议都会对我有所帮助。
我认为您应该实施 Traits 来添加租户约束,例如: 在型号中:
BelongsToTenantModelTrait{
public static function bootBelongsToTenantModelTrait(){
static::addGlobalScope(function ($model){
model->where('tenant_id',auth()->user()->tenant->id);
//Or any similar logic
});
}
如果需要,控制器的其他特征。
如果需要,您还可以添加 AuthTenant 等中间件。
我觉得这种方式应该尽可能去耦合租户相关的逻辑。
让我知道你的想法。