You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义多租户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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 06:55:43