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/dmsPermission/DmsPermissionGuard.java |  199 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 199 insertions(+), 0 deletions(-)

diff --git a/jnpf-dms/jnpf-dms-biz/src/main/java/jnpf/dmsPermission/DmsPermissionGuard.java b/jnpf-dms/jnpf-dms-biz/src/main/java/jnpf/dmsPermission/DmsPermissionGuard.java
new file mode 100644
index 0000000..66ef2a2
--- /dev/null
+++ b/jnpf-dms/jnpf-dms-biz/src/main/java/jnpf/dmsPermission/DmsPermissionGuard.java
@@ -0,0 +1,199 @@
+package jnpf.dmsPermission;
+
+import jnpf.base.UserInfo;
+import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity;
+import jnpf.dmsEntity.permission.entity.DmsPermissionUsageEntity;
+import jnpf.dmsEntity.permission.enums.DmsPermissionAction;
+import jnpf.dmsEntity.permission.form.PermissionDecisionForm;
+import jnpf.dmsEntity.permission.model.DmsResourcePath;
+import jnpf.dmsEntity.permission.model.DmsSubjectSet;
+import jnpf.dmsEntity.permission.vo.DmsPermissionDecisionVO;
+import jnpf.dmsMapper.DmsFilePermissionMapper;
+import jnpf.dmsMapper.DmsPermissionUsageMapper;
+import jnpf.dmsService.DmsPermissionDecisionService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.UUID;
+
+@Component
+@RequiredArgsConstructor
+public class DmsPermissionGuard {
+    private final DmsCurrentIdentity currentIdentity;
+    private final DmsPermissionDecisionService decisionService;
+    private final DmsFilePermissionMapper permissionMapper;
+    private final DmsPermissionUsageMapper usageMapper;
+    private final DmsResourceResolver resourceResolver;
+    private final DmsSubjectResolver subjectResolver;
+    private final DmsPermissionGroupMembershipResolver groupMembershipResolver;
+    private final DmsPermissionMatcher matcher;
+    private final DmsAccessExclusionResolver exclusionResolver;
+
+    @Transactional
+    public <T> T execute(DmsPermissionExecutionContext context, DmsPermissionOperation<T> operation) {
+        validateContext(context, operation);
+        UserInfo user = currentIdentity.requireUser();
+        String tenantId = currentIdentity.tenantId(user);
+        if (context.getOperationId() != null) {
+            DmsPermissionUsageEntity prior = usageMapper.selectByOperation(tenantId, user.getUserId(),
+                    context.getOperationId());
+            if (prior != null) {
+                DmsResourcePath path = resourceResolver.resolve(tenantId, context.getResourceType(),
+                        context.getResourceId());
+                ensureNotReadExcluded(context, tenantId, user.getUserId().trim(), path);
+                validateReplay(prior, context, user, tenantId, path);
+                return operation.replay();
+            }
+        }
+
+        PermissionDecisionForm request = new PermissionDecisionForm();
+        request.setResourceType(context.getResourceType());
+        request.setResourceId(context.getResourceId());
+        request.setActionCode(context.getActionCode());
+        DmsPermissionDecisionVO decision = decisionService.evaluate(request);
+        if (!decision.isAllowed()) {
+            throw DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
+                    "褰撳墠鐢ㄦ埛娌℃湁鎵�闇�鐨� " + context.getActionCode() + " 鏉冮檺");
+        }
+        DmsFilePermissionEntity permission = permissionMapper.selectByTenantAndIdForUpdate(tenantId,
+                decision.getMatchedPermissionId());
+        if (permission == null) {
+            throw DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED, "鍖归厤鏉冮檺宸蹭笉瀛樺湪");
+        }
+        if (context.getOperationId() != null) {
+            DmsPermissionUsageEntity concurrentPrior = usageMapper.selectByOperation(tenantId,
+                    user.getUserId(), context.getOperationId());
+            if (concurrentPrior != null) {
+                DmsResourcePath path = resourceResolver.resolve(tenantId, context.getResourceType(),
+                        context.getResourceId());
+                ensureNotReadExcluded(context, tenantId, user.getUserId().trim(), path);
+                validateReplay(concurrentPrior, context, user, tenantId, path);
+                return operation.replay();
+            }
+        }
+        DmsResourcePath path = resourceResolver.resolve(tenantId, context.getResourceType(),
+                context.getResourceId());
+        ensureNotReadExcluded(context, tenantId, user.getUserId().trim(), path);
+        validateLockedPermission(permission, context, user, tenantId, path);
+        if (permission.getMaxUseCount() == null) {
+            return operation.execute();
+        }
+        if (context.getOperationId() == null) {
+            throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
+                    "鎵ц鎸夋鏉冮檺蹇呴』鎻愪緵 operationId");
+        }
+
+        Date sessionExpiresAt = normalizedSessionExpiry(context);
+        T result = operation.execute();
+        DmsPermissionUsageEntity usage = new DmsPermissionUsageEntity();
+        usage.setTenantId(tenantId);
+        usage.setPermissionId(permission.getId());
+        usage.setUserId(user.getUserId());
+        usage.setActionCode(context.getActionCode().name());
+        usage.setActualResourceType(context.getResourceType().name());
+        usage.setActualResourceId(context.getResourceId());
+        usage.setOperationId(context.getOperationId());
+        usage.setSessionExpiresAt(sessionExpiresAt);
+        try {
+            usageMapper.insert(usage);
+        } catch (DuplicateKeyException ex) {
+            throw DmsPermissionException.of(DmsPermissionError.IDEMPOTENCY_CONFLICT,
+                    "operationId 宸茶骞跺彂璇锋眰浣跨敤锛岃鎸夌浉鍚� operationId 閲嶈瘯");
+        }
+        return result;
+    }
+
+    private void validateLockedPermission(DmsFilePermissionEntity permission,
+                                          DmsPermissionExecutionContext context, UserInfo user,
+                                          String tenantId, DmsResourcePath path) {
+        Date now = new Date();
+        if (!tenantId.equals(permission.getTenantId())
+                || !context.getActionCode().name().equals(permission.getActionCode())
+                || permission.getRevokedAt() != null
+                || permission.getValidFrom().after(now)
+                || (permission.getValidTo() != null && !now.before(permission.getValidTo()))) {
+            throw DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
+                    "鍖归厤鏉冮檺鍦ㄦ墽琛屽墠宸插け鏁�");
+        }
+        if (permission.getMaxUseCount() != null
+                && permission.getUsedCount() >= permission.getMaxUseCount()) {
+            throw DmsPermissionException.of(DmsPermissionError.PERMISSION_EXHAUSTED,
+                    "鍖归厤鏉冮檺鐨勪娇鐢ㄦ鏁板凡鐢ㄥ敖");
+        }
+        DmsSubjectSet subjects = subjectResolver.resolve(user);
+        if ("GROUP".equals(permission.getPrincipalType())) {
+            groupMembershipResolver.resolve(Collections.singleton(permission.getPrincipalId()), subjects);
+        }
+        if (!matcher.matches(permission, path, subjects)) {
+            throw DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
+                    "褰撳墠涓讳綋鎴栬祫婧愬湪鎵ц鍓嶅凡涓嶅啀鍖归厤鏉冮檺");
+        }
+    }
+
+    private void validateReplay(DmsPermissionUsageEntity prior, DmsPermissionExecutionContext context,
+                                UserInfo user, String tenantId, DmsResourcePath path) {
+        if (!context.getResourceType().name().equals(prior.getActualResourceType())
+                || !context.getResourceId().equals(prior.getActualResourceId())
+                || !context.getActionCode().name().equals(prior.getActionCode())) {
+            throw DmsPermissionException.of(DmsPermissionError.IDEMPOTENCY_CONFLICT,
+                    "operationId 宸茬敤浜庝笉鍚岃祫婧愭垨鍔ㄤ綔");
+        }
+        if ((context.getActionCode() == DmsPermissionAction.READ
+                || context.getActionCode() == DmsPermissionAction.UPDATE)
+                && prior.getSessionExpiresAt() != null && !new Date().before(prior.getSessionExpiresAt())) {
+            throw DmsPermissionException.of(DmsPermissionError.IDEMPOTENCY_CONFLICT,
+                    "operationId 瀵瑰簲鐨勬潈闄愪細璇濆凡杩囨湡锛岃浣跨敤鏂扮殑 operationId");
+        }
+        DmsFilePermissionEntity permission = permissionMapper.selectByTenantAndId(tenantId,
+                prior.getPermissionId());
+        Date now = new Date();
+        if (permission == null || permission.getRevokedAt() != null || permission.getValidFrom().after(now)
+                || (permission.getValidTo() != null && !now.before(permission.getValidTo()))) {
+            throw DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
+                    "鍘熸寜娆℃潈闄愬凡鎾ら攢鎴栦笉鍦ㄦ湁鏁堟湡鍐�");
+        }
+        DmsSubjectSet subjects = subjectResolver.resolve(user);
+        if ("GROUP".equals(permission.getPrincipalType())) {
+            groupMembershipResolver.resolve(Collections.singleton(permission.getPrincipalId()), subjects);
+        }
+        if (!permission.getActionCode().equals(context.getActionCode().name())
+                || !matcher.matches(permission, path, subjects)) {
+            throw DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
+                    "褰撳墠涓讳綋鎴栬祫婧愪笉鍐嶅尮閰嶅師鎸夋鏉冮檺");
+        }
+    }
+
+    private void ensureNotReadExcluded(DmsPermissionExecutionContext context, String tenantId,
+                                       String userId, DmsResourcePath path) {
+        if (context.getActionCode() == DmsPermissionAction.READ
+                && exclusionResolver.isExcluded(tenantId, userId, path)) {
+            throw DmsPermissionException.of(DmsPermissionError.PERMISSION_DENIED,
+                    "褰撳墠鐢ㄦ埛鐨� READ 鏉冮檺宸茶鎺掗櫎");
+        }
+    }
+
+    private Date normalizedSessionExpiry(DmsPermissionExecutionContext context) {
+        if (context.getActionCode() != DmsPermissionAction.READ
+                && context.getActionCode() != DmsPermissionAction.UPDATE) {
+            return null;
+        }
+        Date expiry = context.getSessionExpiresAt();
+        if (expiry != null && !expiry.after(new Date())) {
+            throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
+                    "sessionExpiresAt 蹇呴』鏅氫簬褰撳墠鏃堕棿");
+        }
+        return expiry;
+    }
+
+    private <T> void validateContext(DmsPermissionExecutionContext context, DmsPermissionOperation<T> operation) {
+        if (context == null || operation == null || context.getResourceType() == null
+                || context.getResourceId() == null || context.getActionCode() == null) {
+            throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
+                    "PermissionGuard 鎵ц涓婁笅鏂囦笉瀹屾暣");
+        }
+    }
+}

--
Gitblit v1.8.0