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 lombok.RequiredArgsConstructor; 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.UUID; import java.util.stream.Collectors; @Service @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; @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); enrichUserNames(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()); enrichUserNames(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()); enrichUserNames(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 enrichUserNames(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()))); } 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(); } }