Skip to content

Foundation/Rbac 架构说明

架构概览

Foundation/Rbac/
├── Contracts/              # 接口层(依赖倒置)
│   ├── PermissionChecker.php          # 权限检查器接口
│   ├── PermissionResolver.php         # 权限解析器接口(策略模式)
│   └── MenuVisibilityChecker.php      # 菜单可见性检查器接口

├── Resolvers/              # 解析器实现(V1/V2 可切换)
│   ├── SnapshotPermissionResolver.php      # V1: 读取 rbac_user_permission_snapshots
│   └── RoleBasedPermissionResolver.php     # V2: 读取 rbac_role_permissions(预留)

├── Checkers/               # 检查器实现
│   ├── DatabasePermissionChecker.php       # 权限检查(依赖 Resolver)
│   └── DatabaseMenuVisibilityChecker.php   # 菜单可见性检查

├── Concerns/               # Trait 混入(借鉴 Weiran)
│   └── HasLimsPermissions.php              # User Model 混入

├── Middleware/
│   └── PermissionMiddleware.php            # 路由权限中间件

├── Facades/
│   └── LimsRbac.php                        # Facade(静态调用)

├── LimsRbac.php                            # 核心服务(便捷 API)
└── LimsRbacServiceProvider.php             # 服务提供者

设计模式

1. 依赖倒置原则(DIP)

php
// ❌ 错误:直接依赖实现
class SomeService {
    protected SnapshotPermissionResolver $resolver;
}

// ✅ 正确:依赖接口
class SomeService {
    protected PermissionResolver $resolver;  // 接口
}

好处

  • 解耦:业务代码不依赖具体实现
  • 可测试:可 Mock 接口
  • 可扩展:可替换实现

2. 策略模式(Strategy Pattern)

php
// V1: 快照策略
app()->singleton(PermissionResolver::class, SnapshotPermissionResolver::class);

// V2: 角色策略
app()->singleton(PermissionResolver::class, RoleBasedPermissionResolver::class);

用户权限来源演进:

阶段Resolver数据源说明
V1(当前)SnapshotPermissionResolverrbac_user_permission_snapshotsLegacy 镜像,sync 同步
V2(切主后)RoleBasedPermissionResolverrbac_role_permissions + override纯 RBAC,动态计算

切换方式

php
// config/rbac.php
'resolver' => env('RBAC_RESOLVER', SnapshotPermissionResolver::class),

无需修改任何业务代码!


3. Facade 模式

php
// 通过 Facade 调用
LimsRbac::check('sample.add');

// 等价于
app('lims.rbac')->check('sample.add');

// 等价于
app(LimsRbac::class)->check('sample.add');

核心流程

权限检查流程

用户请求

中间件/Gate/手动检查

LimsRbac / User Trait

DatabasePermissionChecker::has()

PermissionResolver::resolve()  ← 策略可切换

    ├─ V1: SnapshotPermissionResolver
    │      └─ 读取 rbac_user_permission_snapshots

    └─ V2: RoleBasedPermissionResolver
           └─ 读取 rbac_role_permissions

缓存(Redis, 10分钟)

返回 ['permission_code' => bool]

检查逻辑(OR/AND)

返回 true/false

与旧架构对比

维度旧架构(Services/Rbac)新架构(Foundation/Rbac)
位置app/Services/Rbac/app/Foundation/Rbac/
接口❌ 无✅ Contracts 目录
策略❌ 硬编码快照✅ Resolver 可切换
Trait❌ 无✅ HasLimsPermissions
Facade❌ 无✅ LimsRbac::check()
Gate❌ 自定义中间件✅ Gate::before 集成
Blade✅ 自定义指令✅ 自定义 + Laravel 原生

V1 → V2 演进路径

当前状态(V1)

Legacy(写源)
    ↓ lims:rbac-sync
rbac_user_permission_snapshots(只读镜像)
    ↓ SnapshotPermissionResolver
Beta 读取

特点

  • Legacy 改权限后需运行 sync
  • Beta 读快照,不写
  • 用户权限 = 快照(静态)

切主后(V2)

Beta 管理后台(写源)
    ↓ 直接写
rbac_role_permissions + user_permission_overrides
    ↓ RoleBasedPermissionResolver
Beta 读取

特点

  • 废弃 Legacy 权限管理
  • Beta 直接写 RBAC 表
  • 用户权限 = 角色 union + override(动态)

