百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

做一个SaaS独立站(2)- 安装配置

yuyutoo 2025-03-06 21:05 2 浏览 0 评论

参考:
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

相关推荐

网站建设:从新手到高手

现代化网站应用领域非常广泛,从个人形象网站展示、企业商业网站运作、到政府公益等服务网站,各行各业都需要网站建设。大体上可以归结四类:宣传型网站设计、产品型网站制作、电子商务型网站建设、定制型功能网站开...

JetBrains 推出全新 AI 编程工具 Junie,助力高效开发

JetBrains宣布推出名为Junie的全新AI编程工具。这款工具不仅能执行简单的代码生成与检查任务,还能应对编写测试、验证结果等复杂项目,为开发者提供全方位支持。根据SWEBench...

AI也能写代码!代码生成、代码补全、注释生成、代码翻译轻松搞定

清华GLM技术团队打造的多语言代码生成模型CodeGeeX近期更新了新的开源版本「CodeGeeX2-6B」。CodeGeeX2是多语言代码生成模型CodeGeeX的第二代模型,不同于一代CodeG...

一键生成前后端代码,一个36k星的企业级低代码平台

「企业级低代码平台」前后端分离架构SpringBoot2.x,SpringCloud,AntDesign&Vue,Mybatis,Shiro,JWT。强大的代码生成器让前后端代码一键生成,无需写任...

Gitee 代码托管实战指南:5 步完成本地项目云端同步(附避坑要点)

核心流程拆解:远程仓库的搭建登录Gitee官网(注册账号比较简单,大家自行操作),点击“新建仓库”,建议勾选“初始化仓库”和“设置模板文件”(如.gitignore),避免上传临时文件。...

jeecg-boot 源码项目-强烈推荐使用

JEECGBOOT低代码开发平台...

JetBrains推出全新AI编程工具Junie,强调以开发者为中心

IT之家2月1日消息,JetBrains发文,宣布推出一款名为Junie的全新AI编程工具,官方声称这款AI工具既能执行简单的代码生成与检查等基础任务,也能应对“编写测试、验证结...

JetBrains旗下WebStorm和Rider现已加入“非商用免费”阵营

IT之家10月25日消息,软件开发商JetBrains今日宣布,旗下WebStorm(JavaScript开发工具)和Rider(.NET开发工具)现已加入“非商用免费”阵营。如果...

谈谈websocket跨域

了解websocketwebsocket是HTML5的新特性,在客户端和服务端提供了一个基于TCP连接的双向通道。...

websocket调试工具

...

利用webSocket实现消息的实时推送

1.什么是webSocketwebSocket实现实现推送消息WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。以前的推送技术使用Ajax轮询,浏览器需...

Flutter UI自动化测试技术方案选型与探索

...

为 Go 开发的 WebSocket 库

#记录我的2024#...

「Java基础」Springboot+Websocket的实现后端数据实时推送

这篇文章主要就是实现这个功能,只演示一个基本的案例。使用的是websocket技术。...

【Spring Boot】WebSocket 的 6 种集成方式

介绍...

取消回复欢迎 发表评论: