参考:
https://tenancyforlaravel.com/docs/v3/quickstart/ 一步一步来:
整个流程大概如下:
0,配置好租户的事件和数据建表,生成租户,触发各种bootstrap初始化。
1,租户域名–》识别租户–》切换租户数据库–》切换各种资源–》运行应用–》运行相应任务命令
2,主域名–》识别管理中心–》切换主数据库–》运行管理后台–》管理租户
第一:先安装laravel-shop
源码:
https://github.com/summerblue/laravel-shop/tree/L05_8.x
安装后,简单运行一下是否正常,然后我们接下来把它改造成SaaS.
第二:安装 archtechx/tenancy
源码:
https://github.com/archtechx/tenancy
composer require stancl/tenancy
php artisan tenancy:install
安装后生成:migrations, config file, route file and a service provider
然后 ,执行数据库迁移,生成 tenants 租户表,domains 域名表:
php artisan migrate
然后,注册 tenant包的 服务提供者,service provider inconfig/app.php.
一般来说,会继承原来的Tenant model, 进一步修改,同时也要在config/tenancy.php 配置好 Model:
'tenant\_model' => \App\Models\Tenant::class,
这样!就算安装好了!下一步,我们要配置好 SaaS多租户的功能。
第三,配置租户生成事件(Events):
当新建租户的时候,会触发事件任务,例如执行 生成数据库CreateDatabase,迁移数据migration,填充数据seeder等等,在文件TenancyServiceProvider这里是配置 任务:
public function events()
{
return [
// Tenant events
Events\CreatingTenant::class => [],
Events\TenantCreated::class => [
JobPipeline::make([
Jobs\CreateDatabase::class,
Jobs\MigrateDatabase::class,
//Jobs\SeedDatabase::class,
CreateFrameworkDirectoriesForTenant::class, //建立租户文件夹
UpdateAdminMenuForTenant::class //更新租户数据表
// Your own jobs to prepare the tenant.
// Provision API keys, create S3 buckets, anything you want!
])->send(function (Events\TenantCreated $event) {
return $event->tenant;
})->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
],
第四,配置管理中心路由(Central routes):
在
app/Providers/RouteServiceProvider.php 修改路由,这样就可以进入管理中心的路由,而不是进入租户的路由:
public function boot()
{
$this->configureRateLimiting();
$this->mapWebRoutes();
$this->mapApiRoutes();
}
protected function mapWebRoutes()
{
foreach ($this->centralDomains() as $domain) {
Route::middleware('web')
->domain($domain)
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
}
protected function mapApiRoutes()
{
foreach ($this->centralDomains() as $domain) {
Route::prefix('api')
->domain($domain)
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}
protected function centralDomains(): array
{
return config('tenancy.central_domains');
}
第五,配置租户路由(Central routes):
在routes/tenant.php 配置租户的路由,
PreventAccessFromCentralDomains的Middleware中间件是过滤掉不准主域名进入。InitializeTenancyByDomain是识别租户。
Route::middleware([
'web',
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
])->group(function () {
Route::get('/', function () {
return 'This is your multi-tenant application.
The id of the current tenant is ' . tenant('id');
});
});
第六,配置数据迁移Migrations
手动把database/migrations 相关需要迁移的文件 复制到
database/migrations/tenant 里面。当执行租户数据迁移时候就会自动执行,生成租户需要的数据表:
php artisan tenants:migrate
最后,生成租户测试:
Tenant 是生成租户,而Domain是绑定租户Tenant,访问域名进行识别租户:
$ php artisan tinker
>>> $tenant1 = App\Models\Tenant::create(['id' => 'foo']);
>>> $tenant1->domains()->create(['domain' => 'foo.localhost']);
>>>
>>> $tenant2 = App\Models\Tenant::create(['id' => 'bar']);
>>> $tenant2->domains()->create(['domain' => 'bar.localhost']);
这时候,你可以浏览器访问 ‘foo.localhost’ ,就能够进入 租户的应用前端了。
(注:要在hosts绑定域名和本地ip)
租户的应用前端:
租户后台:
管理中心后台:
同时,可以在代码里面对租户进行如下操作:
App\Models\Tenant::all()->runForEach(function () {
App\Models\User::factory()->create(); // 切换租户,执行操作
});
完成!以上就是多租户SaaS的基本安装和配置。下面具体说说配置的知识点。
注:配置的知识点:
1,Config/tenancy.php 配置
'tenant_model' => \App\Models\Tenant::class, //配置好 Tenant和Domain的class
'central_domains' => [
str_replace(['https//', 'http//'], '', env('APP_URL')),
], // 配置好 管理中心的URL
//租户识别后,启动资源隔离:
'bootstrappers' => [
Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper::class,
Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper::class,
Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper::class,
Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class,
// Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed
],
2, TenancyServiceProvider.php 配置:
** 2.1 Events\TenantCreated (租户生成时)配置:**
租户生成时,具体执行的生成任务配置,这里举例几个:
····
public function events()
{
return [
// Tenant events
Events\CreatingTenant::class => [],
Events\TenantCreated::class => [
JobPipeline::make([
Jobs\CreateDatabase::class, //生成数据库
Jobs\MigrateDatabase::class, //迁移数据表
//Jobs\SeedDatabase::class, //填充数据
CreateFrameworkDirectoriesForTenant::class, //生成租户文件夹
UpdateAdminMenuForTenant::class //更新租户数据内容
// Your own jobs to prepare the tenant.
// Provision API keys, create S3 buckets, anything you want!
])->send(function (Events\TenantCreated $event) {
return $event->tenant;
})->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
],
Events\SavingTenant::class => [],
·····
例如,更新数据表内容如下:
例如,生成租户文件夹如下:
** 2.2 boot() 启动租户任务配置:**
public function boot()
{
$this->bootEvents(); //启动事件监听
$this->mapRoutes(); //启动路由监听
$this->makeTenancyMiddlewareHighestPriority();
//以下是我们添加的 自定义配置
InitializeTenancyByDomain::$onFail = function () {
return redirect(env('APP_URL')); //租户访问失败,跳转主访问
};
TenantAssetsController::$tenancyMiddleware = InitializeTenancyByDomainOrSubdomain::class; // 静态资源相关.
// 租户自定义配置.
// @see https://tenancyforlaravel.com/docs/v3/features/tenant-config
TenantConfig::$storageToConfigMap = [
// Do whatever you want.
];
DomainTenantResolver::$shouldCache = true; //租户路由缓存配置
}
protected function mapRoutes()
{
if (file_exists(base_path('routes/tenant.php'))) {
Route::namespace(static::$controllerNamespace)
->group(base_path('routes/tenant.php'));
}
}
3,migration 和 seeder 初始化数据 配置
在config/tenancy.php 可以配置相关参数,我的习惯是 不要seeder,直接把有需要的seeder做成 一个migration,直接执行migration。
/**
* Parameters used by the tenants:migrate command.
*/
'migration_parameters' => [
'--force' => true, // This needs to be true to run migrations in production.
'--path' => [database_path('migrations/tenant')],
'--realpath' => true,
],
/**
* Parameters used by the tenants:seed command.
*/
'seeder_parameters' => [
'--class' => 'DatabaseSeeder', //'TenantDatabaseSeeder', // root seeder class
//'--force' => true,
],
4,路由配置:
这里需要的路由配置有:中心应用路由,管理中心路由,租户应用路由,租户管理后台路由。
4.1 管理中心的路由配置 app/Admin/routes.php例子如下 :
/**
* 超级管理员可以通过此路由进入租户后台.
*/
Route::group([
'prefix' => config('admin.route.prefix'),
'namespace' => config('admin.route.namespace'),
'middleware' => config('admin.route.middleware'),
'domain' => config('tenancy.central_domains')[0], //限定管理中心域名才能进入
], function (Router $router) {
// 租户管理
$router->resource('/tenant', 'TenantController');
// 域名管理
$router->resource('/domain', 'DomainController')->only(['index', 'destroy', 'show']);
$router->get('/', 'HomeController@index');
$router->get('users', 'UsersController@index');
$router->get('products', 'ProductsController@index');
});
4.2 租户管理中心的路由配置 app/Admin/routes.php例子如下 :
/**
* 租户管理员可以通过此路由进入租户后台.
*/
Route::middleware([
'web','admin', // 要经过管理员登录验证
CheckTenantForMaintenanceMode::class, //检查是否维护状态
ScopeSessions::class,
InitializeTenancyByDomain::class, //识别租户,执行切换资源
PreventAccessFromCentralDomains::class, //防止管理中心访问的混入
])
->prefix(config('admin.route.prefix'))
->namespace(config('admin.route.namespace'))
->group(function (Router $router) {
$router->get('/', 'HomeController@index');
$router->get('users', 'UsersController@index');
$router->get('products', 'ProductsController@index');
$router->get('products/create', 'ProductsController@create');
$router->post('products', 'ProductsController@store');
$router->get('products/{id}/edit', 'ProductsController@edit');
$router->put('products/{id}', 'ProductsController@update');
// 开启上帝模式,管理中心是可以直接访问租户后台
$router->get('/god/{token}', function ($token) {
return UserImpersonation::makeResponse($token);
});
});
4.3 中心应用路由的路由配置 routes/web.php例子如下 :
//就是普通平时的路由,不需要解释
Route::get('/', 'PagesController@root')->name('root');
Auth::routes();
// 在之前的路由里加上一个 verify 参数
Auth::routes(['verify' => true]);
Route::get('products/favorites', 'ProductsController@favorites')->name('products.favorites');
// auth 中间件代表需要登录,verified中间件代表需要经过邮箱验证
Route::group(['middleware' => ['auth', 'verified']], function() {
Route::get('user_addresses', 'UserAddressesController@index')->name('user_addresses.index');
4.4 租户应用的路由配置 routes/tenant.php例子如下 :
Route::middleware([
'web',
InitializeTenancyByDomain::class, //识别租户,切换资源
PreventAccessFromCentralDomains::class, //防止中心应用的访问混入
])->group(function () {
Route::get('/', 'PagesController@root')->name('root');
Auth::routes(); // 按正常的用户验证就可以
// 在之前的路由里加上一个 verify 参数
Auth::routes(['verify' => true]);
Route::get('products/favorites', 'ProductsController@favorites')->name('products.favorites');
// auth 中间件代表需要登录,verified中间件代表需要经过邮箱验证
Route::group(['middleware' => ['auth', 'verified']], function() {
Route::get('user_addresses', 'UserAddressesController@index')->name('user_addresses.index');
·····
····
路由知识点:
中心central 和 租户tenants 路由 的 相互限制方式:
'domain' => config('tenancy.central_domains')[0], //限定管理中心域名才能进入
PreventAccessFromCentralDomains::class, //防止中心应用的访问混入
租户基本命令:
1,租户命令:
php artisan tenants:migrate
php artisan tenants:migrate --seed
php artisan tenants:migrate-fresh --seed
php artisan tenants:seed --tenants=XXXX
php artisan tenants:run larabbs:calculate-active-user
php artisan tenants:run email:send --tenants=8075a580-1cb8-11e9-8822-49c5d8f8ff23 --option="queue=1" --option="subject=New Feature" --argument="body=We have launched a new feature. ..."
2,cron Kernel 配置租户执行命令方式:
$schedule->command('tenants:run larabbs:calculate-active-user')->everyMinute()->withoutOverlapping();
多租户的图片资源使用方式:
默认是不对的地址:
http://foo9.larashop.test/storage/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87\_20211116190644.jpg
正确的图片地址应该是这样,有 tenancy/assets :
http://foo9.larashop.test/tenancy/assets/images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87\_20211116190644.jpg
所以要图片资源的函数方法:
public function tenancyUrl($path) {
if (URL::isValidUrl($path)) {
return $path;
}
if (tenant()) {
return tenant_asset($path); // 这里是关键,会转换租户地址
}
return $this->getStorage()->url($path);
}
其他资源的隔离注意:
队列,redis, redis缓存 , 多租户, 文件独立 等都需要注意隔离的配置。
例如:文件缓存的报错, 有些资源是需要用tenant-aware的,如配置利用redis。
This cache store does not support tagging
Hi. If you want your cache to be tenant-aware, you need to use a driver that supports tagging, e.g. Redis.
If you don’t need tenant-aware cache, comment out the CacheTenancyBootstrapper in your tenancy.php config file.
代码的github地址:
为了方便参考,这里提供我的github地址,有相关代码参考:
https://github.com/liangdabiao/laravel-shop-saas
所有账号密码都是 admin admin
后台 /admin
同时也可以参考我的论坛SaaS代码,另一种方式:
https://github.com/liangdabiao/bbs-saas-skeleton