spatie/laravel-multitenancy 多租户组件,它可以帮助你快速实现 Laravel 多租户应用。比 tenancy/tenancy 更灵活。
数据库支持单库模式和1中心库+多租户库模式。多租户库模式下,用户表和用户tokens表可选择放在中心库,也可以放在租户库。
安装
1 2 3 4 5
| composer require spatie/laravel-multitenancy
php artisan vendor:publish --provider="Spatie\Multitenancy\MultitenancyServiceProvider" --tag="multitenancy-config"
php artisan vendor:publish --provider="Spatie\Multitenancy\MultitenancyServiceProvider" --tag="multitenancy-migrations"
|
1 2 3 4 5 6 7
| Schema::create('tenants', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('domain')->unique(); $table->string('database')->unique(); $table->timestamps(); });
|
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| return [
'tenant_finder' => \App\Multitenancy\TenantFinder\HeaderTenantFinder::class,
'tenant_artisan_search_fields' => [ 'id', 'tenant_code', 'database' ],
'switch_tenant_tasks' => [ \Spatie\Multitenancy\Tasks\PrefixCacheTask::class, \Spatie\Multitenancy\Tasks\SwitchTenantDatabaseTask::class, \Spatie\Multitenancy\Tasks\SwitchRouteCacheTask::class, ],
'tenant_model' => \App\Models\Landlord\Tenant::class,
'queues_are_tenant_aware_by_default' => true,
'tenant_database_connection_name' => 'tenant',
'landlord_database_connection_name' => 'landlord',
'current_tenant_container_key' => 'currentTenant',
'shared_routes_cache' => false,
'actions' => [ 'make_tenant_current_action' => MakeTenantCurrentAction::class, 'forget_current_tenant_action' => ForgetCurrentTenantAction::class, 'make_queue_tenant_aware_action' => MakeQueueTenantAwareAction::class, 'migrate_tenant' => MigrateTenantAction::class, ],
'queueable_to_job' => [ SendQueuedMailable::class => 'mailable', SendQueuedNotifications::class => 'notification', CallQueuedClosure::class => 'closure', CallQueuedListener::class => 'class', BroadcastEvent::class => 'event', ],
'tenant_aware_jobs' => [ ],
'not_tenant_aware_jobs' => [ ], ];
|
自定义租户查找器
通过 X-Tenant-ID header 来确定当前租户。
在 config/multitenancy.php 中配置 tenant_finder 为这个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php namespace App\Multitenancy\TenantFinder;
use App\Models\Landlord\Tenant; use Illuminate\Http\Request; use Spatie\Multitenancy\TenantFinder\TenantFinder;
class HeaderTenantFinder extends TenantFinder {
public function findForRequest(Request $request): \App\Models\Landlord\Tenant|null { $id = (int)trim($request->headers->get('X-Tenant-ID', ''));
if ($id === 0 || (!$tenant = Tenant::find($id))) { abort(400, '无效的商户ID'); }
return $tenant; } }
|
模型集成
Spatie\Multitenancy\Models\Tenant 类有切换租户的能力。
Spatie\Multitenancy\Models\Concerns\UsesLandlordConnection 自动把模型数据库连接切换到中心库。
Spatie\Multitenancy\Models\Concerns\UsesTenantConnection 自动把模型数据库连接切换到租户库。
租户分配模型通过继承 Spatie\Multitenancy\Models\Tenant 类,使用 Spatie\Multitenancy\Models\Concerns\UsesLandlordConnection Trait来集成。
中心库模型使用 Spatie\Multitenancy\Models\Concerns\UsesLandlordConnection Trait 集成。
租户模型使用 Spatie\Multitenancy\Models\Concerns\UsesTenantConnection Trait 集成。
授权
App\Http\Kernel::$middlewareGroups 中添加:
1 2 3 4 5 6 7
| 'tenant' => [ \Spatie\Multitenancy\Http\Middleware\NeedsTenant::class, ],
|
在路由中使用中间件:
1 2 3 4 5 6
| Route::middleware(['tenant', 'auth:sanctum'])->group(function () { Route::get('/api/xxx', function () { $user = request()->user(); }); });
|
使用了 tenant 中间件后,自动调用 HeaderTenantFinder::findForRequest() 查找到租户,并切换到租户数据库。
tenant 和 auth:sanctum 放的顺序很重要。
如果用户表放在中心库,那么 auth:sanctum 中间件必须放在 tenant 中间件之前。
如果用户表放在租户库,那么 tenant 中间件必须放在 auth:sanctum 中间件之前,并且还要设置中间件的优先级,把
\Spatie\Multitenancy\Http\Middleware\NeedsTenant::class, 放到 App\Http\Kernel::$middlewarePriority 最前面。
App\Http\Kernel 没看到 $middlewarePriority,则到父类连其列表值一起复制下来。
临时切换切换到租户数据库
$tenant->execute(closure):临时切换、自动恢复、安全隔离。
只在闭包内生效,执行完自动切回原来的租户 / 主库,不会污染全局状态。
1 2 3 4
| $tenant = Spatie\Multitenancy\Models\Tenant::find(1); $tenant->execute(function() { });
|
永久切换切换到租户数据库
$tenant->makeCurrent():永久切换(直到手动改)、全局生效、需手动恢复。
一旦调用,整个后续请求生命周期都用这个租户库,直到你 forgetCurrent() 或切别的租户。
1 2 3
| $tenant = Spatie\Multitenancy\Models\Tenant::find(1);
$tenant->makeCurrent();
|
最简单的集成方式
上面的集成方式比较优雅,通过子域名或header来集成。
最简单的集成方式是通过URL参数来集成。
这个方式不需要实现 App\Multitenancy\TenantFinder\TenantFinder 类。
在 config/multitenancy.php 中配置 tenant_finder 为 ``。
不需要添加切换租户的路由中间件。
例如:
1 2 3 4 5
| Route::get('/api/{tenant}/other', function ($tenant) { $tenant->execute(function() { }); });
|
这个方式的缺点是不优雅,所有路由都需要添加租户参数、所有调用租户数据库的地方都是通过 $tenant->execute(closure) 来切换租户。