package jnpf.dmsService; import jnpf.base.UserInfo; import jnpf.dmsEntity.permission.form.UserFolderPermissionTreeForm; import jnpf.dmsEntity.permission.model.DmsSubjectSet; import jnpf.dmsEntity.permission.model.DmsUserFolderPermissionTreeRow; import jnpf.dmsEntity.permission.vo.DmsCursorPageVO; import jnpf.dmsEntity.permission.vo.DmsUserFolderPermissionTreeNodeVO; import jnpf.dmsEntity.permission.vo.DmsUserFolderPermissionVO; import jnpf.dmsMapper.DmsUserFolderPermissionTreeMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsFileCenterCursorCodec; import jnpf.dmsPermission.DmsPermissionError; import jnpf.dmsPermission.DmsPermissionException; import jnpf.dmsPermission.DmsPermissionGroupMembershipResolver; import jnpf.dmsPermission.DmsPermissionProperties; import jnpf.dmsPermission.DmsUserFolderPermissionPrincipal; import jnpf.dmsPermission.DmsUserFolderPermissionTreeQuery; import jnpf.dmsPermission.DmsUserSubjectBatchResolver; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @Service @RequiredArgsConstructor public class DmsUserFolderPermissionTreeService { private static final String CURSOR_KIND = "USER_FOLDER_PERMISSION_TREE"; private final DmsCurrentIdentity currentIdentity; private final DmsPermissionProperties properties; private final DmsFileCenterCursorCodec cursorCodec; private final DmsUserSubjectBatchResolver subjectResolver; private final DmsPermissionGroupMembershipResolver groupMembershipResolver; private final DmsUserFolderPermissionTreeMapper mapper; @Transactional(readOnly = true) public DmsCursorPageVO tree(UserFolderPermissionTreeForm form) { currentIdentity.requirePermissionAdministrator(); UserInfo actor = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(actor); List userIds = normalizeUserIds(form == null ? null : form.getUserIds()); int pageSize = pageSize(form == null ? null : form.getPageSize()); UUID parentId = form == null ? null : form.getParentId(); String fingerprint = cursorCodec.fingerprint(String.join("\n", userIds)); DmsFileCenterCursorCodec.Cursor cursor = cursorCodec.decode( form == null ? null : form.getCursor(), CURSOR_KIND, tenantId, actor.getUserId().trim(), parentId, fingerprint); List subjects = subjectResolver.resolve(tenantId, userIds); groupMembershipResolver.resolveAll(subjects, properties.getMaxPermissionGroupScan()); DmsUserFolderPermissionTreeQuery query = new DmsUserFolderPermissionTreeQuery(); query.setTenantId(tenantId); query.setParentId(parentId); query.setCursorSortCode(cursor.getSortCode()); query.setCursorId(cursor.getId()); query.setLimit(pageSize + 1); query.setPrincipals(principals(subjects)); List rows = mapper.selectTree(query); return page(rows, userIds, pageSize, tenantId, actor.getUserId().trim(), parentId, fingerprint); } /** Internal business entry: only the authenticated user's full PRINT folder scope. */ @Transactional(readOnly = true) public List currentUserPrintFolderIds() { UserInfo actor = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(actor); String userId = actor.getUserId().trim(); List subjects = subjectResolver.resolve(tenantId, Collections.singletonList(userId)); for (DmsSubjectSet subject : subjects) { if (!userId.equals(subject.getUserId())) { throw invalid("用户授权主体不匹配"); } } groupMembershipResolver.resolveAll(subjects, properties.getMaxPermissionGroupScan()); DmsUserFolderPermissionTreeQuery query = new DmsUserFolderPermissionTreeQuery(); query.setTenantId(tenantId); query.setPrincipals(principals(subjects)); List ids = new ArrayList<>(); for (DmsUserFolderPermissionTreeRow row : mapper.selectPrintFolders(query)) { ids.add(row.getId()); } return ids; } /** Personal workflow grants are version-scoped and never expanded into folder permissions. */ @Transactional(readOnly = true) public List currentUserWorkflowPrintVersionIds() { UserInfo actor = currentIdentity.requireUser(); List ids = new ArrayList<>(); for (DmsUserFolderPermissionTreeRow row : mapper.selectWorkflowPrintVersions( currentIdentity.tenantId(actor), actor.getUserId().trim())) { ids.add(row.getId()); } return ids; } private List normalizeUserIds(List values) { if (values == null || values.isEmpty()) { throw invalid("userIds 不能为空"); } LinkedHashSet normalized = new LinkedHashSet<>(); for (String value : values) { if (value == null || value.trim().isEmpty()) { throw invalid("userIds 不能包含空值"); } normalized.add(value.trim()); } if (normalized.size() > properties.getMaxUserFolderTreeUsers()) { throw invalid("userIds 数量不能超过 " + properties.getMaxUserFolderTreeUsers()); } return new ArrayList<>(normalized); } private int pageSize(Integer requested) { int value = requested == null ? properties.getDefaultPageSize() : requested; if (value < 1 || value > properties.getMaxPageSize()) { throw invalid("pageSize 必须在 1 到 " + properties.getMaxPageSize() + " 之间"); } return value; } private List principals(List subjects) { LinkedHashMap values = new LinkedHashMap<>(); for (DmsSubjectSet subject : subjects) { addPrincipal(values, subject.getUserId(), "USER", subject.getUserId()); addPrincipals(values, subject.getUserId(), "ORG_DIRECT", subject.getDirectOrgIds()); addPrincipals(values, subject.getUserId(), "ORG_ANCESTOR", subject.getAncestorOrgIds()); addPrincipals(values, subject.getUserId(), "POSITION", subject.getPositionIds()); addPrincipals(values, subject.getUserId(), "ROLE", subject.getRoleIds()); addPrincipals(values, subject.getUserId(), "GROUP", subject.getPermissionGroupIds()); } return new ArrayList<>(values.values()); } private void addPrincipals(Map values, String userId, String kind, Set ids) { if (ids == null) { return; } for (String id : ids) { addPrincipal(values, userId, kind, id); } } private void addPrincipal(Map values, String userId, String kind, String id) { if (id == null || id.trim().isEmpty()) { return; } String normalizedId = id.trim(); String key = userId + "\n" + kind + "\n" + normalizedId; values.computeIfAbsent(key, ignored -> new DmsUserFolderPermissionPrincipal(userId, kind, normalizedId)); } private DmsCursorPageVO page( List sourceRows, List userIds, int pageSize, String tenantId, String actorId, UUID parentId, String fingerprint) { List rows = sourceRows == null ? Collections.emptyList() : sourceRows; LinkedHashMap nodes = new LinkedHashMap<>(); Map>> actionsByFolder = new LinkedHashMap<>(); for (DmsUserFolderPermissionTreeRow row : rows) { DmsUserFolderPermissionTreeNodeVO node = nodes.computeIfAbsent(row.getId(), ignored -> node(row)); if (row.getUserId() != null && row.getActionCode() != null) { actionsByFolder.computeIfAbsent(node.getId(), ignored -> new LinkedHashMap<>()) .computeIfAbsent(row.getUserId(), ignored -> new LinkedHashSet<>()) .add(row.getActionCode()); } } boolean hasMore = nodes.size() > pageSize; List list = new ArrayList<>(nodes.values()); if (hasMore) { list = new ArrayList<>(list.subList(0, pageSize)); } for (DmsUserFolderPermissionTreeNodeVO node : list) { Map> byUser = actionsByFolder.getOrDefault( node.getId(), Collections.>emptyMap()); for (String userId : userIds) { Set actions = byUser.get(userId); if (actions == null || actions.isEmpty()) { continue; } DmsUserFolderPermissionVO permission = new DmsUserFolderPermissionVO(); permission.setUserId(userId); List ordered = new ArrayList<>(); if (actions.contains("READ")) { ordered.add("READ"); } if (actions.contains("PRINT")) { ordered.add("PRINT"); } permission.setActions(ordered); node.getUserPermissions().add(permission); } } DmsCursorPageVO result = new DmsCursorPageVO<>(); result.setList(list); result.setHasMore(hasMore); if (hasMore && !list.isEmpty()) { DmsUserFolderPermissionTreeNodeVO last = list.get(list.size() - 1); result.setNextCursor(cursorCodec.encode(CURSOR_KIND, tenantId, actorId, parentId, fingerprint, new DmsFileCenterCursorCodec.Cursor( last.getName(), null, null, last.getSortCode(), last.getId()))); } return result; } private DmsUserFolderPermissionTreeNodeVO node(DmsUserFolderPermissionTreeRow row) { DmsUserFolderPermissionTreeNodeVO node = new DmsUserFolderPermissionTreeNodeVO(); node.setId(row.getId()); node.setParentId(row.getParentId()); node.setName(row.getName()); node.setSortCode(row.getSortCode()); node.setHasChildren(row.isHasChildren()); return node; } private DmsPermissionException invalid(String message) { return DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST, message); } }