Laravel 8:实现SaaS应用的用户登录后动态数据库切换

Laravel 8:实现SaaS应用的用户登录后动态数据库切换

本教程详细阐述了在laravel 8 saas应用中,如何根据用户登录信息动态切换数据库连接,以实现多租户数据隔离。文章将指导您配置多个数据库连接,并重点介绍如何利用laravel的中间件机制,在用户认证后编程化地修改默认数据库连接,确保所有模型和控制器自动与租户专属数据库交互,从而构建健壮的多租户架构。

引言

在构建SaaS(软件即服务)应用时,一个常见的需求是为每个租户(或用户)提供独立的数据存储,以确保数据隔离和安全性。这意味着当用户登录时,应用程序需要动态地将其数据库连接从一个主数据库切换到该用户所属的租户数据库。对于Laravel 8后端应用,实现这一功能需要巧妙地管理数据库配置和连接生命周期。

本教程将指导您如何通过动态配置和中间件来实现这一目标,确保您的Laravel应用能够根据登录用户无缝地切换到正确的租户数据库,使得所有Eloquent模型和数据库查询都自动指向租户专属的数据源。

Laravel数据库连接基础

Laravel的数据库配置位于 config/database.php 文件中。您可以在此文件中定义多个数据库连接。每个连接都有一组参数,如驱动、主机、数据库名、用户名和密码。

// config/database.php

'connections' => [

    'mysql' => [
        'driver' => 'mysql',
        'url' => env('DATABASE_URL'),
        'host' => env('DB_HOST', '127.0.0.1'),
        'port' => env('DB_PORT', '3306'),
        'database' => env('DB_DATABASE', 'forge'), // 主数据库或默认数据库
        'username' => env('DB_USERNAME', 'forge'),
        'password' => env('DB_PASSWORD', ''),
        'unix_socket' => env('DB_SOCKET', ''),
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix' => '',
        'prefix_indexes' => true,
        'strict' => true,
        'engine' => null,
        'options' => extension_loaded('pdo_mysql') ? array_filter([
            PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
        ]) : [],
    ],

    'master_db' => [ // 示例:主数据库连接,用于存储租户信息
        'driver' => 'mysql',
        'host' => env('MASTER_DB_HOST', '127.0.0.1'),
        'port' => env('MASTER_DB_PORT', '3306'),
        'database' => env('MASTER_DB_DATABASE', 'master_tenant_info'),
        'username' => env('MASTER_DB_USERNAME', 'root'),
        'password' => env('MASTER_DB_PASSWORD', ''),
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix' => '',
        'strict' => true,
        'engine' => null,
    ],

    // ... 其他连接

],

'default' => env('DB_CONNECTION', 'mysql'), // 默认连接
登录后复制

当您需要访问非默认的数据库连接时,可以使用 DB Facade的 connection() 方法:

use Illuminate/Support/Facades/DB;

// 从主数据库获取租户信息
$tenantInfo = DB::connection('master_db')->table('tenants')->where('id', 1)->first();

// 从默认连接查询用户
$users = DB::table('users')->get();
登录后复制

然而,仅仅使用 DB::connection(‘anther-db’) 只能在特定查询中指定连接,而不能改变所有Eloquent模型和后续数据库操作的默认连接。要实现全局动态切换,我们需要在运行时修改Laravel的配置。

实现动态切换默认数据库连接

核心思想是:在用户登录后,根据其租户信息(例如,从主数据库获取的租户ID、数据库名、用户名和密码),动态地修改Laravel的数据库配置,使其默认连接指向该租户的数据库。

步骤1:获取租户数据库凭据

这部分在您的SaaS应用中可能已经实现。通常,您会在用户登录后,根据用户邮箱或ID从一个“主数据库”中查询到该用户所属租户的专属数据库名称、用户名和密码等信息。


AVCLabs

AVCLabs

AI移除视频背景,100%自动和免费

AVCLabs
268


查看详情
AVCLabs

// 假设用户登录后,可以通过 auth()->user() 获取到用户实例
$user = auth()->user();

// 假设用户模型上存储了租户数据库信息,或者通过关联查询获取
$tenantDbName = $user->tenant_database_name;
$tenantDbUser = $user->tenant_database_username;
$tenantDbPass = $user->tenant_database_password;
登录后复制

步骤2:动态配置新的数据库连接并设置默认连接

Laravel允许您在运行时通过 Config Facade 修改配置。我们可以利用这一点来动态添加或修改一个数据库连接的配置,并将其设置为新的默认连接。

use Illuminate/Support/Facades/Config;
use Illuminate/Support/Facades/DB;

// 假设我们已经获取了 $tenantDbName, $tenantDbUser, $tenantDbPass

