package jnpf.dmsService; import jnpf.base.UserInfo; import jnpf.dmsEntity.permission.entity.DmsDocumentEntity; import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity; import jnpf.dmsEntity.permission.entity.DmsFolderEntity; import jnpf.dmsEntity.permission.entity.DmsPermissionResourceEntity; import jnpf.dmsEntity.permission.enums.DmsPermissionAction; import jnpf.dmsEntity.permission.enums.DmsResourceType; import jnpf.dmsEntity.permission.form.DocumentCreateForm; import jnpf.dmsEntity.permission.form.FileVersionCreateForm; import jnpf.dmsEntity.permission.form.FolderCreateForm; import jnpf.dmsEntity.permission.form.FolderCreateUserForm; import jnpf.dmsEntity.permission.vo.DmsPermissionResourceVO; import jnpf.dmsIntegration.audit.DmsAuditAdapter; import jnpf.dmsMapper.DmsDocumentMapper; import jnpf.dmsMapper.DmsFileVersionMapper; import jnpf.dmsMapper.DmsFolderMapper; import jnpf.dmsMapper.DmsPermissionResourceMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsPermissionError; import jnpf.dmsPermission.DmsPermissionException; import lombok.RequiredArgsConstructor; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Locale; import java.util.Objects; import java.util.UUID; @Service @RequiredArgsConstructor public class DmsResourceService { private static final String WEB_FOLDER_REGISTRAR = "jnpf-dms-web"; private static final String WEB_FOLDER_SOURCE_PREFIX = "web-folder:"; private final DmsCurrentIdentity currentIdentity; private final DmsPermissionDecisionService decisionService; private final DmsFolderMapper folderMapper; private final DmsDocumentMapper documentMapper; private final DmsFileVersionMapper fileVersionMapper; private final DmsPermissionResourceMapper resourceMapper; private final DmsPermissionGrantService permissionGrantService; private final DmsAuditAdapter auditAdapter; @Transactional public DmsPermissionResourceVO createFolder(FolderCreateUserForm form) { UserInfo user = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(user); if (form.getParentId() == null) { throw DmsPermissionException.of(DmsPermissionError.INVALID_RESOURCE_PARENT, "网页端只能在已有 Folder 下创建子目录"); } decisionService.requireAllowed(DmsResourceType.FOLDER, form.getParentId(), DmsPermissionAction.CREATE); String sourceId = WEB_FOLDER_SOURCE_PREFIX + form.getSourceId().trim(); String name = form.getName().trim(); resourceMapper.lockSource(tenantId, "SYSTEM", sourceId); DmsPermissionResourceEntity existingRegistry = resourceMapper.selectBySource( tenantId, "SYSTEM", sourceId); if (existingRegistry != null) { DmsPermissionResourceVO existing = existingWebFolder(tenantId, user.getUserId(), form.getParentId(), name, sourceId, existingRegistry); permissionGrantService.ensureFolderCreatorCreatePermission( tenantId, existingRegistry.getId(), user.getUserId()); return existing; } UUID folderId = resourceMapper.nextUuidV7(); DmsFolderEntity folder = new DmsFolderEntity(); folder.setId(folderId); folder.setTenantId(tenantId); folder.setParentId(form.getParentId()); folder.setName(name); folder.setCreatedBy(user.getUserId()); try { folderMapper.insert(folder); insertRegistry(tenantId, folderId, DmsResourceType.FOLDER, sourceId, WEB_FOLDER_REGISTRAR); permissionGrantService.ensureFolderCreatorCreatePermission( tenantId, folderId, user.getUserId()); } catch (DuplicateKeyException ex) { throw conflict("Folder ID 或 source_id 已被其他资源占用", ex); } auditAdapter.dataChange("CREATE", "网页创建 DMS 子目录", "dms_folders", folderId.toString(), null, null); return requireView(tenantId, folderId); } @Transactional public DmsPermissionResourceVO createFolder(FolderCreateForm form, String serviceId) { UserInfo user = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(user); validateFolderMode(form); if (form.getParentId() != null) { decisionService.requireAllowed(DmsResourceType.FOLDER, form.getParentId(), DmsPermissionAction.CREATE); } DmsFolderEntity desired = new DmsFolderEntity(); desired.setId(form.getId()); desired.setTenantId(tenantId); desired.setParentId(form.getParentId()); desired.setName(form.getName().trim()); desired.setCreatedBy(user.getUserId()); DmsPermissionResourceVO existing = existingFolder(desired, form.getSourceId(), serviceId); if (existing != null) { return existing; } try { folderMapper.insert(desired); insertRegistry(tenantId, form.getId(), DmsResourceType.FOLDER, form.getSourceId(), serviceId); } catch (DuplicateKeyException ex) { throw conflict("Folder ID、父级、名称或 source_id 已被其他资源占用", ex); } auditAdapter.dataChange("CREATE", "创建 DMS Folder", "dms_folders", form.getId().toString(), null, null); return requireView(tenantId, form.getId()); } @Transactional public DmsPermissionResourceVO createDocument(DocumentCreateForm form, String serviceId) { UserInfo user = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(user); decisionService.requireAllowed(DmsResourceType.FOLDER, form.getFolderId(), DmsPermissionAction.CREATE); DmsDocumentEntity desired = new DmsDocumentEntity(); desired.setId(form.getId()); desired.setTenantId(tenantId); desired.setFolderId(form.getFolderId()); desired.setName(form.getName().trim()); desired.setCreatedBy(user.getUserId()); DmsPermissionResourceVO existing = existingDocument(desired, form.getSourceId(), serviceId); if (existing != null) { return existing; } try { documentMapper.insert(desired); insertRegistry(tenantId, form.getId(), DmsResourceType.DOCUMENT, form.getSourceId(), serviceId); } catch (DuplicateKeyException ex) { throw conflict("Document ID、所属 Folder 或 source_id 冲突", ex); } auditAdapter.dataChange("CREATE", "创建 DMS Document", "dms_documents", form.getId().toString(), null, null); return requireView(tenantId, form.getId()); } @Transactional public DmsPermissionResourceVO createFileVersion(FileVersionCreateForm form, String serviceId) { UserInfo user = currentIdentity.requireUser(); String tenantId = currentIdentity.tenantId(user); decisionService.requireAllowed(DmsResourceType.DOCUMENT, form.getDocumentId(), DmsPermissionAction.UPDATE); DmsFileVersionEntity desired = new DmsFileVersionEntity(); desired.setId(form.getId()); desired.setTenantId(tenantId); desired.setDocumentId(form.getDocumentId()); desired.setVersionNo(form.getVersionNo()); desired.setFileName(form.getFileName().trim()); desired.setMimeType(trimToNull(form.getMimeType())); desired.setSizeBytes(form.getSizeBytes()); desired.setChecksumSha256(form.getChecksumSha256() == null ? null : form.getChecksumSha256().toLowerCase(Locale.ROOT)); desired.setCreatedBy(user.getUserId()); DmsPermissionResourceVO existing = existingFileVersion(desired, form.getSourceId(), serviceId); if (existing != null) { return existing; } try { fileVersionMapper.insert(desired); insertRegistry(tenantId, form.getId(), DmsResourceType.FILE_VERSION, form.getSourceId(), serviceId); } catch (DuplicateKeyException ex) { throw conflict("FileVersion ID、版本号或 source_id 冲突", ex); } auditAdapter.dataChange("CREATE", "创建 DMS FileVersion", "dms_file_versions", form.getId().toString(), null, null); return requireView(tenantId, form.getId()); } private DmsPermissionResourceVO existingFolder(DmsFolderEntity desired, String sourceId, String serviceId) { DmsFolderEntity actual = folderMapper.selectByTenantAndId(desired.getTenantId(), desired.getId()); DmsPermissionResourceEntity registry = existingRegistry(desired.getTenantId(), desired.getId(), sourceId); if (actual == null && registry == null) { return null; } if (actual == null || registry == null || !Objects.equals(actual.getParentId(), desired.getParentId()) || !Objects.equals(actual.getName(), desired.getName()) || !sameRegistry(registry, DmsResourceType.FOLDER, sourceId, serviceId)) { throw conflict("Folder 创建重试内容与原请求不一致", null); } return requireView(desired.getTenantId(), desired.getId()); } private DmsPermissionResourceVO existingWebFolder(String tenantId, String userId, UUID parentId, String name, String sourceId, DmsPermissionResourceEntity registry) { DmsFolderEntity folder = folderMapper.selectByTenantAndId(tenantId, registry.getId()); if (folder == null || !DmsResourceType.FOLDER.name().equals(registry.getResourceType()) || !"SYSTEM".equals(registry.getSourceType()) || !sourceId.equals(registry.getSourceId()) || !WEB_FOLDER_REGISTRAR.equals(registry.getRegisteredBy()) || !userId.equals(folder.getCreatedBy()) || !Objects.equals(parentId, folder.getParentId()) || !name.equals(folder.getName())) { throw conflict("source_id 已存在,但子目录创建内容与原请求不一致", null); } return requireView(tenantId, registry.getId()); } private DmsPermissionResourceVO existingDocument(DmsDocumentEntity desired, String sourceId, String serviceId) { DmsDocumentEntity actual = documentMapper.selectByTenantAndId(desired.getTenantId(), desired.getId()); DmsPermissionResourceEntity registry = existingRegistry(desired.getTenantId(), desired.getId(), sourceId); if (actual == null && registry == null) { return null; } if (actual == null || registry == null || !Objects.equals(actual.getFolderId(), desired.getFolderId()) || !Objects.equals(actual.getName(), desired.getName()) || !sameRegistry(registry, DmsResourceType.DOCUMENT, sourceId, serviceId)) { throw conflict("Document 创建重试内容与原请求不一致", null); } return requireView(desired.getTenantId(), desired.getId()); } private DmsPermissionResourceVO existingFileVersion(DmsFileVersionEntity desired, String sourceId, String serviceId) { DmsFileVersionEntity actual = fileVersionMapper.selectByTenantAndId(desired.getTenantId(), desired.getId()); DmsPermissionResourceEntity registry = existingRegistry(desired.getTenantId(), desired.getId(), sourceId); if (actual == null && registry == null) { return null; } if (actual == null || registry == null || !Objects.equals(actual.getDocumentId(), desired.getDocumentId()) || !Objects.equals(actual.getVersionNo(), desired.getVersionNo()) || !Objects.equals(actual.getFileName(), desired.getFileName()) || !Objects.equals(actual.getMimeType(), desired.getMimeType()) || !Objects.equals(actual.getSizeBytes(), desired.getSizeBytes()) || !equalsIgnoreCase(actual.getChecksumSha256(), desired.getChecksumSha256()) || !sameRegistry(registry, DmsResourceType.FILE_VERSION, sourceId, serviceId)) { throw conflict("FileVersion 创建重试内容与原请求不一致", null); } return requireView(desired.getTenantId(), desired.getId()); } private DmsPermissionResourceEntity existingRegistry(String tenantId, UUID id, String sourceId) { DmsPermissionResourceEntity byId = resourceMapper.selectByTenantAndId(tenantId, id); DmsPermissionResourceEntity bySource = resourceMapper.selectBySource(tenantId, "SYSTEM", sourceId.trim()); if (bySource != null && !id.equals(bySource.getId())) { throw conflict("source_id 已绑定到另一个权限资源", null); } return byId; } private boolean sameRegistry(DmsPermissionResourceEntity registry, DmsResourceType type, String sourceId, String serviceId) { return type.name().equals(registry.getResourceType()) && "SYSTEM".equals(registry.getSourceType()) && sourceId.trim().equals(registry.getSourceId()) && serviceId.equals(registry.getRegisteredBy()); } private void insertRegistry(String tenantId, UUID id, DmsResourceType type, String sourceId, String serviceId) { DmsPermissionResourceEntity registry = new DmsPermissionResourceEntity(); registry.setId(id); registry.setTenantId(tenantId); registry.setResourceType(type.name()); registry.setSourceType("SYSTEM"); registry.setSourceId(sourceId.trim()); registry.setRegisteredBy(serviceId); resourceMapper.insert(registry); } private DmsPermissionResourceVO requireView(String tenantId, UUID id) { DmsPermissionResourceVO view = resourceMapper.selectView(tenantId, id); if (view == null) { throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND, "权限资源创建后无法读取: " + id); } return view; } private void validateFolderMode(FolderCreateForm form) { if (form.isRootInitialization() != (form.getParentId() == null)) { throw DmsPermissionException.of(DmsPermissionError.INVALID_RESOURCE_PARENT, "仅 rootInitialization=true 的 Folder 可以不提供 parentId"); } } private String trimToNull(String value) { if (value == null || value.trim().isEmpty()) { return null; } return value.trim(); } private boolean equalsIgnoreCase(String left, String right) { return left == null ? right == null : right != null && left.equalsIgnoreCase(right); } private DmsPermissionException conflict(String message, Throwable cause) { DmsPermissionException exception = DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT, message); if (cause != null) { exception.initCause(cause); } return exception; } }