外观
RBAC 核心算法
实现须与本文一致;改算法同步更新单测。表结构见数据模型,verify 菜单项用下文算法 B。
PermissionChecker
php
interface PermissionCheckerInterface
{
public function has(string $permissionCode, ?int $userId = null): bool;
public function hasAny(array $codes, ?int $userId = null): bool;
public function isSuperAdmin(?int $userId = null): bool;
public function canViewMenu(int $legacyMenuId, ?int $userId = null): bool;
}| 实现 | 用途 |
|---|---|
DatabasePermissionChecker | 读 RBAC 表 |
LegacySessionPermissionChecker | 读 $u[$code],仅 sync 期间 A/B 对比 |
中间件:PermissionMiddleware,用法 ->middleware('permission:site_manage')。
A. has(code)
输入:userId(默认当前)、permissionCode。
数据:users、user_permission_snapshots、permissions。
| 步骤 | 条件 | 结果 |
|---|---|---|
| A0 | user.id == 1 或 user.admin == 1 | true |
| A1 | permission 不存在或未激活 | false + log |
| A1b | (后期)按 code 未命中时可查 std_code | 落到同一 permission_id |
| A3 | 查 user_permission_snapshots.has_permission | true/false |
| A4 | 无快照行 | false + log rbac.permission_snapshot_missing |
php
function has(string $code, ?int $userId = null): bool
{
$user = resolveUser($userId);
if (isSuperAdmin($user)) {
return true;
}
// 第一版:仅 permissions.code;后期可增加 std_code 解析
$perm = permissions.active(code: $code);
if ($perm === null) {
return false;
}
$snapshot = user_permission_snapshots.find(user_id, permission_id);
if ($snapshot === null) {
logWarning('rbac.permission_snapshot_missing', ...);
return false;
}
return (bool) $snapshot->has_permission;
}对齐 legacy 的 $u[$qx]==1;不用 role_permissions 直接鉴权。
B. canViewMenu(legacyMenuId)
menu.useing != 1 → false。
取该 menu 的 menu_permissions 规则集 R(按 sort):
| 优先级 | 规则 | 判定 |
|---|---|---|
| B0 | R 为空 | false + log |
| B1 | empty / allow_all | true |
| B2 | display_hidden | false(beta 不出 display/隐藏) |
| B3 | admin / require_super_admin | 仅超管 true |
| B4 | deny_if_user_has | 有该权限 → false(如 !audit_test) |
| B5 | require_any | 任一 has → true |
组合语义(对齐 get_menu.php):
IF allow_all in R → visible
ELSE IF display_hidden in R → hidden
ELSE IF require_super_admin AND isSuperAdmin → visible
ELSE IF any deny_if_user_has matches → hidden
ELSE IF any require_any has(permission.code) → visible
ELSE → hidden第一版不覆盖:site_type 动态子项、shuju_tongji、penetration_test()、父级无可见子项自动收起。
C. PermissionMiddleware
permission:site_manage → has('site_manage')
permission:site_manage,cy → hasAny(OR)
permission:site_manage&cy → 全部 has(AND)RBAC_ENFORCE=true 才生效;默认 false。
缓存
- Key:
LimsRbacDef::userPermissionSnapshotCacheKey($userId) - TTL:
RBAC_CACHE_TTL_MINUTES(默认 10) - sync 后 flush 该 user 或
LimsRbacDef::cacheTag($fzxId)
单测
| 范围 | 用例 |
|---|---|
| A0–A4 | 超管、inactive、快照缺失 |
| B0–B5 | 空 qx、display、admin、!audit_test、require_any |
| C | 中间件 403/200;OR/AND 参数 |
Feature:试点路由 + sync 后改权限。