if ($tenantDbName) {
    // 动态配置一个名为 'tenant' 的新数据库连接
    Config::set('database.connections.tenant', [
        'driver' => 'mysql', // 根据实际情况调整
        'host' => env('DB_HOST', '127.0.0.1'), // 通常租户数据库与主应用在同一主机
        'port' => env('DB_PORT', '3306'),
        'database' => $tenantDbName,
        'username' => $tenantDbUser ?: env('DB_USERNAME', 'root'), // 如果租户有独立用户,否则使用默认
        'password' => $tenantDbPass ?: env('DB_PASSWORD', ''), // 同上
        'unix_socket' => env('DB_SOCKET', ''),
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix' => '',
        'prefix_indexes' => true,
        'strict' => true,
        'engine' => null,
    ]);

    // 将默认连接设置为新创建的 'tenant' 连接
    Config::set('database.default', 'tenant');

    // 清除并重新连接:这是关键一步,确保所有后续的数据库操作都使用新配置。
    // DB::purge() 会关闭当前默认连接的所有活动连接。
    // DB::reconnect() 会使用新的默认配置重新建立连接。
    DB::purge();
    DB::reconnect();
}
登录后复制

完成上述操作后,所有后续的 DB::table() 查询和Eloquent模型操作都将自动使用名为 tenant 的新连接。

推荐实践:使用中间件实现动态切换

将动态数据库切换逻辑放置在中间件中是最佳实践。中间件在请求生命周期的正确阶段执行(例如,在用户认证之后,但在控制器处理请求之前),可以确保在应用程序处理任何业务逻辑之前,数据库连接已经正确设置。

1. 创建中间件

首先,生成一个新的中间件:

php artisan make:middleware TenantDatabaseSwitcher
登录后复制

然后,编辑 app/Http/Middleware/TenantDatabaseSwitcher.php 文件:

<?php

namespace App/Http/Middleware;

use Closure;
use Illuminate/Http/Request;
use Illuminate/Support/Facades/DB;
use Illuminate/Support/Facades/Config;
use PDO; // 确保引入PDO,如果需要在options中使用PDO常量

class TenantDatabaseSwitcher
{
    /**
     * 处理传入请求。
     *
     * @param  /Illuminate/Http/Request  $request
     * @param  /Closure(/Illuminate/Http/Request): (/Illuminate/Http/Response|/Illuminate/Http/RedirectResponse)  $next
     * @return /Illuminate/Http/Response|/Illuminate/Http/RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        // 确保用户已登录且可以获取到租户信息
        if (auth()->check()) {
            $user = auth()->user();

            // 从主数据库或其他来源获取租户的数据库信息
            // 示例:假设用户模型上直接有这些属性
            $tenantDbName = $user->tenant_database_name;
            $tenantDbUser = $user->tenant_database_username;
            $tenantDbPass = $user->tenant_database_password;

            if ($tenantDbName) {
                // 动态配置一个名为 'tenant' 的新数据库连接
                Config::set('database.connections.tenant', [
                    'driver' => 'mysql',
                    'host' => env('DB_HOST', '127.0.0.1'),
                    'port' => env('DB_PORT', '3306'),
                    'database' => $tenantDbName,
                    'username' => $tenantDbUser ?: env('DB_USERNAME', 'root'),
                    'password' => $tenantDbPass ?: env('DB_PASSWORD', ''),
                    'unix_socket' => env('DB_SOCKET', ''),
                    'charset' => 'utf8mb4',
                    'collation' => 'utf8mb4_unicode_ci',
                    'prefix' => '',
                    'prefix_indexes' => true,
                    'strict' => true,
                    'engine' => null,
                    // 如果需要,可以在这里添加其他PDO选项,例如SSL
                    // 'options' => extension_loaded('pdo_mysql') ? array_filter([
                    //     PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
                    // ]) : [],
                ]);

                // 设置默认连接为新创建的 'tenant' 连接
                Config::set('database.default', 'tenant');

                // 清除并重新连接,确保所有后续DB操作都使用新连接
                DB::purge();      // 清除当前的默认连接(可能是之前的mysql)
                DB::reconnect();  // 重新连接到新的默认连接(即 'tenant')
            }
        }

        return $next($request);
    }
}
登录后复制

2. 注册中间件

在 app/Http/Kernel.php 文件中,将 TenantDatabaseSwitcher 中间件添加到 web 或 api 中间件组中,确保它在用户认证之后执行。通常,会放在 AuthenticateSession 或 StartSession 之后。

// app/Http/Kernel.php

protected array $middlewareGroups = [
    'web' => [
        /App/Http/Middleware/EncryptCookies::class,
        /Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse::class,
        /Illuminate/Session/Middleware/StartSession::class,
        /Illuminate/View/Middleware/ShareErrorsFromSession::class,
        /App/Http/Middleware/VerifyCsrfToken::class,
        /Illuminate/Routing/Middleware/SubstituteBindings::class,
        /App/Http/Middleware/TenantDatabaseSwitcher::class, // 在认证之后添加
    ],

    'api' => [
        // /Laravel/Sanctum/Http/Middleware/EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        /Illuminate/Routing/Middleware/SubstituteBindings::class,
        /App/Http/Middleware/TenantDatabaseSwitcher::class, // 如果API也需要
    ],
];
登录后复制

注意事项

  • 连接池管理: 每次请求都动态切换数据库连接可能会对连接池管理产生影响。DB::purge() 和 DB::reconnect() 是

以上就是Laravel 8:实现SaaS应用的用户登录后动态数据库切换的详细内容,更多请关注php中文网其它相关文章!

https://www.php.cn/faq/1773676.html

发表回复

Your email address will not be published. Required fields are marked *