Filament多租户环境下ApplyTenantScopes中间件重复执行引发查询重复问题
Filament多租户:ApplyTenantScopes中间件重复执行导致SQL条件重复
在Filament多租户场景中,ApplyTenantScopes中间件的handle方法会在每个页面请求时执行两次,最终生成的SQL查询包含重复的租户过滤条件,例如:
select * from "categories" where "categories"."unit_id" in (1) and "categories"."unit_id" in (1) order by "categories"."id" asc
不确定这是框架Bug还是自身配置错误,相关代码如下:
中间件代码
class ApplyTenantScopes { /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next): Response { Category::addGlobalScope( fn(Builder $query) => $query->whereBelongsTo(Filament::getTenant()), ); return $next($request); } }
面板提供者注册代码
->tenantMiddleware([ ApplyTenantScopes::class, ], isPersistent: true)
问题原因与解决方案
核心原因
- 全局作用域重复添加:全局作用域是绑定在模型类上的持久化设置,每次中间件执行都会给
Category新增一次相同的作用域,两次执行就会生成重复的SQL条件。 - 持久化中间件的触发逻辑:设置
isPersistent: true后,中间件会作用于所有应用路由(包括Filament内部的AJAX请求、资源加载请求等),同一请求链路中可能被多次触发。
修复方案
方案1:避免重复添加全局作用域
在添加作用域前先检查是否已存在,防止重复绑定:
public function handle(Request $request, Closure $next): Response { // 通过作用域名称判断是否已添加 if (!collect(Category::getGlobalScopes())->has('tenant_filter')) { Category::addGlobalScope('tenant_filter', function (Builder $query) { $query->whereBelongsTo(Filament::getTenant()); }); } return $next($request); }
方案2:调整中间件注册方式
如果不需要中间件在所有路由生效,移除isPersistent: true,仅让其作用于Filament面板路由:
->tenantMiddleware([ ApplyTenantScopes::class, ])
方案3:改用Filament内置租户机制
Filament原生支持租户模型过滤,无需手动编写中间件:
让Category模型实现HasTenants接口并定义租户关联:
use Filament\Models\Contracts\HasTenants; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Category extends Model implements HasTenants { public function getTenant(): ?Model { return Filament::getTenant(); } public function tenant(): BelongsTo { return $this->belongsTo(Unit::class, 'unit_id'); } }
Filament会自动为该模型添加租户过滤逻辑,无需手动维护中间件。
内容的提问来源于stack exchange,提问作者marin
相关产品推荐
相关产品推荐

