whereNull('delete_time') ->field('id,name,data_scope') ->select() ->toArray(); return self::mergeRoleScopes($roleRows); } /** * 合并多个角色的数据范围。 * * 历史迁移曾把全部旧角色默认成 scope=1;如果账号同时具有有限范围角色,普通的 * scope=1 视为功能角色而不参与放大,防止“医生 + 医助”等组合意外获得全站数据。 * 2/3/4 是嵌套授权,取最小值即可表达多个有效数据角色的可见范围并集。 * * @param array> $roleRows */ private static function mergeRoleScopes(array $roleRows): int { $validRows = []; foreach ($roleRows as $roleRow) { $scope = (int) ($roleRow['data_scope'] ?? 0); if ($scope < self::SCOPE_ALL || $scope > self::SCOPE_SELF) { continue; } $validRows[] = [ 'name' => trim((string) ($roleRow['name'] ?? '')), 'scope' => $scope, ]; } // 角色存在但库中无有效 data_scope(缺失/脏数据/已删角色):宁可收窄到本人。 if ($validRows === []) { return self::SCOPE_SELF; } foreach ($validRows as $roleRow) { if ($roleRow['scope'] === self::SCOPE_ALL && in_array($roleRow['name'], self::ALL_SCOPE_ROLE_NAMES, true)) { return self::SCOPE_ALL; } } $boundedScopes = array_column(array_values(array_filter( $validRows, static fn (array $roleRow): bool => $roleRow['scope'] > self::SCOPE_ALL )), 'scope'); if ($boundedScopes !== []) { return (int) min($boundedScopes); } // 只有普通 scope=1 角色时保持原有“全部数据”行为。 return self::SCOPE_ALL; } /** * 统一解析 token/cache 中的 role_id(数组 | 单整数 | JSON 字符串)。 * * @return int[] */ private static function normalizeRoleIds(mixed $raw): array { if ($raw === null || $raw === '') { return []; } if (\is_int($raw) || \is_float($raw)) { $v = (int) $raw; return $v > 0 ? [$v] : []; } if (\is_string($raw) && is_numeric($raw)) { $v = (int) $raw; return $v > 0 ? [$v] : []; } if (\is_string($raw)) { $decoded = json_decode($raw, true); if (\is_array($decoded)) { $raw = $decoded; } else { return []; } } if (!\is_array($raw)) { return []; } return array_values(array_filter(array_map( static fn ($v): int => (int) $v, $raw ), static fn (int $v): bool => $v > 0)); } /** * 可见 admin id 集合;null = 不过滤(ALL) * * @return array|null */ public static function getVisibleAdminIds(int $adminId, array $adminInfo): ?array { $scope = self::getEffectiveScope($adminInfo); if ($scope === self::SCOPE_ALL) { return null; } if ($scope === self::SCOPE_SELF) { return $adminId > 0 ? [$adminId] : []; } $myDeptIds = AdminDept::where('admin_id', $adminId)->column('dept_id'); $myDeptIds = array_values(array_filter(array_map('intval', $myDeptIds), static function (int $v): bool { return $v > 0; })); if ($myDeptIds === []) { $fallback = (bool) Config::get('project.data_scope.no_dept_fallback_self', true); return $fallback ? [$adminId] : []; } $targetDeptIds = []; if ($scope === self::SCOPE_DEPT) { $targetDeptIds = $myDeptIds; } else { foreach ($myDeptIds as $did) { foreach (DeptLogic::getSelfAndDescendantIds($did) as $id) { $id = (int) $id; if ($id > 0) { $targetDeptIds[$id] = true; } } } $targetDeptIds = array_keys($targetDeptIds); } if ($targetDeptIds === []) { return $adminId > 0 ? [$adminId] : []; } $ids = AdminDept::whereIn('dept_id', $targetDeptIds)->column('admin_id'); $ids = array_values(array_unique(array_filter(array_map('intval', $ids), static function (int $v): bool { return $v > 0; }))); if ($adminId > 0 && !in_array($adminId, $ids, true)) { $ids[] = $adminId; } return $ids; } public static function isEnabled(): bool { return (bool) Config::get('project.data_scope.enabled', true); } public static function isAll(array $adminInfo): bool { return self::getEffectiveScope($adminInfo) === self::SCOPE_ALL; } /** * 数据范围下:可见成员所在部门及其下级部门 id(与业绩看板 deptOptions、部门类下拉收窄一致)。 * * @return array|null null 表示不限制;[] 表示无可选部门 */ public static function getAllowedDeptIdSet(int $adminId, array $adminInfo): ?array { if ($adminId <= 0 || !self::isEnabled()) { return null; } $visibleIds = self::getVisibleAdminIds($adminId, $adminInfo); if ($visibleIds === null) { return null; } if ($visibleIds === []) { return []; } $set = []; foreach ($visibleIds as $aid) { $deptRows = AdminDept::where('admin_id', (int) $aid)->column('dept_id'); foreach ($deptRows as $d) { $d = (int) $d; if ($d <= 0) { continue; } foreach (DeptLogic::getSelfAndDescendantIds($d) as $x) { $x = (int) $x; if ($x > 0) { $set[$x] = true; } } } } return $set; } /** * 文字描述(日志 / 接口返回可选使用) */ public static function scopeLabel(int $scope): string { return [ self::SCOPE_ALL => '全部', self::SCOPE_DEPT_AND_CHILD => '本部门及下级', self::SCOPE_DEPT => '仅本部门', self::SCOPE_SELF => '仅本人', ][$scope] ?? '全部'; } }