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

Filament多租户与Spatie Permissions集成问题:组织资源数据不显示及深层关联租户配置咨询

Filament多租户与Spatie Permissions集成问题:组织资源数据不显示及深层关联租户配置咨询

嗨,我来帮你梳理下这个Filament多租户和Spatie权限集成的问题,先从你最核心的「组织资源数据不显示」问题说起,再拓展到深层关联模型的租户配置:


一、组织资源数据不显示的核心原因与修复方案

你设置了protected static ?string $tenantOwnershipRelationshipName = 'users';,但这个属性的作用是让Filament识别租户模型(Organization)关联到用户的关系,可你当前的多对多关系下,Filament默认的租户范围逻辑没办法直接适配,导致数据筛选失效。

1. 修正OrganizationResource的查询逻辑

直接在OrganizationResource的getEloquentQuery方法里手动添加用户关联的筛选,同时区分管理员和普通用户的权限:

public static function getEloquentQuery(): Builder
{
    $query = parent::getEloquentQuery()
        ->withoutGlobalScopes([
            SoftDeletingScope::class,
        ]);

    // 非管理员用户只能看到自己关联的组织
    $user = auth()->user();
    if ($user && !$user->hasRole('super_admin') && !$user->organizations()->where('is_admin', true)->exists()) {
        $query->whereHas('users', function ($q) use ($user) {
            $q->where('user_id', $user->id);
        });
    }

    return $query;
}

2. 验证租户中间件的生效逻辑

你在AdminPanelProvider里加了ApplyTenantScopes::class中间件,但这个中间件默认适配的是用户与租户一对多的关系(比如用户属于单个组织),对于多对多场景需要自定义租户范围:
创建一个全局范围类App\Scopes\OrganizationTenantScope:

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;
use Filament\Facades\Filament;

class OrganizationTenantScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $user = Auth::user();
        if (!$user) return;

        // 管理员或属于超级组织的用户跳过筛选
        if ($user->hasRole('super_admin') || $user->organizations()->where('is_admin', true)->exists()) {
            return;
        }

        $currentTenant = Filament::getTenant();
        if (!$currentTenant) return;

        // 针对不同模型适配筛选逻辑
        if ($model instanceof \App\Models\Organization) {
            $builder->whereHas('users', fn($q) => $q->where('user_id', $user->id));
        } elseif (method_exists($model, 'user')) {
            // 比如Game模型,关联到User再关联到Organization
            $builder->whereHas('user.organizations', fn($q) => $q->where('id', $currentTenant->id));
        } elseif (method_exists($model, 'game')) {
            // 比如Media模型,关联到Game -> User -> Organization
            $builder->whereHas('game.user.organizations', fn($q) => $q->where('id', $currentTenant->id));
        }
    }
}

然后在Organization模型的booted方法里注册这个全局范围:

protected static function booted()
{
    parent::booted();

    static::addGlobalScope(new \App\Scopes\OrganizationTenantScope());

    // 保留你原来的created逻辑
    self::created(function ($model) {
        $session_team_id = config('organizations.default_organization_id');
        setPermissionsTeamId($model);
        setPermissionsTeamId($session_team_id);
    });
}

二、深层关联模型(Media -> Game -> User -> Organizations)的租户配置

对于这类嵌套关联的模型,你可以通过两种方式处理:

  1. 全局范围统一处理:上面的OrganizationTenantScope已经包含了对Game、Media这类模型的筛选逻辑,只要模型上有对应的关联方法(比如game()、user()),就能自动应用租户范围。
  2. 资源单独配置:如果需要更精细的控制,也可以在对应资源的getEloquentQuery里手动添加筛选:
    比如MediaResource:
public static function getEloquentQuery(): Builder
{
    $query = parent::getEloquentQuery();
    $user = auth()->user();
    
    if ($user && !$user->hasRole('super_admin') && !$user->organizations()->where('is_admin', true)->exists()) {
        $currentTenant = Filament::getTenant();
        $query->whereHas('game.user.organizations', fn($q) => $q->where('id', $currentTenant->id));
    }

    return $query;
}

三、额外注意点

  • 确保用户切换租户后,Filament能正确获取到当前租户:可以在用户切换租户的逻辑里,把租户ID存入session,方便全局范围调用。
  • 测试管理员权限:当用户属于is_admin = true的组织时,要确保能绕过所有租户范围,看到所有数据。

备注:内容来源于stack exchange,提问作者Kristian Vasilev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.16 02:55:28