切换步骤

  1. 开发 V2 Resolver(已完成,预留)

    • RoleBasedPermissionResolver 已实现
  2. 创建权限管理界面(未来)

    • 角色管理 CRUD
    • 用户分配角色
    • 用户独立权限 override(可选)
  3. 数据迁移

    • 停止 Legacy 改权限
    • 将快照数据迁移为角色分配
  4. 切换配置

    env
    RBAC_RESOLVER=App\Foundation\Rbac\Resolvers\RoleBasedPermissionResolver
  5. 废弃 Legacy

    • 停止 lims:rbac-sync
    • 归档 users.{qx}

Laravel Gate 集成

工作原理

php
// LimsRbacServiceProvider::bootGateIntegration()

Gate::before(function ($user, $ability) {
    if (! config('lims_rbac.enforce')) {
        return null; // 不拦截
    }

    // RBAC 只接管小写点式权限,段内使用 kebab-case;单段 ability 留给 Policy。
    if (preg_match(
        '/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?:\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*)+$/',
        $ability
    ) !== 1) {
        return null;
    }

    // 超管短路封装在 Checker 内,且只会作用于已确认的 RBAC ability。
    return app(PermissionChecker::class)->has((int) $user->id, $ability);
});

Laravel 原生方法自动生效

php
// 控制器
$this->authorize('sample.add');

// Blade
@can('sample.add')
    <button>添加</button>
@endcan

// 代码
Gate::allows('sample.add')
Gate::denies('sample.add')

viewupdateapprove 等单段 ability 不会进入 RBAC,继续由对应 Laravel Policy 处理;权限是否存在或是否启用属于 PermissionChecker 的授权结果, 不用于猜测 ability 归属。


缓存策略

缓存键格式

V1: lims_rbac:snapshot:user:{userId}:permissions
V2: lims_rbac:role_based:user:{userId}:permissions

缓存时长

  • 默认:10 分钟
  • 配置:RBAC_CACHE_TTL_MINUTES

缓存清除时机

  1. 用户权限变更后

    php
    $user->clearPermissionCache();
    LimsRbac::clearUserCache($user);
  2. 角色权限变更后(V2)

    php
    // 需清除该角色所有用户的缓存
    $role->users()->each(fn($user) => $user->clearPermissionCache());
  3. 同步后

    php
    // lims:rbac-sync 命令执行后
    Cache::flush(); // 或批量清除

性能优化

1. 批量检查

php
// ❌ 慢:循环单次检查
foreach ($permissions as $perm) {
    if (LimsRbac::check($perm)) {
        // ...
    }
}

// ✅ 快:批量检查
$results = LimsRbac::batchCheck($permissions);
foreach ($results as $perm => $has) {
    if ($has) {
        // ...
    }
}

2. 缓存预热

php
// 系统启动后预热热点用户
$hotUserIds = [1, 2, 3, 4, 5];
$resolver = app(PermissionResolver::class);

foreach ($hotUserIds as $userId) {
    $resolver->resolve($userId); // 触发缓存
}

3. 超管短路

php
// 超管判断优先,避免数据库查询
if ($checker->isSuperAdmin($userId)) {
    return true;
}

扩展点

1. 自定义 Resolver

php
class LdapPermissionResolver implements PermissionResolver {
    public function resolve(int $userId): array {
        // 从 LDAP 读取权限
    }
}

2. 自定义 Checker

php
class CachedPermissionChecker implements PermissionChecker {
    // 实现自定义缓存逻辑
}

3. 自定义中间件

php
class ApiPermissionMiddleware {
    // 针对 API 的权限检查逻辑
}

测试建议

单元测试

php
public function test_snapshot_resolver_resolves_user_permissions()
{
    $resolver = new SnapshotPermissionResolver();
    $permissions = $resolver->resolve(1);
    
    $this->assertIsArray($permissions);
    $this->assertArrayHasKey('sample.add', $permissions);
}

集成测试

php
public function test_user_can_check_permission_via_trait()
{
    $user = User::factory()->create();
    
    $this->assertTrue($user->hasPermission('sample.add'));
    $this->assertFalse($user->hasPermission('admin_only'));
}

总结

Foundation/Rbac 架构实现了:

  1. 标准 RBAC 模型:User - Role - Permission
  2. 接口分离:依赖抽象,易扩展
  3. 策略可切换:V1 快照 → V2 角色,无缝升级
  4. Laravel 集成:Gate + Blade 原生支持
  5. 便捷 API:Trait + Facade 多种用法
  6. 高性能:缓存 + 批量检查 + 超管短路

核心优势:切换 Resolver 无需改业务代码,配置切换即可演进!