'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']); } /** * 将用户在默认站点上的角色设为指定 slug 集合(全量替换该站点 pivot)。 * * @param list $slugs */ /** * @param list $roleIds */ public function syncAgentRoleIds(int $agentNodeId, array $roleIds): void { $roleIds = array_values(array_unique(array_map(static fn ($id): int => (int) $id, $roleIds))); DB::transaction(function () use ($agentNodeId, $roleIds): void { DB::table('admin_user_agent_roles') ->where('admin_user_id', $this->id) ->where('agent_node_id', $agentNodeId) ->delete(); $now = now(); foreach ($roleIds as $roleId) { DB::table('admin_user_agent_roles')->insert([ 'admin_user_id' => $this->id, 'agent_node_id' => $agentNodeId, 'role_id' => $roleId, 'granted_at' => $now, ]); } $siteId = (int) (AgentNode::query()->where('id', $agentNodeId)->value('admin_site_id') ?? 0); if ($siteId > 0) { DB::table('admin_user_site_roles') ->where('admin_user_id', $this->id) ->where('site_id', $siteId) ->delete(); foreach ($roleIds as $roleId) { DB::table('admin_user_site_roles')->insert([ 'admin_user_id' => $this->id, 'site_id' => $siteId, 'role_id' => $roleId, 'granted_at' => $now, ]); } } }); } /** * @return list */ private function roleMenuActionPermissionCodes(): array { $agentId = $this->primaryAgentNodeId(); if ($agentId !== null) { $fromAgent = $this->agentRoleMenuActionPermissionCodes($agentId); if ($fromAgent !== []) { return $fromAgent; } } return $this->siteRoleMenuActionPermissionCodes(); } /** * @return list */ private function agentRoleMenuActionPermissionCodes(int $agentId): array { return DB::table('admin_user_agent_roles as uar') ->join('admin_role_menu_actions as rma', 'rma.role_id', '=', 'uar.role_id') ->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id') ->where('uar.admin_user_id', $this->id) ->where('uar.agent_node_id', $agentId) ->where('ma.status', 1) ->pluck('ma.permission_code') ->all(); } /** * @return list */ private function siteRoleMenuActionPermissionCodes(): array { return 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(); } public function effectiveRoleSource(): string { $agentId = $this->primaryAgentNodeId(); if ($agentId !== null) { return $this->agentRoleMenuActionPermissionCodes($agentId) !== [] ? 'agent' : 'agent_empty'; } return $this->siteRoleMenuActionPermissionCodes() !== [] ? 'site' : 'none'; } public function syncRoleSlugsForDefaultSite(array $slugs): void { $siteId = self::defaultAdminSiteId(); $slugs = array_values(array_unique($slugs)); $roleIds = DB::table('admin_roles') ->whereIn('slug', $slugs) ->pluck('id') ->all(); DB::transaction(function () use ($siteId, $roleIds): void { DB::table('admin_user_site_roles') ->where('admin_user_id', $this->id) ->where('site_id', $siteId) ->delete(); $now = now(); foreach ($roleIds as $rid) { DB::table('admin_user_site_roles')->insert([ 'admin_user_id' => $this->id, 'site_id' => $siteId, 'role_id' => (int) $rid, 'granted_at' => $now, ]); } $agentId = $this->primaryAgentNodeId(); if ($agentId !== null) { $this->syncAgentRoleIds($agentId, array_map('intval', $roleIds)); } }); } 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(); } public function primaryAgentNodeId(): ?int { $id = DB::table('admin_user_agents') ->where('admin_user_id', $this->id) ->value('agent_node_id'); return $id !== null ? (int) $id : null; } public function primaryAgentNode(): ?AgentNode { $id = $this->primaryAgentNodeId(); if ($id === null) { return null; } return AgentNode::query()->find($id); } /** * 可访问的 admin_sites.id 列表;`null` 表示不限制(超管)。 * * @return list|null */ public function accessibleAdminSiteIds(): ?array { if ($this->isSuperAdmin()) { return null; } $agent = $this->primaryAgentNode(); if ($agent !== null) { return [(int) $agent->admin_site_id]; } $ids = DB::table('admin_user_site_roles') ->where('admin_user_id', $this->id) ->distinct() ->pluck('site_id') ->map(static fn ($id): int => (int) $id) ->values() ->all(); return $ids; } /** * 仅来自「直接授权」的 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 = $this->roleMenuActionPermissionCodes(); $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; } /** * 仅按 permission_code 判定(不处理 prd.* 映射)。 */ public function hasPermissionCode(string $permissionCode): bool { if ($permissionCode === '') { return false; } if ($this->isSuperAdmin()) { return true; } return in_array($permissionCode, $this->effectiveMenuActionPermissionCodes(), true); } /** * @return list 与 Next 侧栏兼容的 `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(); } }