Laravel 多租户架构的路由分发与租户数据的隔离策略

🚀 Laravel 多租户架构的路由分发与租户数据隔离策略:一场技术讲座

嗨,大家好!今天咱们来聊聊一个超级有趣的话题——Laravel 多租户架构。如果你正在构建一个 SaaS 应用(比如一个可以同时服务多个企业的系统),那么多租户架构就是你的最佳拍档 😊。

别担心,我会用轻松诙谐的语言和一些代码片段,带你一步步理解如何实现多租户架构中的路由分发和数据隔离策略。准备好了吗?那我们开始吧!


🌟 什么是多租户架构?

简单来说,多租户架构允许一个应用同时服务于多个独立的“租户”(Tenant)。每个租户都有自己的数据、配置甚至主题样式,但它们共享同一个代码库。就像一个公寓楼,每家有自己的钥匙和房间,但共用电梯和物业 😄。

在 Laravel 中实现多租户架构的关键在于:

  1. 路由分发:让每个租户都能访问到属于自己的资源。
  2. 数据隔离:确保租户之间的数据互不干扰。

🛣️ 路由分发:为每个租户找到回家的路

想象一下,你有多个租户,每个租户都有自己的一套页面。为了让每个租户都能正确访问到自己的资源,我们需要设计一个聪明的路由系统。

使用子域名区分租户

最常见的做法是通过子域名来区分租户。例如:

  • tenant1.example.com → 属于租户1
  • tenant2.example.com → 属于租户2

如何实现?

在 Laravel 中,可以通过中间件来解析子域名并识别租户。下面是一个简单的例子:

// App/Http/Middleware/ResolveTenant.php
namespace AppHttpMiddleware;

use Closure;
use IlluminateSupportFacadesLog;

class ResolveTenant
{
    public function handle($request, Closure $next)
    {
        // 解析子域名
        $host = $request->getHost();
        $subdomain = explode('.', $host)[0];

        // 根据子域名加载租户信息
        $tenant = AppModelsTenant::where('subdomain', $subdomain)->first();

        if (!$tenant) {
            abort(404, 'Tenant not found.');
        }

        // 将租户信息绑定到请求
        $request->attributes->set('tenant', $tenant);

        return $next($request);
    }
}

然后,在 Kernel.php 中注册这个中间件:

protected $middlewareGroups = [
    'web' => [
        AppHttpMiddlewareResolveTenant::class,
    ],
];

现在,每个请求都会自动解析出对应的租户信息,并绑定到请求中。🎉


动态路由分发

接下来,我们可以根据租户信息动态生成路由。假设每个租户都有自己的仪表盘页面:

Route::middleware(['web', 'auth'])->group(function () {
    Route::get('/', function () {
        $tenant = request()->attributes->get('tenant');
        return view('dashboard', ['tenant' => $tenant]);
    })->name('home');
});

这样,无论哪个租户访问根路径 /,他们都会看到自己专属的仪表盘页面。


🔒 数据隔离:让租户的数据各归其主

数据隔离是多租户架构的核心之一。我们需要确保每个租户只能访问自己的数据,而不能越界访问其他租户的数据。

方法一:使用全局作用域(Global Scope)

Laravel 提供了全局作用域的功能,可以在查询时自动添加租户过滤条件。

首先,创建一个全局作用域类:

// App/Scopes/TenantScope.php
namespace AppScopes;

use IlluminateDatabaseEloquentBuilder;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentScope;

class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $tenant = request()->attributes->get('tenant');

        if ($tenant) {
            $builder->where('tenant_id', $tenant->id);
        }
    }
}

然后,在模型中应用这个作用域:

// App/Models/Post.php
namespace AppModels;

use AppScopesTenantScope;
use IlluminateDatabaseEloquentModel;

class Post extends Model
{
    protected static function booted()
    {
        static::addGlobalScope(new TenantScope());
    }
}

现在,当你查询 Post 模型时,Laravel 会自动加上 tenant_id 的过滤条件,确保只返回当前租户的数据。


方法二:使用独立数据库或表前缀

如果需要更强的数据隔离,可以为每个租户分配独立的数据库或表前缀。虽然这种方法更复杂,但它提供了更高的安全性。

独立数据库

config/database.php 中动态切换数据库连接:

'default' => env('DB_CONNECTION', 'mysql'),

'connections' => [
    'tenant' => function () {
        $tenant = request()->attributes->get('tenant');
        return [
            'driver' => 'mysql',
            'host' => env('DB_HOST'),
            'database' => 'tenant_' . $tenant->id,
            'username' => env('DB_USERNAME'),
            'password' => env('DB_PASSWORD'),
        ];
    },
],

然后,在查询时切换到租户数据库:

DB::connection('tenant')->table('posts')->get();

表前缀

另一种方法是为每个租户的表加上前缀,例如 tenant1_poststenant2_posts。可以通过修改模型的 $table 属性来实现:

class Post extends Model
{
    public function getTable()
    {
        $tenant = request()->attributes->get('tenant');
        return 'tenant_' . $tenant->id . '_posts';
    }
}

📊 总结:多租户架构的设计决策

方法 优点 缺点
全局作用域 实现简单,适合大多数场景 数据库层面仍可能被误操作
独立数据库 最高的数据隔离性 需要管理多个数据库连接
表前缀 不需要额外的数据库 查询性能可能下降

🎉 结语

今天的讲座就到这里啦!通过子域名解析、动态路由分发和数据隔离策略,我们已经成功搭建了一个基础的多租户架构。希望这些技巧能帮助你在 Laravel 中实现更强大的 SaaS 应用。

最后,记得给你的租户们一杯咖啡 ☕,毕竟他们是让你的系统发光发热的重要伙伴!如果有任何问题,欢迎随时提问哦~ 😊

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注