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) 来切换租户。