package jnpf.dmsService; import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity; import jnpf.dmsEntity.permission.entity.DmsPermissionUsageEntity; import jnpf.dmsEntity.permission.enums.DmsPrincipalType; import jnpf.dmsEntity.permission.enums.DmsResourceType; import jnpf.dmsEntity.permission.model.DmsResourcePath; import jnpf.dmsEntity.permission.vo.DmsCursorPageVO; import jnpf.dmsEntity.permission.vo.DmsPermissionGrantVO; import jnpf.dmsEntity.permission.vo.DmsPrincipalOptionVO; import jnpf.dmsEntity.permission.vo.DmsPermissionUsageVO; import jnpf.dmsMapper.DmsFilePermissionMapper; import jnpf.dmsMapper.DmsPermissionGroupMapper; import jnpf.dmsMapper.DmsPermissionUsageMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsCursorCodec; import jnpf.dmsPermission.DmsPermissionError; import jnpf.dmsPermission.DmsPermissionException; import jnpf.dmsPermission.DmsPermissionMatcher; import jnpf.dmsPermission.DmsPermissionProperties; import jnpf.dmsPermission.DmsResourceResolver; import jnpf.dmsPermission.DmsUserDisplayNameResolver; import jnpf.permission.OrganizeApi; import jnpf.permission.PositionApi; import jnpf.permission.RoleApi; import jnpf.permission.entity.PositionEntity; import jnpf.permission.entity.RoleEntity; import jnpf.permission.entity.PermissionGroupEntity; import jnpf.permission.entity.OrganizeEntity; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.LinkedHashMap; import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; @Service @Slf4j @RequiredArgsConstructor public class DmsPermissionAdminQueryService { private final DmsCurrentIdentity currentIdentity; private final DmsPermissionProperties properties; private final DmsCursorCodec cursorCodec; private final DmsFilePermissionMapper permissionMapper; private final DmsPermissionGroupMapper permissionGroupMapper; private final DmsPermissionUsageMapper usageMapper; private final DmsPermissionGrantService grantService; private final DmsResourceResolver resourceResolver; private final DmsPermissionMatcher matcher; private final DmsUserDisplayNameResolver userDisplayNameResolver; private final OrganizeApi organizeApi; private final PositionApi positionApi; private final RoleApi roleApi; @Transactional(readOnly = true) public DmsCursorPageVO grants(UUID resourceId, String principalType, String principalId, String cursor, Integer pageSize) { currentIdentity.requirePermissionAdministrator(); String normalizedPrincipalType = normalizePrincipalType(principalType); int limit = pageSize(pageSize); DmsCursorCodec.Cursor decoded = cursorCodec.decode(cursor); List rows = permissionMapper.selectPage(currentIdentity.tenantId(), resourceId, normalizedPrincipalType, trimToNull(principalId), decoded.getTime(), decoded.getId(), limit + 1); return page(rows, limit); } @Transactional(readOnly = true) public DmsPermissionGrantVO grant(UUID id) { currentIdentity.requirePermissionAdministrator(); DmsFilePermissionEntity value = permissionMapper.selectByTenantAndId(currentIdentity.tenantId(), id); if (value == null) { throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND, "权限不存在: " + id); } DmsPermissionGrantVO result = enriched(value, false); enrichPrincipalNames(Collections.singletonList(result)); return result; } @Transactional(readOnly = true) public List resourceGrants(DmsResourceType resourceType, UUID resourceId) { currentIdentity.requirePermissionAdministrator(); String tenantId = currentIdentity.tenantId(); DmsResourcePath path = resourceResolver.resolve(tenantId, resourceType, resourceId); List candidateIds = resourceResolver.candidateResourceIds(path); List result = permissionMapper.selectForResourceIds(tenantId, candidateIds, 1000).stream() .filter(permission -> matcher.coversResource(permission, path)) .map(permission -> enriched(permission, !permission.getResourceId().equals(resourceId))) .collect(Collectors.toList()); enrichPrincipalNames(result); return result; } @Transactional(readOnly = true) public List usages(UUID permissionId, Integer pageSize) { currentIdentity.requirePermissionAdministrator(); String tenantId = currentIdentity.tenantId(); if (permissionMapper.selectByTenantAndId(tenantId, permissionId) == null) { throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND, "权限不存在: " + permissionId); } return usageMapper.selectByPermission(tenantId, permissionId, pageSize(pageSize)).stream() .map(this::usageVO).collect(Collectors.toList()); } @Transactional(readOnly = true) public List permissionGroups(String keyword, Integer pageSize) { currentIdentity.requirePermissionAdministrator(); return permissionGroupMapper.selectEnabled(trimToNull(keyword), pageSize(pageSize)).stream() .map(group -> new DmsPrincipalOptionVO(group.getId(), group.getFullName())) .collect(Collectors.toList()); } private DmsCursorPageVO page(List rows, int limit) { DmsCursorPageVO result = new DmsCursorPageVO<>(); boolean hasMore = rows.size() > limit; if (hasMore) { rows = rows.subList(0, limit); } List values = rows.stream().map(row -> enriched(row, false)) .collect(Collectors.toList()); enrichPrincipalNames(values); result.setList(values); result.setHasMore(hasMore); if (hasMore && !rows.isEmpty()) { DmsFilePermissionEntity last = rows.get(rows.size() - 1); result.setNextCursor(cursorCodec.encode(last.getCreatedAt(), last.getId())); } return result; } private void enrichPrincipalNames(List values) { Map names = userDisplayNameResolver.resolve(values.stream() .filter(value -> DmsPrincipalType.USER.name().equals(value.getPrincipalType())) .map(DmsPermissionGrantVO::getPrincipalId) .collect(Collectors.toList())); values.stream() .filter(value -> DmsPrincipalType.USER.name().equals(value.getPrincipalType())) .forEach(value -> value.setPrincipalName(names.get(value.getPrincipalId()))); enrichNames(values, DmsPrincipalType.ORG, organizeApi::getListByIds, OrganizeEntity::getId, OrganizeEntity::getFullName); enrichNames(values, DmsPrincipalType.POSITION, positionApi::getListByIds, PositionEntity::getId, PositionEntity::getFullName); enrichNames(values, DmsPrincipalType.ROLE, roleApi::getListByIds, RoleEntity::getId, RoleEntity::getFullName); enrichNames(values, DmsPrincipalType.GROUP, permissionGroupMapper::selectDisplayByIds, PermissionGroupEntity::getId, PermissionGroupEntity::getFullName); } private void enrichNames(List values, DmsPrincipalType type, Function, List> lookup, Function idGetter, Function nameGetter) { List ids = values.stream() .filter(value -> type.name().equals(value.getPrincipalType())) .map(DmsPermissionGrantVO::getPrincipalId) .filter(id -> id != null && !id.trim().isEmpty()) .distinct() .collect(Collectors.toList()); if (ids.isEmpty()) { return; } List principals; try { principals = lookup.apply(ids); } catch (RuntimeException ex) { log.warn("DMS {} display name lookup failed for {} principals", type, ids.size(), ex); return; } if (principals == null || principals.isEmpty()) { return; } Map names = new LinkedHashMap<>(); for (T principal : principals) { if (principal == null) { continue; } String name = trimToNull(nameGetter.apply(principal)); if (name != null) { names.putIfAbsent(idGetter.apply(principal), name); } } values.stream() .filter(value -> type.name().equals(value.getPrincipalType())) .forEach(value -> value.setPrincipalName(names.get(value.getPrincipalId()))); } private DmsPermissionGrantVO enriched(DmsFilePermissionEntity value, boolean inherited) { DmsPermissionGrantVO result = grantService.toVO(value); result.setInherited(inherited); result.setStatus(status(value)); if (value.getMaxUseCount() != null) { result.setRemainingUseCount(value.getMaxUseCount() - value.getUsedCount()); } return result; } private String status(DmsFilePermissionEntity value) { Date now = new Date(); if (value.getRevokedAt() != null) { return "REVOKED"; } if (value.getValidFrom().after(now)) { return "PENDING"; } if (value.getValidTo() != null && !now.before(value.getValidTo())) { return "EXPIRED"; } if (value.getMaxUseCount() != null && value.getUsedCount() >= value.getMaxUseCount()) { return "EXHAUSTED"; } return "ACTIVE"; } private DmsPermissionUsageVO usageVO(DmsPermissionUsageEntity value) { DmsPermissionUsageVO result = new DmsPermissionUsageVO(); result.setId(value.getId()); result.setPermissionId(value.getPermissionId()); result.setUserId(value.getUserId()); result.setActionCode(value.getActionCode()); result.setActualResourceType(value.getActualResourceType()); result.setActualResourceId(value.getActualResourceId()); result.setOperationId(value.getOperationId()); result.setSessionExpiresAt(value.getSessionExpiresAt()); result.setUsedAt(value.getUsedAt()); return result; } private int pageSize(Integer requested) { int value = requested == null ? properties.getDefaultPageSize() : requested; if (value < 1 || value > properties.getMaxPageSize()) { throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST, "pageSize 必须在 1 到 " + properties.getMaxPageSize() + " 之间"); } return value; } private String normalizePrincipalType(String value) { if (value == null || value.trim().isEmpty()) { return null; } try { return DmsPrincipalType.valueOf(value.trim().toUpperCase()).name(); } catch (IllegalArgumentException ex) { throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST, "principalType 无效"); } } private String trimToNull(String value) { return value == null || value.trim().isEmpty() ? null : value.trim(); } }