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<DmsPermissionGrantVO> 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<DmsFilePermissionEntity> 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<DmsPermissionGrantVO> resourceGrants(DmsResourceType resourceType, UUID resourceId) {
|
currentIdentity.requirePermissionAdministrator();
|
String tenantId = currentIdentity.tenantId();
|
DmsResourcePath path = resourceResolver.resolve(tenantId, resourceType, resourceId);
|
List<UUID> candidateIds = resourceResolver.candidateResourceIds(path);
|
List<DmsPermissionGrantVO> 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<DmsPermissionUsageVO> 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<DmsPrincipalOptionVO> 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<DmsPermissionGrantVO> page(List<DmsFilePermissionEntity> rows, int limit) {
|
DmsCursorPageVO<DmsPermissionGrantVO> result = new DmsCursorPageVO<>();
|
boolean hasMore = rows.size() > limit;
|
if (hasMore) {
|
rows = rows.subList(0, limit);
|
}
|
List<DmsPermissionGrantVO> 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<DmsPermissionGrantVO> values) {
|
Map<String, String> 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 <T> void enrichNames(List<DmsPermissionGrantVO> values, DmsPrincipalType type,
|
Function<List<String>, List<T>> lookup,
|
Function<T, String> idGetter, Function<T, String> nameGetter) {
|
List<String> 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<T> 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<String, String> 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();
|
}
|
}
|