自定义多租户Laravel应用:如何为单个店铺独立启用内置维护模式并实现秘钥绕过
利用Laravel内置维护模式实现多租户(店铺)独立维护模式
当然可以!Laravel的维护模式并非只能全局生效——我们可以通过扩展它的底层逻辑,轻松实现每个店铺(租户)独立的维护模式,同时复用Laravel内置的秘钥绕过机制。下面是一步步的实现方案:
1. 准备数据库字段
首先确保你的shops表新增两个字段,用来存储店铺维护模式的状态和绕过秘钥:
// 生成迁移文件后修改内容 Schema::table('shops', function (Blueprint $table) { $table->boolean('maintenance_mode')->default(false); $table->string('maintenance_secret')->nullable(); });
执行迁移后,每个店铺就可以独立存储维护模式的配置了。
2. 自定义维护模式逻辑类
Laravel的维护模式核心是由MaintenanceMode契约驱动的,我们可以创建一个自定义类替换默认实现,让它基于当前请求的店铺判断维护状态:
<?php namespace App; use Illuminate\Contracts\Foundation\MaintenanceMode as MaintenanceModeContract; use Illuminate\Http\Request; class TenantMaintenanceMode implements MaintenanceModeContract { protected $request; public function __construct(Request $request) { $this->request = $request; } // 判断当前店铺是否处于维护模式 public function isActive(): bool { // 先获取当前请求对应的店铺(复用你现有idShop参数逻辑) $shop = \App\Shop::find($this->request->idShop); // 店铺不存在时默认不启用维护模式 return $shop ? $shop->maintenance_mode : false; } // 验证是否可以通过秘钥绕过维护模式 public function bypass(Request $request): bool { $shop = \App\Shop::find($this->request->idShop); // 店铺未开启维护模式或无秘钥时,直接返回false if (!$shop || !$shop->maintenance_secret) { return false; } // 复用Laravel原生逻辑:检查请求参数或Cookie return $request->has('secret') && $request->input('secret') === $shop->maintenance_secret || $request->cookie('laravel_maintenance') === $shop->maintenance_secret; } // 返回维护模式的503响应 public function response(Request $request) { return response()->view('errors.503', [], 503); } }
3. 替换默认维护模式绑定
在App\Providers\AppServiceProvider的register方法中,把我们的自定义类绑定到Laravel的维护模式契约,这样框架就会用我们的逻辑来判断维护状态:
public function register() { $this->app->bind( \Illuminate\Contracts\Foundation\MaintenanceMode::class, \App\TenantMaintenanceMode::class ); }
4. 自定义Artisan命令(可选)
如果你需要像全局down/up命令一样,通过命令行或API切换单个店铺的维护模式,可以创建两个自定义命令:
命令1:开启指定店铺维护模式
<?php namespace App\Console\Commands; use App\Shop; use Illuminate\Console\Command; class TenantDownCommand extends Command { protected $signature = 'tenant:down {shopId} {--secret=}'; protected $description = 'Put a specific shop into maintenance mode'; public function handle() { $shop = Shop::findOrFail($this->argument('shopId')); $shop->maintenance_mode = true; // 可选:设置绕过秘钥 if ($secret = $this->option('secret')) { $shop->maintenance_secret = $secret; } $shop->save(); $this->info("Shop {$shop->id} has been put into maintenance mode."); } }
命令2:关闭指定店铺维护模式
<?php namespace App\Console\Commands; use App\Shop; use Illuminate\Console\Command; class TenantUpCommand extends Command { protected $signature = 'tenant:up {shopId}'; protected $description = 'Bring a specific shop out of maintenance mode'; public function handle() { $shop = Shop::findOrFail($this->argument('shopId')); $shop->maintenance_mode = false; $shop->maintenance_secret = null; // 可选:清空秘钥 $shop->save(); $this->info("Shop {$shop->id} is now live."); } }
然后在App\Console\Kernel的$commands数组中注册这两个命令:
protected $commands = [ \App\Console\Commands\TenantDownCommand::class, \App\Console\Commands\TenantUpCommand::class, ];
5. 调整API端点(可选)
如果你需要通过API切换店铺维护模式,只需在控制器中修改原有逻辑,操作对应店铺的数据库字段即可:
public function toggleShopMaintenance(Request $request) { $shop = Shop::findOrFail($request->idShop); $shop->maintenance_mode = !$shop->maintenance_mode; if ($request->has('secret')) { $shop->maintenance_secret = $request->secret; } $shop->save(); return response()->json([ 'message' => $shop->maintenance_mode ? 'Shop is now in maintenance mode' : 'Shop is live' ]); }
6. 关键注意事项
- 店铺识别优先级: 确保所有需要维护模式校验的路由都能正确传递
idShop参数,或者通过域名/子域名等方式识别当前店铺,否则自定义类无法获取到对应店铺的配置。 - 全局维护模式兼容: 如果需要优先全局维护模式,可以在
isActive方法中先判断全局状态:public function isActive(): bool { // 先检查全局维护模式是否开启 if (app(\Illuminate\Foundation\MaintenanceMode::class)->isActive()) { return true; } // 再检查当前店铺的维护模式 $shop = \App\Shop::find($this->request->idShop); return $shop ? $shop->maintenance_mode : false; } - 用户体验一致性: 我们的
bypass方法复用了Laravel原生的Cookie逻辑,用户通过?secret=xxx验证后,后续请求会自动绕过维护模式,和全局模式体验一致。
这种方案完全基于Laravel内置机制实现,比自定义中间件更优雅,也能复用框架维护模式的所有特性,比如响应头处理、Cookie验证等。
内容的提问来源于stack exchange,提问作者jssDev
相关产品推荐
相关产品推荐

