From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master
---
jnpf-dms/jnpf-dms-biz/src/main/java/jnpf/dmsService/DmsPermissionGrantService.java | 356 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 356 insertions(+), 0 deletions(-)
diff --git a/jnpf-dms/jnpf-dms-biz/src/main/java/jnpf/dmsService/DmsPermissionGrantService.java b/jnpf-dms/jnpf-dms-biz/src/main/java/jnpf/dmsService/DmsPermissionGrantService.java
new file mode 100644
index 0000000..4a9f186
--- /dev/null
+++ b/jnpf-dms/jnpf-dms-biz/src/main/java/jnpf/dmsService/DmsPermissionGrantService.java
@@ -0,0 +1,356 @@
+package jnpf.dmsService;
+
+import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity;
+import jnpf.dmsEntity.permission.entity.DmsPermissionResourceEntity;
+import jnpf.dmsEntity.permission.enums.DmsActorType;
+import jnpf.dmsEntity.permission.enums.DmsPermissionAction;
+import jnpf.dmsEntity.permission.enums.DmsPrincipalType;
+import jnpf.dmsEntity.permission.enums.DmsResourceScope;
+import jnpf.dmsEntity.permission.enums.DmsResourceType;
+import jnpf.dmsEntity.permission.form.PermissionGrantBatchForm;
+import jnpf.dmsEntity.permission.form.PermissionGrantItemForm;
+import jnpf.dmsEntity.permission.form.PermissionRevokeForm;
+import jnpf.dmsEntity.permission.vo.DmsPermissionGrantVO;
+import jnpf.dmsIntegration.audit.DmsAuditAdapter;
+import jnpf.dmsMapper.DmsFilePermissionMapper;
+import jnpf.dmsMapper.DmsPermissionResourceMapper;
+import jnpf.dmsPermission.DmsCurrentIdentity;
+import jnpf.dmsPermission.DmsPermissionError;
+import jnpf.dmsPermission.DmsPermissionException;
+import jnpf.dmsPermission.DmsPermissionProperties;
+import jnpf.dmsPermission.DmsPrincipalValidator;
+import lombok.RequiredArgsConstructor;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class DmsPermissionGrantService {
+ private static final String FOLDER_CREATOR_CREATE_SOURCE_PREFIX = "folder-creator-create:";
+ private static final String FOLDER_CREATOR_CREATE_REASON = "鐩綍鍒涘缓鑰呴粯璁� CREATE 鏉冮檺";
+
+ private final DmsCurrentIdentity currentIdentity;
+ private final DmsPermissionProperties properties;
+ private final DmsPrincipalValidator principalValidator;
+ private final DmsPermissionResourceMapper resourceMapper;
+ private final DmsFilePermissionMapper permissionMapper;
+ private final DmsAuditAdapter auditAdapter;
+
+ @Transactional
+ public List<DmsPermissionGrantVO> grant(PermissionGrantBatchForm form) {
+ currentIdentity.requirePermissionAdministrator();
+ if (form.getItems().size() > properties.getMaxGrantBatchSize()) {
+ throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
+ "鎺堟潈鎵归噺澶у皬涓嶈兘瓒呰繃 " + properties.getMaxGrantBatchSize());
+ }
+ String tenantId = currentIdentity.tenantId();
+ String sourceType = form.getSourceType().name();
+ String sourceId = form.getSourceId().trim();
+ List<PermissionGrantItemForm> items = normalizeAndValidate(form.getItems());
+ permissionMapper.lockSource(tenantId, sourceType, sourceId);
+ List<DmsFilePermissionEntity> existing = permissionMapper.selectBySource(tenantId, sourceType, sourceId);
+ if (!existing.isEmpty()) {
+ verifyIdempotentRetry(existing, items);
+ return existing.stream().map(this::toVO).collect(Collectors.toList());
+ }
+
+ validateResources(tenantId, items);
+ principalValidator.validate(items);
+ List<DmsFilePermissionEntity> created = new ArrayList<>();
+ try {
+ for (PermissionGrantItemForm item : items) {
+ DmsFilePermissionEntity permission = toEntity(tenantId, sourceType, sourceId, item);
+ created.add(permissionMapper.insertReturning(permission));
+ }
+ } catch (DuplicateKeyException ex) {
+ throw DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT,
+ "鎺堟潈鏉ユ簮鎴栨巿鏉冮」涓庡凡鏈夋暟鎹啿绐�");
+ }
+ for (DmsFilePermissionEntity permission : created) {
+ auditAdapter.dataChange("CREATE", "鍒涘缓 DMS 鏂囦欢鏉冮檺", "dms_file_permissions",
+ permission.getId().toString(), permission.getGrantReason(), null);
+ }
+ return created.stream().map(this::toVO).collect(Collectors.toList());
+ }
+
+ @Transactional
+ public DmsFilePermissionEntity ensureFolderCreatorCreatePermission(String tenantId, UUID folderId,
+ String creatorUserId) {
+ String sourceType = "SYSTEM";
+ String sourceId = FOLDER_CREATOR_CREATE_SOURCE_PREFIX + folderId;
+ permissionMapper.lockSource(tenantId, sourceType, sourceId);
+ List<DmsFilePermissionEntity> existing = permissionMapper.selectBySource(tenantId, sourceType, sourceId);
+ if (!existing.isEmpty()) {
+ if (existing.size() != 1 || !sameFolderCreatorCreatePermission(
+ existing.get(0), tenantId, folderId, creatorUserId, sourceId)) {
+ throw DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT,
+ "鐩綍鍒涘缓鑰呴粯璁ゆ潈闄愭潵婧愪笌宸叉湁鎺堟潈涓嶄竴鑷�");
+ }
+ return existing.get(0);
+ }
+
+ DmsFilePermissionEntity permission = new DmsFilePermissionEntity();
+ permission.setTenantId(tenantId);
+ permission.setResourceType(DmsResourceType.FOLDER.name());
+ permission.setResourceId(folderId);
+ permission.setResourceScope(DmsResourceScope.SELF.name());
+ permission.setPrincipalType(DmsPrincipalType.USER.name());
+ permission.setPrincipalId(creatorUserId);
+ permission.setIncludeChildOrgs(false);
+ permission.setActionCode(DmsPermissionAction.CREATE.name());
+ permission.setSourceType(sourceType);
+ permission.setSourceId(sourceId);
+ permission.setValidFrom(new Date());
+ permission.setGrantReason(FOLDER_CREATOR_CREATE_REASON);
+ permission.setGrantedByType(DmsActorType.USER.name());
+ permission.setGrantedById(creatorUserId);
+ try {
+ permission = permissionMapper.insertReturning(permission);
+ } catch (DuplicateKeyException ex) {
+ throw DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT,
+ "鐩綍鍒涘缓鑰呴粯璁ゆ潈闄愪笌宸叉湁鎺堟潈鍐茬獊");
+ }
+ auditAdapter.dataChange("CREATE", "鍒涘缓 DMS 鐩綍鍒涘缓鑰呴粯璁ゆ潈闄�", "dms_file_permissions",
+ permission.getId().toString(), FOLDER_CREATOR_CREATE_REASON, null);
+ return permission;
+ }
+
+ @Transactional
+ public List<DmsPermissionGrantVO> revoke(PermissionRevokeForm form) {
+ currentIdentity.requirePermissionAdministrator();
+ String tenantId = currentIdentity.tenantId();
+ String actorId = currentIdentity.userId();
+ String reason = form.getReason().trim();
+ List<UUID> ids = new ArrayList<>(new LinkedHashSet<>(form.getPermissionIds()));
+ ids.sort(Comparator.comparing(UUID::toString));
+ List<DmsFilePermissionEntity> locked = new ArrayList<>();
+ for (UUID id : ids) {
+ DmsFilePermissionEntity permission = permissionMapper.selectByTenantAndIdForUpdate(tenantId, id);
+ if (permission == null) {
+ throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND,
+ "鏉冮檺涓嶅瓨鍦�: " + id);
+ }
+ if (permission.getRevokedAt() != null
+ && (!DmsActorType.USER.name().equals(permission.getRevokedByType())
+ || !actorId.equals(permission.getRevokedById())
+ || !reason.equals(permission.getRevokeReason()))) {
+ throw DmsPermissionException.of(DmsPermissionError.REVISION_CONFLICT,
+ "鏉冮檺宸茶鍏朵粬璇锋眰鎾ら攢: " + id);
+ }
+ locked.add(permission);
+ }
+ for (DmsFilePermissionEntity permission : locked) {
+ if (permission.getRevokedAt() == null) {
+ int changed = permissionMapper.revoke(tenantId, permission.getId(), DmsActorType.USER.name(),
+ actorId, reason);
+ if (changed != 1) {
+ throw DmsPermissionException.of(DmsPermissionError.REVISION_CONFLICT,
+ "鏉冮檺鎾ら攢鍙戠敓骞跺彂鍐茬獊: " + permission.getId());
+ }
+ auditAdapter.dataChange("UPDATE", "鎾ら攢 DMS 鏂囦欢鏉冮檺", "dms_file_permissions",
+ permission.getId().toString(), reason, null);
+ }
+ }
+ return ids.stream().map(id -> toVO(permissionMapper.selectByTenantAndId(tenantId, id)))
+ .collect(Collectors.toList());
+ }
+
+ private List<PermissionGrantItemForm> normalizeAndValidate(List<PermissionGrantItemForm> source) {
+ List<PermissionGrantItemForm> result = new ArrayList<>();
+ Set<String> identities = new HashSet<>();
+ for (PermissionGrantItemForm original : source) {
+ PermissionGrantItemForm item = copy(original);
+ validateItem(item);
+ String identity = identity(item);
+ if (!identities.add(identity)) {
+ throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
+ "鍚屼竴鎺堟潈鎵规鍖呭惈閲嶅鎺堟潈椤�");
+ }
+ result.add(item);
+ }
+ result.sort(Comparator.comparing(this::identity));
+ return result;
+ }
+
+ private PermissionGrantItemForm copy(PermissionGrantItemForm value) {
+ PermissionGrantItemForm result = new PermissionGrantItemForm();
+ result.setResourceType(value.getResourceType());
+ result.setResourceId(value.getResourceId());
+ result.setResourceScope(value.getResourceScope());
+ result.setPrincipalType(value.getPrincipalType());
+ result.setPrincipalId(value.getPrincipalId().trim());
+ result.setIncludeChildOrgs(value.isIncludeChildOrgs());
+ result.setActionCode(value.getActionCode());
+ result.setValidFrom(value.getValidFrom());
+ result.setValidTo(value.getValidTo());
+ result.setMaxUseCount(value.getMaxUseCount());
+ result.setGrantReason(trimToNull(value.getGrantReason()));
+ return result;
+ }
+
+ private void validateItem(PermissionGrantItemForm item) {
+ if (item.getResourceType() != DmsResourceType.FOLDER
+ && item.getResourceScope() != DmsResourceScope.SELF) {
+ invalid("Document 鍜� FileVersion 鍙厑璁� SELF 鑼冨洿");
+ }
+ if (item.getActionCode() == DmsPermissionAction.CREATE
+ && item.getResourceType() != DmsResourceType.FOLDER) {
+ invalid("CREATE 鍙厑璁告巿浜� Folder");
+ }
+ if (item.isIncludeChildOrgs() && item.getPrincipalType() != DmsPrincipalType.ORG) {
+ invalid("includeChildOrgs 鍙�傜敤浜� ORG 涓讳綋");
+ }
+ if (item.getMaxUseCount() != null && item.getPrincipalType() != DmsPrincipalType.USER) {
+ invalid("鎸夋鏉冮檺鍙厑璁告巿浜� USER 涓讳綋");
+ }
+ Date effectiveFrom = item.getValidFrom() == null ? new Date() : item.getValidFrom();
+ if (item.getValidTo() != null && !item.getValidTo().after(effectiveFrom)) {
+ invalid("validTo 蹇呴』鏅氫簬 validFrom");
+ }
+ }
+
+ private void validateResources(String tenantId, List<PermissionGrantItemForm> items) {
+ List<UUID> ids = items.stream().map(PermissionGrantItemForm::getResourceId).distinct()
+ .collect(Collectors.toList());
+ Map<UUID, DmsPermissionResourceEntity> resources = resourceMapper.selectByTenantAndIds(tenantId, ids)
+ .stream().collect(Collectors.toMap(DmsPermissionResourceEntity::getId, Function.identity()));
+ for (PermissionGrantItemForm item : items) {
+ DmsPermissionResourceEntity resource = resources.get(item.getResourceId());
+ if (resource == null || !item.getResourceType().name().equals(resource.getResourceType())) {
+ throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND,
+ "鎺堟潈璧勬簮涓嶅瓨鍦ㄦ垨绫诲瀷涓嶄竴鑷�: " + item.getResourceId());
+ }
+ }
+ }
+
+ private void verifyIdempotentRetry(List<DmsFilePermissionEntity> existing,
+ List<PermissionGrantItemForm> requested) {
+ if (existing.size() != requested.size() || existing.stream().anyMatch(row -> row.getRevokedAt() != null)) {
+ throw DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT,
+ "source_id 宸插瓨鍦紝浣嗘巿鏉冮泦鍚堟暟閲忎笉鍚屾垨鍖呭惈宸叉挙閿�鏉冮檺");
+ }
+ Map<String, DmsFilePermissionEntity> byIdentity = existing.stream().collect(Collectors.toMap(
+ this::identity, Function.identity(), (left, right) -> left, LinkedHashMap::new));
+ for (PermissionGrantItemForm item : requested) {
+ DmsFilePermissionEntity row = byIdentity.get(identity(item));
+ if (row == null || !sameDefinition(row, item)) {
+ throw DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT,
+ "source_id 宸插瓨鍦紝浣嗗畬鏁存巿鏉冮泦鍚堜笌鏈璇锋眰涓嶄竴鑷�");
+ }
+ }
+ }
+
+ private boolean sameDefinition(DmsFilePermissionEntity row, PermissionGrantItemForm item) {
+ return (item.getValidFrom() == null || sameInstant(row.getValidFrom(), item.getValidFrom()))
+ && sameInstant(row.getValidTo(), item.getValidTo())
+ && Objects.equals(row.getMaxUseCount(), item.getMaxUseCount())
+ && Objects.equals(row.getGrantReason(), item.getGrantReason());
+ }
+
+ private boolean sameFolderCreatorCreatePermission(DmsFilePermissionEntity row, String tenantId,
+ UUID folderId, String creatorUserId, String sourceId) {
+ return tenantId.equals(row.getTenantId())
+ && DmsResourceType.FOLDER.name().equals(row.getResourceType())
+ && folderId.equals(row.getResourceId())
+ && DmsResourceScope.SELF.name().equals(row.getResourceScope())
+ && DmsPrincipalType.USER.name().equals(row.getPrincipalType())
+ && creatorUserId.equals(row.getPrincipalId())
+ && Boolean.FALSE.equals(row.getIncludeChildOrgs())
+ && DmsPermissionAction.CREATE.name().equals(row.getActionCode())
+ && "SYSTEM".equals(row.getSourceType())
+ && sourceId.equals(row.getSourceId())
+ && row.getValidTo() == null
+ && row.getMaxUseCount() == null
+ && FOLDER_CREATOR_CREATE_REASON.equals(row.getGrantReason())
+ && DmsActorType.USER.name().equals(row.getGrantedByType())
+ && creatorUserId.equals(row.getGrantedById());
+ }
+
+ private DmsFilePermissionEntity toEntity(String tenantId, String sourceType, String sourceId,
+ PermissionGrantItemForm item) {
+ DmsFilePermissionEntity entity = new DmsFilePermissionEntity();
+ entity.setTenantId(tenantId);
+ entity.setResourceType(item.getResourceType().name());
+ entity.setResourceId(item.getResourceId());
+ entity.setResourceScope(item.getResourceScope().name());
+ entity.setPrincipalType(item.getPrincipalType().name());
+ entity.setPrincipalId(item.getPrincipalId());
+ entity.setIncludeChildOrgs(item.isIncludeChildOrgs());
+ entity.setActionCode(item.getActionCode().name());
+ entity.setSourceType(sourceType);
+ entity.setSourceId(sourceId);
+ entity.setValidFrom(item.getValidFrom() == null ? new Date() : item.getValidFrom());
+ entity.setValidTo(item.getValidTo());
+ entity.setMaxUseCount(item.getMaxUseCount());
+ entity.setGrantReason(item.getGrantReason());
+ entity.setGrantedByType(DmsActorType.USER.name());
+ entity.setGrantedById(currentIdentity.userId());
+ return entity;
+ }
+
+ private String identity(PermissionGrantItemForm item) {
+ return item.getResourceType() + "|" + item.getResourceId() + "|" + item.getResourceScope()
+ + "|" + item.getPrincipalType() + "|" + item.getPrincipalId() + "|"
+ + item.isIncludeChildOrgs() + "|" + item.getActionCode();
+ }
+
+ private String identity(DmsFilePermissionEntity item) {
+ return item.getResourceType() + "|" + item.getResourceId() + "|" + item.getResourceScope()
+ + "|" + item.getPrincipalType() + "|" + item.getPrincipalId() + "|"
+ + item.getIncludeChildOrgs() + "|" + item.getActionCode();
+ }
+
+ private boolean sameInstant(Date left, Date right) {
+ return left == null ? right == null : right != null && left.toInstant().equals(right.toInstant());
+ }
+
+ private String trimToNull(String value) {
+ return value == null || value.trim().isEmpty() ? null : value.trim();
+ }
+
+ private void invalid(String message) {
+ throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST, message);
+ }
+
+ public DmsPermissionGrantVO toVO(DmsFilePermissionEntity value) {
+ DmsPermissionGrantVO result = new DmsPermissionGrantVO();
+ result.setId(value.getId());
+ result.setResourceType(value.getResourceType());
+ result.setResourceId(value.getResourceId());
+ result.setResourceScope(value.getResourceScope());
+ result.setPrincipalType(value.getPrincipalType());
+ result.setPrincipalId(value.getPrincipalId());
+ result.setIncludeChildOrgs(value.getIncludeChildOrgs());
+ result.setActionCode(value.getActionCode());
+ result.setSourceType(value.getSourceType());
+ result.setSourceId(value.getSourceId());
+ result.setValidFrom(value.getValidFrom());
+ result.setValidTo(value.getValidTo());
+ result.setMaxUseCount(value.getMaxUseCount());
+ result.setUsedCount(value.getUsedCount());
+ result.setGrantReason(value.getGrantReason());
+ result.setRevokedAt(value.getRevokedAt());
+ result.setRevokeReason(value.getRevokeReason());
+ result.setRevision(value.getRevision());
+ result.setCreatedAt(value.getCreatedAt());
+ result.setUpdatedAt(value.getUpdatedAt());
+ return result;
+ }
+}
--
Gitblit v1.8.0