Laravel Tenancy 中文文档 (自用)
引言
Tenancy被开发成一个小型生态系统。它是一组产品/包,都专注于一个简单的目标:为您的应用程序提供最佳的多租户工作方式。
这个想法
Tenancy的一般思路是以一种非常强大的模块化方式在任何Laravel应用程序中实现多租户工作。
通过使用模块化方法,我们允许您仅选择您特定应用程序所需的组件。这使您能够只实现您需要的特定功能,而不必强制实现额外的功能来改变应用程序的工作方式。
要求
知识
多租户在理解上可能非常困难,更不用说在应用程序中应用了。这就是为什么我们建议每个人在开始使用Tenancy之前先熟悉Laravel应用程序开发。
软件
Tenancy始终希望为其包支持最新的Laravel版本。这个版本的Tenancy支持:
- Laravel 9.x
- PHP 8.0或更高版本
由于文档有时可能过时,我们强烈建议您查看最新的Github Actions。
模块特定要求
根据使用的Tenancy模块的不同,可能会有其他特定于模块的要求。请确保检查您安装的每个模块以确保满足要求。
安装
基本安装
有两种方法可以安装基本包,即简化安装和选择性安装。
简化安装方法将安装基本包和所有其他模块,但不会注册所有提供者。
选择性安装方法只安装基本包,每个附加模块都需要单独安装。
我们强烈建议使用选择性安装方法,因为它确保只安装您需要的模块,并自动注册相应的提供者。
简化安装
要快速进行简化安装,可以一次性安装所有内容:
composer require tenancy/tenancy
重要提示: 您需要手动启用您选择的数据库驱动,方法是将相关的服务提供者添加到 app.php
文件。
注意: 这将默认加载特定的非侵入式影响。您可以通过查看存储库的 composer.json
文件来检查加载了哪些影响。
这是一种轻松试用这个工具包的方法!
我们建议有选择性地安装您需要的包。安装了 tenancy/tenancy
之后,确保注册您希望使用的影响和钩子。您只需简单地注册相应的提供者即可。
以下是可以添加到 config/app.php
文件中的非详尽提供者列表,请确保注释掉您不需要的部分。每个组件的文档中都包含所需的确切提供者。
'providers' => [
// ...
/*
* Package Service Providers...
*/
Tenancy\Affects\Broadcasts\Provider::class,
Tenancy\Affects\Cache\Provider::class,
Tenancy\Affects\Configs\Provider::class,
Tenancy\Affects\Connections\Provider::class,
Tenancy\Affects\Filesystems\Provider::class,
Tenancy\Affects\Logs\Provider::class,
Tenancy\Affects\Mails\Provider::class,
Tenancy\Affects\Models\Provider::class,
Tenancy\Affects\Routes\Provider::class,
Tenancy\Affects\URLs\Provider::class,
Tenancy\Affects\Views\Provider::class,
Tenancy\Hooks\Database\Provider::class,
Tenancy\Hooks\Migration\Provider::class,
Tenancy\Hooks\Hostname\Provider::class,
Tenancy\Database\Drivers\Mysql\Provider::class,
Tenancy\Database\Drivers\Sqlite\Provider::class,
// ...
]
选择性安装
不要一次性加载所有 tenancy 包,您可以选择性地安装您需要的内容。您至少需要安装框架:
composer require tenancy/framework
下一步
现在安装了基本包,您需要确定您的租户对象是什么,并对租户对象执行设置。
之后,选择性地添加和/或配置以下内容:
Tenancy 的架构分为三个主要组件类型。
生命周期钩子
这些组件关注的是在系统中创建、更新或删除租户时发生的事情。这些组件涉及的内容包括确保租户注册的域名有效,创建数据库,并注册/创建处理租户需求的服务。
标识
Tenancy 的标识组件负责在应用程序逻辑发生之前识别租户,无论该逻辑是来自 HTTP 请求、队列中运行的作业还是在控制台中运行的命令。
影响
影响组件负责为已识别的租户更改应用程序。这些组件包括设置与数据库的连接、更改日志设置和其他服务的配置等操作。
什么是租户
概述
无论是基于请求的主机名、登录的用户还是用户所属的团队来分离数据,完全取决于您自己。租户的主题可以是应用程序中的任何 eloquent 模型,您希望围绕其构建业务逻辑。
选择租户
在构建多租户应用程序时,最重要的决策是选择租户。为了帮助您做出决策,请自问以下问题:
- 我的业务逻辑中的租户数据属于哪个实体?
- 我是否有公司与员工注册?那么很可能该公司是租户,它保存了我们希望与应用程序中的任何其他公司分隔开的所有信息。这在使用自定义 URL 的应用程序中是一个非常常见的选择,例如 yourteam.saas.app。
- 或者,我们可以简单地将用户标记为租户。这将将所有数据连接到单个帐户。Gmail 就是一个很好的例子。您登录后只能看到您帐户的电子邮件。
- 还有一种混合版本也很常见,GitHub 提供个人命名空间,如 github.com/luceos 和团队命名空间,如 github.com/tenancy。
Tenancy 对于任何情况都提供解决方案,因为对租户的标识没有限制,也没有配置的租户数量限制。以上述混合示例为例,租户既可以是用户模型,也可以是组织模型。通过应用契约并实现所需的方法,它们可以被标记为有效的租户实体。
下一步
一旦确定了将成为租户的对象,您将需要设置这些租户。要开始,请继续阅读租户设置。
租户设置
租户契约
租户契约 Tenancy\Identification\Contracts\Tenant
将特定的类标记为有效的租户。让该类实现契约方法以开始使用:
use Illuminate\Database\Eloquent\Model;
use Tenancy\Identification\Contracts\Tenant;
class User extends Model implements Tenant
{
/**
* The attribute of the Model to use for the key.
*
* @return string
*/
public function getTenantKeyName(): string
{
return 'id';
}
/**
* The actual value of the key for the tenant Model.
*
* @return string|int
*/
public function getTenantKey()
{
return $this->id;
}
/**
* A unique identifier, eg class or table to distinguish this tenant Model.
*
* @return string
*/
public function getTenantIdentifier(): string
{
return get_class($this);
}
}
这将强制您的 User 模型实现一些 tenancy 所需的方法。
租户模型 Trait
当然,tenancy 提供了一种简单的方式,通过使用 trait 来为模型应用契约所需的方法。
use Illuminate\Database\Eloquent\Model;
use Tenancy\Identification\Concerns\AllowsTenantIdentification;
use Tenancy\Identification\Contracts\Tenant;
class User extends Model implements Tenant
{
use AllowsTenantIdentification;
}
您的第一个有效租户已经存在。现在我们需要确保我们的应用程序知道它的存在。
租户注册
为了让包知道一个有效的租户存在,您需要在租户识别解析器上注册它。最好的方法是在 app/Providers/AppServiceProvider
中或者是您创建的专用 app/Providers/TenantProvider
中进行注册。通过使用 addModel
函数将租户提供给租户解析器。
namespace App\Providers;
use App\User;
use Illuminate\Support\ServiceProvider;
use Tenancy\Identification\Contracts\ResolvesTenants;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->resolving(ResolvesTenants::class, function (ResolvesTenants $resolver) {
$resolver->addModel(User::class);
return $resolver;
});
}
}
下一步
生命周期钩子
生命周期钩子允许您在创建、更新或删除租户时执行特定操作。详细信息请查看生命周期钩子。
生命周期钩子
生命周期钩子在创建、更新或删除租户时执行。它们允许对新创建的租户进行简单的引导,例如提供新数据库虚拟机、注册域名或设置 S3 存储桶。
当 HookResolver
触发钩子时,它将执行以下操作:
- 解析已注册的钩子。
- 调用钩子的
for()
方法,并传递特定的生命周期事件。 - 通过检查
fires()
方法的返回值来过滤不应触发的钩子。 - 根据
priority()
方法的返回值进行排序,参见优先级。 - 直接调用
fire()
方法,或者当queued()
为 true 时,将其派发到队列返回的任务。
事件
生命周期钩子监听以下任一事件:
Tenancy\Tenant\Events\Created
Tenancy\Tenant\Events\Updated
Tenancy\Tenant\Events\Deleted
您需要在自己的代码库中触发这些事件,例如:
event(new \Tenancy\Tenant\Events\Created($tenant));
或者通过 Laravel 的 $dispatchesEvents
:
use Illuminate\Database\Eloquent\Model;
class Tenant extends Model
{
protected $dispatchesEvents = [
'created' => \Tenancy\Tenant\Events\Created::class,
'updated' => \Tenancy\Tenant\Events\Updated::class,
'deleted' => \Tenancy\Tenant\Events\Deleted::class,
];
}
需要注意的一些要点:
- 租户必须实现租户契约。
- 在您自己的代码完成之后触发事件。
- 在任何事务之外触发事件,以便已更改的属性持久化,并且钩子使用正确的值。
优先级
要确保钩子按正确的顺序执行(例如,在创建数据库后运行迁移),钩子需要具有优先级。确保您的钩子使用正确的值。钩子按从最低优先级到最高优先级的顺序运行。
- 创建、更新和删除数据库:
-100
- 如果启用了 hooks-migrations:
-50
因此,如果您想在创建租户时设置和配置数据库服务器,请确保使用优先级低于 -100 的值。如果您想在迁移和数据填充完成后动态配置数据库值,请使用高于 -50 的值。
可用的钩子
内置钩子
下一步
标识
标识驱动将确定您的应用程序在请求、命令或作业期间应加载哪个租户。
生命周期钩子 - 数据库
概述
此包的目的是处理租户数据库的创建、更新和删除。
要使用此包,还必须安装数据库驱动。
需要有一个租户,并分发生命周期事件中的 Created
、Updated
和/或 Deleted
事件才能使用此包。
安装
使用 Composer 安装:
composer require tenancy/hooks-database
注册以下 ServiceProvider:
Tenancy\Hooks\Database\Provider::class
配置
建议监听 Tenancy\Hooks\Database\Events\Drivers\Configuring
事件以调整数据库配置。
可用的函数有:
useConnection()
:使用在 Laravel 应用程序的config/database.php
文件中定义的连接之一。useConfig()
:使用任意路径中的文件提供配置。defaults()
:使用 Tenancy 内部代码来获取以下配置:数据库名称(租户键)、数据库用户名(租户键)、数据库用户密码(基于tenancy/framework
中的PasswordGenerator
生成)。
以下示例将配置数据库使用 config/database.php
中定义的 mysql
连接,并将 Tenancy 的默认数据库配置与此合并:
namespace App\Listeners;
use Tenancy\Hooks\Database\Events\Drivers\Configuring;
class ConfigureTenantDatabase
{
public function handle(Configuring $event)
{
$event->useConnection('mysql', $event->defaults($event->tenant));
}
}
数据库变异
Tenancy 提供了一个特定的事件 Tenancy\Hooks\Database\Events\ConfigureDatabaseMutation
,允许您“配置”数据库变异。此事件将在确定应触发数据库变异后触发。
此事件允许您:
- 重新设置钩子的优先级
- 禁用钩子
您可以在此事件触发时执行任何操作。
以下示例禁用变异以在删除租户后保留数据库:
namespace App\Listeners;
use Tenancy\Hooks\Database\Events\ConfigureDatabaseMutation;
use Tenancy\Tenant\Events\Deleted;
class ConfigureTenantDatabaseMutations
{
public function handle(ConfigureDatabaseMutation $event)
{
if ($event->event instanceof Deleted) {
$event->disable();
}
}
}
生命周期钩子 - 迁移
概述
此包的目的是处理迁移和数据填充。
要使用此包,需要安装和配置 affects-connections
包。
需要有一个租户 分发生命周期事件 Created
、Updated
和/或 Deleted
。
安装
使用 Composer 安装:
composer require tenancy/hooks-migration
注册以下 ServiceProvider:
Tenancy\Hooks\Migration\Provider::class
迁移
配置
安装完包后,需要配置如何迁移租户。可以通过监听 Tenancy\Hooks\Migration\Events\ConfigureMigrations
事件来完成配置。该事件提供了一些简单的功能,让迁移工作更加轻松:
path()
:注册迁移文件所在的路径。这些迁移文件将在迁移时使用。disable()
:完全禁用迁移。priority()
:更改迁移的优先级。
警告:迁移的默认优先级是 -50,会在数据库钩子之后运行。如果未禁用迁移或设置优先级小于 -100,则会出现错误,因为数据库已被删除。
示例
以下示例将添加包含应用于租户数据库的迁移文件的 database/tenant/migrations
文件夹。
namespace App\Listeners;
use Tenancy\Hooks\Migration\Events\ConfigureMigrations;
class ConfigureTenantMigrations
{
public function handle(ConfigureMigrations $event)
{
$event->path(database_path('tenant/migrations'));
}
}
手动运行迁移
如果已经设置了迁移钩子,可以触发 Tenancy\Tenant\Events\Updated
事件来触发钩子并运行迁移:
event(new \Tenancy\Tenant\Events\Updated($tenant));
或者,可以使用 php artisan migrate
命令并传递附加参数:
--tenant
参数指定要为其运行迁移的租户。这需要控制台识别。--database
参数告诉迁移在tenant
连接上运行,该连接是使用 affects-connections 动态配置的。--path
参数指定迁移文件的路径。
示例:
php artisan migrate --tenant=1 --database=tenant --path=database/tenant/migrations
数据填充
配置
安装完迁移后,可以使用 Tenancy\Hooks\Migration\Events\ConfigureSeeds
事件来填充租户数据库。该事件有一个非常简单的方法可供使用:
seed()
:接受 Seeder 类的字符串。在下面的示例中可以看到如何使用。disable()
:完全禁用填充。priority()
:更改填充的优先级。
注意:填充的默认优先级是 -40,默认在迁移钩子之后运行。
示例
以下示例将使用 ConfigureSeeds
事件添加 UsersSeeder
数据填充类。
namespace App\Listeners;
use Tenancy\Hooks\Migration\Events\ConfigureSeeds;
class ConfigureTenantSeeds
{
public function handle(ConfigureSeeds $event)
{
$event->seed(\UsersSeeder::class);
}
}
生命周期钩子 - 主机名
概述
此包的目的是在租户更新时运行自定义函数。
要使用此包,需要一个租户,并且租户需要分发 Created
、Updated
和/或 Deleted
的生命周期事件。
安装
使用 Composer 安装:
composer require tenancy/hooks-hostname
注册以下 ServiceProvider:
Tenancy\Hooks\Hostname\Provider::class
配置
租户
首先,需要更新租户以使用 Tenancy\Hooks\Hostname\Contracts\HasHostnames
契约,并实现 getHostnames
函数以返回租户的主机名数组。
以下示例假设租户有一个 “hostname” 属性:
class Tenant implements Tenancy\Hooks\Hostname\Contracts\HasHostnames
{
public function getHostnames(): array
{
return [
$this->hostname
];
}
}
处理程序
接下来,需要创建处理程序。处理程序用于执行与租户的主机名相关的所有逻辑,例如注册域名、更新 Nginx/Apache 配置文件等。
要创建处理程序,需要创建一个新的类,该类实现 Tenancy\Hooks\Hostname\Contracts\HostnameHandler
契约,并实现 handle
函数以执行所需的逻辑。
以下示例检查租户的域名是否有效。如果租户没有有效的域名,将向管理员发送电子邮件,告知域名无效。
namespace App\Handlers;
use Tenancy\Hooks\Hostname\Contracts\HostnameHandler;
use Tenancy\Tenant\Events\Event;
use Illuminate\Support\Facades\Mail;
class SimpleHandler implements HostnameHandler
{
public function handle(Event $event): void
{
if (!$this->hasValidDomains($event->tenant)) {
Mail::to($event->tenant->email)->send(new DomainsNotValid($event->tenant->getHostnames()));
}
}
}
钩子
与其他钩子一样,此钩子触发一个简单的事件,允许您正确配置它。该事件是 Tenancy\Hooks\Hostname\Events\ConfigureHostnames
,它具有一些非常简单的功能:
registerHandler()
:注册处理程序。getHandlers()
:获取处理程序。
以下示例注册了前面示例中创建的处理程序:
namespace App\Listeners;
use App\Handlers\SimpleHandler;
use Tenancy\Hooks\Hostname\Events\ConfigureHostnames;
class ConfigureHostnameHandlers
{
public function handle(ConfigureHostnames $event)
{
$event->registerHandler(new SimpleHandler);
}
}
生命周期钩子 - 自定义钩子
概述
该功能旨在实现通过现有钩子无法实现的自定义功能。
所需条件:
基础
您可以通过实现 Tenancy\Contracts\LifecycleHook
契约来构建自己的钩子。
有两个抽象的钩子类可以简化实现过程:
Tenancy\Lifecycle\Hook
Tenancy\Lifecycle\ConfigurableHook
基本区别在于,在 Hook
中预期钩子设置不会发生更改,而在 ConfigurableHook
中预期钩子设置是可变的。
例如,一个生命周期钩子在同一默认优先级和默认队列上始终运行,应使用 Hook
,而一个将在租户队列上运行的生命周期钩子应使用 ConfigurableHook
。
实现
只需创建一个包含钩子逻辑的 fire
方法即可实现。
public function fire()
{
echo "Running hook for " . $this->event->tenant->name;
}
如果钩子需要基于事件实现不同的逻辑(例如创建和删除),可以在 fire
方法中检查事件的类型。
public function fire()
{
// Created Event
if ($this->event instanceof \Tenancy\Tenant\Events\Created) {
dump("Running hook for the creation of a tenant");
return;
}
// Deleted Event
if ($this->event instanceof \Tenancy\Tenant\Events\Deleted) {
dump("Running hook for the deletion of a tenant");
return;
}
}
如果钩子只需要在一个事件上运行,您可以禁用钩子并在需要触发时启用它。
namespace App\Hooks;
class CustomCreateHook extends ConfigurableHook
{
public $fires = false;
public function for($event)
{
parent::for($event);
if ($this->event instanceof \Tenancy\Tenant\Events\Created) {
$this->fires = true;
}
return $this;
}
}
对于更高级或租户相关的事件更改,请考虑创建一个 事件和监听器 来配置钩子。
区别
Hook
和 ConfigurableHook
的实现基本相同,除了以下区别。
队列
Hook
默认在队列上运行,并默认使用应用程序的默认队列。
ConfigurableHook
默认不使用队列,只有在指定队列时才会运行在队列上。
// Hook
public $queued = true;
public $queue = null;
// ConfigurableHook
public $queue = null;
触发
Hook
默认始终触发,只要传递的事件是有效的 Tenancy 事件。
ConfigurableHook
默认始终触发。
// Hook
public $event; // 必须是 Tenancy\Tenant\Events\Event 的实例
public $fires = true;
// ConfigurableHook
public $fires = true;
请注意,Hook
和 ConfigurableHook
都具有包含租户的 event
属性,可以通过 $event->tenant
来访问租户。
注册
有两种方法可以注册自定义的生命周期事件。
扩展 HooksProvider
最简单的方法是创建一个新的服务提供程序(或使用现有的服务提供程序),并使其扩展 Tenancy\Support\HooksProvider
。然后创建一个包含您自定义钩子的数组。
namespace App\Providers;
class TenantLifecycleProvider extends Tenancy\Support\HooksProvider
{
protected $hooks = [
\App\Hooks\CustomHook::class,
];
}
这是注册生命周期事件的推荐方法。
解析解析器
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Tenancy\Lifecycle\Contracts\ResolvesHooks;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
$this->app->resolving(ResolvesHooks::class, function (ResolvesHooks $resolver) {
$resolver->addHook(\App\Hooks\CustomHook::class);
});
}
}
请注意,这不是注册生命周期事件的推荐方法。
高级用法
ConfiguresHook 事件
如果要执行高级逻辑以配置钩子,就像可用的钩子一样,可以创建一个新的事件和监听器来配置自定义钩子。为此,请确保您在钩子中覆盖 for
方法。
// Hook
public function for($event)
{
parent::for($event);
event(new \App\Events\ConfiguresCustomHookEvent($this->event, $this));
return $this;
}
示例事件
namespace App\Events;
class ConfiguresCustomHookEvent
{
/**
* @var Event
*/
public $event;
/**
* @var ConfigurableHook
*/
public $hook;
public function __construct(Event $event, ConfigurableHook $hook)
{
$this->event = $event;
$this->hook = $hook;
}
public function disable()
{
$this->hook->fires = false;
return $this;
}
public function priority(int $priority = -50)
{
$this->hook->priority = $priority;
return $this;
}
}
此时,您可以为新事件创建监听器,并按照 Laravel 文档进行注册。
租户识别
概述
租户识别过程决定了您要为哪个租户提供服务。虽然使用 TenantResolver
不是绝对必需的,但这样做会将大部分租户业务逻辑转移到应用程序中。
TenantResolver
分发一些事件,这些事件由识别驱动程序用于识别当前请求的租户。通过使用事件,您不仅限于使用一个驱动程序。每个驱动程序只会在其识别时触发自己的契约。然而,如果触发识别时没有指定特定的驱动程序,它将尝试识别所有驱动程序。第一个响应有效租户对象的驱动程序将导致其他驱动程序被忽略。
无法识别的处理
根据您选择如何构建应用程序,可能会有一些情况下需要在未识别到租户时执行特定的逻辑。要实现此目的,只需为 Tenancy\Identification\Events\NothingIdentified
事件创建一个监听器。
在以下示例中,我们将终止执行并返回 404 错误。
namespace App\Listeners;
use Tenancy\Identification\Events\NothingIdentified;
class NoTenantIdentified
{
public function handle(NothingIdentified $event)
{
abort(404);
}
}
识别驱动程序
每个驱动程序都使用相同的原则。为了让驱动程序使用租户,它必须实现与驱动程序相关的接口(或在 Laravel 术语中称为契约)。
根据您的应用程序,可能需要使用不同的方法来识别租户。以下是可以单独或组合使用的可用识别驱动程序。
下一步
Affects
Affects通过与框架紧密集成改变了您的 Laravel 应用程序的行为。
HTTP 租户识别
概述
该包的目的是通过 HTTP 请求来识别租户。
要求
租户必须在 TenantResolver 中注册。
使用场景
- 通过子目录路径识别租户
- 通过子域名识别租户
- 基于唯一域名识别租户
- 基于查询参数识别租户
- 等等
简介
通过 HTTP 请求对象来识别租户是一种非常常见的方式。
要允许通过 Http 驱动程序识别租户,您需要为租户类应用合同并实现所需的方法。
安装
通过 composer 安装:
composer require tenancy/identification-driver-http
注册以下 ServiceProvider:
Tenancy\Identification\Drivers\Http\Providers\IdentificationProvider::class
配置
通过 HTTP 进行标识的租户需要实现 Tenancy\Identification\Drivers\Http\Contracts\IdentifiesByHttp
合同。
tenantIdentificationByHttp
方法应该返回根据当前请求识别出的租户。
以下示例假设您的 Customer 模型具有两个附加列,用于标识其配置的门户。portal_hostname 和 portal_path 允许您将此 Customer 标识为当前主机名和路径请求的租户。
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Tenancy\Identification\Concerns\AllowsTenantIdentification;
use Tenancy\Identification\Contracts\Tenant;
use Tenancy\Identification\Drivers\Http\Contracts\IdentifiesByHttp;
class Customer extends Model implements Tenant, IdentifiesByHttp
{
use AllowsTenantIdentification;
/**
* Specify whether the tenant model is matching the request.
*
* @param Request $request
* @return Tenant
*/
public function tenantIdentificationByHttp(Request $request): ?Tenant
{
return $this->query()
->where('portal_hostname', $request->getHttpHost())
->where('portal_path', $request->path())
->first();
}
}
以下示例假设您的 Customer 模型为用户配置使用的子域名添加了额外的列。
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Tenancy\Identification\Concerns\AllowsTenantIdentification;
use Tenancy\Identification\Contracts\Tenant;
use Tenancy\Identification\Drivers\Http\Contracts\IdentifiesByHttp;
class Customer extends Model implements Tenant, IdentifiesByHttp
{
use AllowsTenantIdentification;
/**
* Specify whether the tenant model is matching the request.
*
* @param Request $request
* @return Tenant
*/
public function tenantIdentificationByHttp(Request $request): ?Tenant
{
list($subdomain) = explode('.', $request->getHost(), 2);
return $this->query()
->where('subdomain', $subdomain)
->first();
}
}
队列租户识别
概述
该包的目的是允许在队列任务中识别租户。
要求
租户必须在 TenantResolver 中注册。
使用场景
- 识别特定作业所属的租户
- 等等
简介
Laravel 中最强大的功能之一是队列。它们用于运行不需要用户界面并且可以延迟的特定任务。当涉及到多租户时,处理队列可能会很困难,这正是这个包的用武之地。
该包已经为您做了很多工作,如果在队列任务排队时识别出租户,它将记住是哪个租户。如果您想为不同的租户排队一些东西,它还提供了一些强大的覆盖工具。
安装
通过 composer 安装:
composer require tenancy/identification-driver-queue
注册以下 ServiceProvider:
Tenancy\Identification\Drivers\Queue\Providers\IdentificationProvider::class
配置
为了启用租户的队列标识,它必须实现特定的合同 Tenancy\Identification\Drivers\Queue\Contracts\IdentifiesByQueue
。在这个函数中,您将获得一个简单的自定义 Processing
事件,其中包含以下两个信息之一:
- 默认提供的
tenant_key
和tenant_identifier
,如果在排队作业时识别到租户。 - 您自己提供的
tenant
、tenant_key
或tenant_identifier
。
以下示例中,我们将首先检查是否提供了一个覆盖的租户。如果没有,我们将简单地检查是否可以在此模型中找到租户。
use Illuminate\Database\Eloquent\Model;
use Illuminate\Queue\Events\Processing;
use Tenancy\Identification\Concerns\AllowsTenantIdentification;
use Tenancy\Identification\Contracts\Tenant;
use Tenancy\Identification\Drivers\Queue\Contracts\IdentifiesByQueue;
class Customer extends Model implements Tenant, IdentifiesByQueue
{
public function tenantIdentificationByQueue(Processing $event): ?Tenant
{
if ($event->tenant) {
return $event->tenant;
}
if ($event->tenant_key && $event->tenant_identifier === $this->getTenantIdentifier()) {
return $this->newQuery()
->where($this->getTenantKeyName(), $event->tenant_key)
->first();
}
return null;
}
}
覆盖
有时,您可能希望覆盖租户(例如,当您的管理面板本身是一个租户)。您可以通过为作业提供以下公钥之一来实现这一点:
tenant_identifier
,应该与租户的getTenantIdentifier
相同。tenant_key
,应该与租户的getTenantKey
相同。tenant
,可以是完整的租户对象。
以下示例展示了一个可覆盖的作业。
namespace App\Jobs;
use App\Models\Customer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Tenancy\Facades\Tenancy;
class MailCustomer implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var Customer $tenant */
public $tenant;
public function __construct(Customer $customer, $tenant_key = null, string $tenant_identifier = null)
{
$this->tenant = $customer;
}
public function handle()
{
// ..
}
}
控制台租户识别
概述
该包的目的是允许通过控制台命令识别租户。
要求
租户必须在 TenantResolver 中注册。
使用场景
- 通过自定义字段识别租户
- 通过ID识别租户
- 等等
简介
使用控制台输入对象来识别租户。
为了允许租户通过控制台驱动进行识别,所有命令都增加了两个新选项,--tenant
和 --tenant-identifier
。
要使用控制台驱动程序识别租户,您需要将合同应用到租户类并实现所需的方法。
安装
确保您使用的模型已在 TenantResolver 中注册。
通过 composer 安装:
composer require tenancy/identification-driver-console
注册以下 ServiceProvider:
Tenancy\Identification\Drivers\Console\Providers\IdentificationProvider::class
配置
需要实现 Tenancy\Identification\Drivers\Console\Contracts\IdentifiesByConsole
合同来识别通过控制台的租户。
tenantIdentificationByConsole
方法应根据当前请求返回已识别的租户。
以下示例假设您的 Customer 模型具有一个 slug 字段,我们将使用该字段来识别租户。
use Illuminate\Database\Eloquent\Model;
use Tenancy\Identification\Concerns\AllowsTenantIdentification;
use Tenancy\Identification\Contracts\Tenant;
use Tenancy\Identification\Drivers\Console\Contracts\IdentifiesByConsole;
use Symfony\Component\Console\Input\InputInterface;
class Customer extends Model implements Tenant, IdentifiesByConsole
{
use AllowsTenantIdentification;
/**
* Specify whether the tenant model is matching the request.
*
* @param InputInterface $input
* @return Tenant
*/
public function tenantIdentificationByConsole(InputInterface $input): ?Tenant
{
if ($input->hasParameterOption('--tenant')) {
return $this->query()
->where('slug', $input->getParameterOption('--tenant'))
->first();
}
return null;
}
}
高级示例
在下面的示例中,我们将进一步扩展前面的示例,假设您有多个租户类型,可能共享相同的 slug。
use Illuminate\Database\Eloquent\Model;
use Tenancy\Identification\Concerns\AllowsTenantIdentification;
use Tenancy\Identification\Contracts\Tenant;
use Tenancy\Identification\Drivers\Console\Contracts\IdentifiesByConsole;
use Symfony\Component\Console\Input\InputInterface;
class Customer extends Model implements Tenant, IdentifiesByConsole
{
use AllowsTenantIdentification;
/**
* Specify whether the tenant model is matching the request.
*
* @param InputInterface $input
* @return Tenant|null
*/
public function tenantIdentificationByConsole(InputInterface $input): ?Tenant
{
if ($input->hasParameterOption('--tenant-identifier')) {
if($input->getParameterOption('--tenant-identifier') != $this->getTenantIdentifier()) {
return null;
}
}
if ($input->hasParameterOption('--tenant')) {
return $this->query()
->where('slug', $input->getParameterOption('--tenant'))
->first();
}
return null;
}
}
用法
–tenant 选项
该选项用于指定特定的租户。
在下面的示例中,我们假设您要列出特定租户的可用路由。
我们还假设所讨论的租户已经实现了前面的示例,并具有 slug 为 “my-first-tenant”。
php artisan route:list --tenant=my-first-tenant
–tenant-identifier 选项
该选项用于指定特定租户类型。
由于租户可以是多个类,当编写自定义命令以执行特定于不同类型租户的特定操作时,此选项很有用,或者当可以通过相同的 --tenant
值识别不同类型的租户时。
自定义命令
在以下示例命令中,我们假设每个租户都有一个 “maintenance” 值,以便在不必关闭整个应用程序的情况下,能够将其重定向到维护页面。
我们还假设您有两个租户:用户和组织。
namespace App\Console\Commands;
use Illuminate\Console\Command;
class TenantMaintenanceCommand extends Command
{
protected $signature = 'tenant:maintenance';
protected $description = 'Inverts the Maintenance mode for a tenant';
/** @var Resolver */
protected $resolver;
public function __construct(ResolvesTenants $resolver)
{
parent::__construct();
$this->resolver = $resolver;
}
public function handle()
{
$this->resolver->getModels()->each(function (string $class) {
$model = (new $class());
if ($this->option('tenant-identifier') != $model->getTenantIdentifier()) {
return;
}
$model->all()->each(function ($tenant) {
$tenant->maintenance = !$tenant->maintenance;
$tenant->save();
})
});
}
}
用法
为了仅为用户租户运行您的自定义命令,可以执行以下操作:
php artisan tenant:maintenance --tenant-identifier=mysql.users
环境租户识别
概述
该包的目的是允许基于环境来识别租户。
要求
租户必须在 TenantResolver 中注册。
使用场景
- 通过应用程序运行的环境来识别租户
- 在演示环境中确保加载特定租户
- 等等
简介
该包允许基于环境来识别租户。
安装
通过 composer 安装:
composer require tenancy/identification-driver-environment
注册以下 ServiceProvider:
Tenancy\Identification\Drivers\Environment\Providers\IdentificationProvider::class
配置
这允许您根据环境变量设置自己的身份验证要求,例如:
- Slug:自动生成的人类可读字符串
- ID:数据库自增ID
以下示例假设您的 Customer 模型具有一个 slug 字段,我们将使用该字段来识别租户。
use Illuminate\Database\Eloquent\Model;
use Tenancy\Identification\Concerns\AllowsTenantIdentification;
use Tenancy\Identification\Contracts\Tenant;
use Tenancy\Identification\Drivers\Environment\Contracts\IdentifiesByEnvironment;
class Customer extends Model implements Tenant, IdentifiesByEnvironment
{
use AllowsTenantIdentification;
/**
* Specify whether the tenant model is matching the request.
*
* @return Tenant
*/
public function tenantIdentificationByEnvironment(): ?Tenant
{
if ($tenant = env('TENANT')) {
return $this->query()
->where('slug', $tenant)
->first();
}
return null;
}
}
注意,使用 env()
助手函数在 Laravel 缓存配置时无法正常工作。
本作品采用《CC 协议》,转载必须注明作者和本文链接
github.com/plugins-world/LaravelSa... 半个小时内从0创建一个laravel租户项目。
mark