'datetime', 'last_login_at' => 'datetime', 'password' => 'hashed', ]; } public static function defaultAdminSiteId(): int { static $cached = null; if ($cached !== null) { return $cached; } $id = DB::table('admin_sites')->where('is_default', true)->value('id'); if ($id === null) { $id = DB::table('admin_sites')->orderBy('id')->value('id'); } if ($id === null) { throw new \RuntimeException('No admin_sites row found.'); } $cached = (int) $id; return $cached; } /** * 用户在各站点上的角色(多站点 RBAC)。 * * @return BelongsToMany */ public function roles(): BelongsToMany { return $this->belongsToMany( AdminRole::class, 'admin_user_site_roles', 'admin_user_id', 'role_id', )->withPivot(['site_id', 'granted_at']); } public function isSuperAdmin(): bool { if ($this->relationLoaded('roles')) { return $this->roles->contains('slug', self::ROLE_SUPER_ADMIN); } return $this->roles()->where('admin_roles.slug', self::ROLE_SUPER_ADMIN)->exists(); } /** * 仅来自「直接授权」的 menu_action.permission_code(默认站点,含 site_id 为 null 的历史行)。 * * @return list */ public function directMenuActionPermissionCodes(): array { $siteId = self::defaultAdminSiteId(); $rows = DB::table('admin_user_menu_actions as uma') ->join('admin_menu_actions as ma', 'ma.id', '=', 'uma.menu_action_id') ->where('uma.admin_user_id', $this->id) ->where(function ($q) use ($siteId): void { $q->where('uma.site_id', $siteId)->orWhereNull('uma.site_id'); }) ->where('ma.status', 1) ->pluck('ma.permission_code') ->all(); $out = []; foreach ($rows as $code) { if (is_string($code) && $code !== '') { $out[$code] = true; } } return array_keys($out); } /** * 直接授权对应的 `prd.*` 展示列表(与 {@see self::directMenuActionPermissionCodes()} 桥接)。 * * @return list */ public function directLegacyPermissionSlugs(): array { return AdminPermissionBridge::legacySlugsGrantedByMenuActionCodes($this->directMenuActionPermissionCodes()); } /** * 角色 + 直接授权合并后的 menu_action.permission_code。 * * @return list */ public function effectiveMenuActionPermissionCodes(): array { if ($this->isSuperAdmin()) { $codes = DB::table('admin_menu_actions')->where('status', 1)->pluck('permission_code')->all(); $out = []; foreach ($codes as $c) { if (is_string($c) && $c !== '') { $out[$c] = true; } } return array_keys($out); } $fromRoles = DB::table('admin_user_site_roles as usr') ->join('admin_role_menu_actions as rma', 'rma.role_id', '=', 'usr.role_id') ->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id') ->where('usr.admin_user_id', $this->id) ->where('ma.status', 1) ->pluck('ma.permission_code') ->all(); $merged = []; foreach (array_merge($fromRoles, $this->directMenuActionPermissionCodes()) as $c) { if (is_string($c) && $c !== '') { $merged[$c] = true; } } return array_keys($merged); } /** 是否具备指定权限:`prd.*` 走 legacy_map;否则按 permission_code 精确匹配。含 `super_admin` 全放行。 */ public function hasAdminPermission(string $slug): bool { if ($this->isSuperAdmin()) { return true; } $effective = $this->effectiveMenuActionPermissionCodes(); if ($slug !== '' && in_array($slug, $effective, true)) { return true; } if (! str_starts_with($slug, 'prd.')) { return false; } $needed = AdminPermissionBridge::menuActionCodesForLegacy($slug); if ($needed === []) { return false; } return count(array_intersect($needed, $effective)) > 0; } /** * @return list 与 Next 侧栏、`admin.permission` 中间件一致的 `prd.*` slug 列表 */ public function adminPermissionSlugs(): array { if ($this->isSuperAdmin()) { return AdminPermissionBridge::allLegacySlugs(); } return AdminPermissionBridge::legacySlugsGrantedByMenuActionCodes($this->effectiveMenuActionPermissionCodes()); } /** * @return list */ public function adminRoleSlugs(): array { $this->loadMissing('roles'); return $this->roles ->pluck('slug') ->filter(static fn ($slug): bool => is_string($slug) && $slug !== '') ->unique() ->values() ->all(); } }