package jnpf.dmsService;
|
|
import jnpf.base.UserInfo;
|
import jnpf.dmsEntity.permission.entity.DmsFolderEntity;
|
import jnpf.dmsEntity.permission.entity.DmsPermissionResourceEntity;
|
import jnpf.dmsEntity.permission.enums.DmsFolderReorderDirection;
|
import jnpf.dmsEntity.permission.enums.DmsResourceType;
|
import jnpf.dmsEntity.permission.form.FolderCopyForm;
|
import jnpf.dmsEntity.permission.form.FolderAuditSignForm;
|
import jnpf.dmsEntity.permission.form.FolderMoveForm;
|
import jnpf.dmsEntity.permission.form.FolderRenameForm;
|
import jnpf.dmsEntity.permission.form.FolderReorderForm;
|
import jnpf.dmsEntity.permission.vo.DmsPermissionResourceVO;
|
import jnpf.dmsIntegration.audit.DmsAuditAdapter;
|
import jnpf.dmsIntegration.audit.DmsAuditRequestSignResolver;
|
import jnpf.dmsIntegration.audit.DmsFolderAuditEvent;
|
import jnpf.dmsIntegration.audit.DmsFolderAuditSnapshot;
|
import jnpf.dmsMapper.DmsFolderMapper;
|
import jnpf.dmsMapper.DmsPermissionResourceMapper;
|
import jnpf.dmsMapper.DmsPermissionRevisionMapper;
|
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.ArrayList;
|
import java.util.Comparator;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.UUID;
|
|
@Service
|
@RequiredArgsConstructor
|
public class DmsFolderManagementService {
|
private static final String COPY_SOURCE_PREFIX = "web-folder-copy:";
|
private static final String WEB_FOLDER_REGISTRAR = "jnpf-dms-web";
|
|
private final DmsCurrentIdentity currentIdentity;
|
private final DmsFolderMapper folderMapper;
|
private final DmsPermissionResourceMapper resourceMapper;
|
private final DmsPermissionRevisionMapper permissionRevisionMapper;
|
private final DmsPermissionGrantService permissionGrantService;
|
private final DmsAuditAdapter auditAdapter;
|
|
@Transactional
|
public DmsPermissionResourceVO rename(UUID id, FolderRenameForm form) {
|
Actor actor = actor();
|
DmsFolderEntity snapshot = requireFolder(actor.tenantId, id);
|
lockSiblingSets(actor.tenantId, snapshot.getParentId());
|
DmsFolderEntity folder = requireLockedFolder(actor.tenantId, id);
|
requireRevision(folder, form.getRevision());
|
String name = form.getName().trim();
|
if (name.equals(folder.getName())) {
|
return requireView(actor.tenantId, id);
|
}
|
if (folderMapper.siblingNameExists(actor.tenantId, folder.getParentId(), name, id)) {
|
throw conflict("同级目录名称已存在", null);
|
}
|
String oldPath = folderMapper.selectPath(actor.tenantId, id);
|
if (folderMapper.rename(actor.tenantId, id, name, folder.getRevision(), actor.userId) != 1) {
|
throw revisionConflict();
|
}
|
auditAdapter.folderChange(folderEvent(id, "UPDATE", "重命名 DMS 文件夹",
|
folderSnapshot(folder, oldPath),
|
folderSnapshot(actor.tenantId, id, folder.getParentId(), name, folder.getRevision() + 1,
|
folderMapper.selectPath(actor.tenantId, id)),
|
form.getRevision(), form));
|
return requireView(actor.tenantId, id);
|
}
|
|
@Transactional
|
public DmsPermissionResourceVO reorder(UUID id, FolderReorderForm form) {
|
Actor actor = actor();
|
DmsFolderEntity snapshot = requireFolder(actor.tenantId, id);
|
lockSiblingSets(actor.tenantId, snapshot.getParentId());
|
DmsFolderEntity folder = requireLockedFolder(actor.tenantId, id);
|
requireRevision(folder, form.getRevision());
|
DmsFolderEntity adjacent = form.getDirection() == DmsFolderReorderDirection.UP
|
? folderMapper.selectPreviousForUpdate(actor.tenantId, folder.getParentId(),
|
folder.getSortCode(), folder.getId())
|
: folderMapper.selectNextForUpdate(actor.tenantId, folder.getParentId(),
|
folder.getSortCode(), folder.getId());
|
if (adjacent == null) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
|
form.getDirection() == DmsFolderReorderDirection.UP
|
? "目录已经位于同级第一项" : "目录已经位于同级最后一项");
|
}
|
long folderSort = folder.getSortCode();
|
long adjacentSort = adjacent.getSortCode();
|
if (folderMapper.reorder(actor.tenantId, folder.getId(), adjacentSort,
|
folder.getRevision(), actor.userId) != 1
|
|| folderMapper.reorder(actor.tenantId, adjacent.getId(), folderSort,
|
adjacent.getRevision(), actor.userId) != 1) {
|
throw revisionConflict();
|
}
|
auditAdapter.dataChange("UPDATE", "调整 DMS 同级目录排序", "dms_folders",
|
id.toString(), null, null);
|
return requireView(actor.tenantId, id);
|
}
|
|
@Transactional
|
public DmsPermissionResourceVO move(UUID id, FolderMoveForm form) {
|
Actor actor = actor();
|
DmsFolderEntity snapshot = requireFolder(actor.tenantId, id);
|
if (snapshot.getParentId() == null) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_RESOURCE_PARENT,
|
"根目录不能剪切移动");
|
}
|
requireFolder(actor.tenantId, form.getParentId());
|
if (Objects.equals(snapshot.getParentId(), form.getParentId())) {
|
requireRevision(snapshot, form.getRevision());
|
return requireView(actor.tenantId, id);
|
}
|
if (folderMapper.targetIsSelfOrDescendant(actor.tenantId, id, form.getParentId())) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_RESOURCE_PARENT,
|
"目录不能移动到自身或其后代目录");
|
}
|
|
lockSiblingSets(actor.tenantId, snapshot.getParentId(), form.getParentId());
|
DmsFolderEntity folder = requireLockedFolder(actor.tenantId, id);
|
requireLockedFolder(actor.tenantId, form.getParentId());
|
requireRevision(folder, form.getRevision());
|
if (folderMapper.siblingNameExists(actor.tenantId, form.getParentId(), folder.getName(), null)) {
|
throw conflict("目标目录下已存在同名目录", null);
|
}
|
long sortCode = folderMapper.nextSortCode(actor.tenantId, form.getParentId());
|
String oldPath = folderMapper.selectPath(actor.tenantId, id);
|
if (folderMapper.move(actor.tenantId, id, form.getParentId(), sortCode,
|
folder.getRevision(), actor.userId) != 1) {
|
throw revisionConflict();
|
}
|
permissionRevisionMapper.bump(actor.tenantId);
|
auditAdapter.folderChange(folderEvent(id, "UPDATE", "移动 DMS 文件夹",
|
folderSnapshot(folder, oldPath),
|
folderSnapshot(actor.tenantId, id, form.getParentId(), folder.getName(), folder.getRevision() + 1,
|
folderMapper.selectPath(actor.tenantId, id)),
|
form.getRevision(), form));
|
return requireView(actor.tenantId, id);
|
}
|
|
@Transactional
|
public DmsPermissionResourceVO copy(UUID id, FolderCopyForm form) {
|
Actor actor = actor();
|
DmsFolderEntity source = requireFolder(actor.tenantId, id);
|
requireFolder(actor.tenantId, form.getParentId());
|
if (folderMapper.targetIsSelfOrDescendant(actor.tenantId, id, form.getParentId())) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_RESOURCE_PARENT,
|
"目录不能复制到自身或其后代目录");
|
}
|
String requestId = form.getRequestId().trim();
|
String rootSourceId = copySourceId(requestId, id);
|
resourceMapper.lockSource(actor.tenantId, "SYSTEM", rootSourceId);
|
DmsPermissionResourceEntity existing = resourceMapper.selectBySource(
|
actor.tenantId, "SYSTEM", rootSourceId);
|
if (existing != null) {
|
DmsFolderEntity existingFolder = folderMapper.selectByTenantAndId(actor.tenantId, existing.getId());
|
if (existingFolder == null || !Objects.equals(existingFolder.getParentId(), form.getParentId())
|
|| !actor.userId.equals(existingFolder.getCreatedBy())) {
|
throw DmsPermissionException.of(DmsPermissionError.IDEMPOTENCY_CONFLICT,
|
"复制请求 ID 已用于其他目标或用户");
|
}
|
return requireView(actor.tenantId, existing.getId());
|
}
|
|
List<DmsFolderEntity> subtree = folderMapper.selectSubtree(actor.tenantId, id);
|
if (subtree.isEmpty()) {
|
throw notFound(id);
|
}
|
lockSiblingSets(actor.tenantId, form.getParentId());
|
String rootName = uniqueCopyName(actor.tenantId, form.getParentId(), source.getName());
|
long rootSortCode = folderMapper.nextSortCode(actor.tenantId, form.getParentId());
|
Map<UUID, UUID> copiedIds = new HashMap<>();
|
UUID copiedRootId = null;
|
try {
|
for (DmsFolderEntity original : subtree) {
|
UUID copiedId = resourceMapper.nextUuidV7();
|
UUID copiedParentId = original.getId().equals(id)
|
? form.getParentId() : copiedIds.get(original.getParentId());
|
if (copiedParentId == null) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_RESOURCE_PARENT,
|
"复制目录结构不完整");
|
}
|
DmsFolderEntity copy = new DmsFolderEntity();
|
copy.setId(copiedId);
|
copy.setTenantId(actor.tenantId);
|
copy.setParentId(copiedParentId);
|
copy.setName(original.getId().equals(id) ? rootName : original.getName());
|
copy.setCreatedBy(actor.userId);
|
copy.setSortCode(original.getId().equals(id) ? rootSortCode : original.getSortCode());
|
copy.setRevision(0L);
|
copy.setDeleteMark(null);
|
folderMapper.insert(copy);
|
insertRegistry(actor.tenantId, copiedId, copySourceId(requestId, original.getId()));
|
permissionGrantService.ensureFolderCreatorCreatePermission(
|
actor.tenantId, copiedId, actor.userId);
|
copiedIds.put(original.getId(), copiedId);
|
if (original.getId().equals(id)) {
|
copiedRootId = copiedId;
|
}
|
}
|
} catch (DuplicateKeyException ex) {
|
throw conflict("复制目录时发生名称或资源冲突", ex);
|
}
|
auditAdapter.dataChange("CREATE", "复制 DMS 目录结构", "dms_folders",
|
Objects.requireNonNull(copiedRootId).toString(), null,
|
"{\"folderCount\":" + subtree.size() + "}");
|
return requireView(actor.tenantId, copiedRootId);
|
}
|
|
@Transactional
|
public void delete(UUID id, long revision) {
|
Actor actor = actor();
|
DmsFolderEntity snapshot = requireFolder(actor.tenantId, id);
|
if (snapshot.getParentId() == null) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_RESOURCE_PARENT,
|
"根目录不能删除");
|
}
|
lockSiblingSets(actor.tenantId, snapshot.getParentId());
|
DmsFolderEntity folder = requireLockedFolder(actor.tenantId, id);
|
requireRevision(folder, revision);
|
if (folderMapper.hasContents(actor.tenantId, id)) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
|
"只能删除不包含子目录和文件的空目录");
|
}
|
String oldPath = folderMapper.selectPath(actor.tenantId, id);
|
if (folderMapper.softDelete(actor.tenantId, id, folder.getRevision(), actor.userId) != 1) {
|
throw revisionConflict();
|
}
|
permissionRevisionMapper.bump(actor.tenantId);
|
auditAdapter.folderChange(folderEvent(id, "DELETE", "删除 DMS 文件夹",
|
folderSnapshot(folder, oldPath), null, revision, null));
|
}
|
|
private String uniqueCopyName(String tenantId, UUID parentId, String sourceName) {
|
String base = sourceName.length() <= 294 ? sourceName : sourceName.substring(0, 294);
|
String candidate = base + " - 副本";
|
int suffix = 2;
|
while (folderMapper.siblingNameExists(tenantId, parentId, candidate, null)) {
|
String ending = " - 副本 (" + suffix++ + ")";
|
int baseLength = Math.min(sourceName.length(), 300 - ending.length());
|
candidate = sourceName.substring(0, baseLength) + ending;
|
if (suffix > 1000) {
|
throw conflict("无法为复制目录生成唯一名称", null);
|
}
|
}
|
return candidate;
|
}
|
|
private void insertRegistry(String tenantId, UUID id, String sourceId) {
|
DmsPermissionResourceEntity registry = new DmsPermissionResourceEntity();
|
registry.setId(id);
|
registry.setTenantId(tenantId);
|
registry.setResourceType(DmsResourceType.FOLDER.name());
|
registry.setSourceType("SYSTEM");
|
registry.setSourceId(sourceId);
|
registry.setRegisteredBy(WEB_FOLDER_REGISTRAR);
|
resourceMapper.insert(registry);
|
}
|
|
private String copySourceId(String requestId, UUID sourceId) {
|
return COPY_SOURCE_PREFIX + requestId + ":" + sourceId;
|
}
|
|
private Actor actor() {
|
currentIdentity.requirePermissionAdministrator();
|
UserInfo user = currentIdentity.requireUser();
|
return new Actor(currentIdentity.tenantId(user), user.getUserId().trim());
|
}
|
|
private DmsFolderEntity requireFolder(String tenantId, UUID id) {
|
DmsFolderEntity folder = folderMapper.selectByTenantAndId(tenantId, id);
|
if (folder == null) {
|
throw notFound(id);
|
}
|
return folder;
|
}
|
|
private DmsFolderEntity requireLockedFolder(String tenantId, UUID id) {
|
DmsFolderEntity folder = folderMapper.selectByTenantAndIdForUpdate(tenantId, id);
|
if (folder == null) {
|
throw notFound(id);
|
}
|
return folder;
|
}
|
|
private void requireRevision(DmsFolderEntity folder, Long expectedRevision) {
|
if (expectedRevision == null || !expectedRevision.equals(folder.getRevision())) {
|
throw revisionConflict();
|
}
|
}
|
|
private DmsPermissionResourceVO requireView(String tenantId, UUID id) {
|
DmsPermissionResourceVO view = resourceMapper.selectView(tenantId, id);
|
if (view == null) {
|
throw notFound(id);
|
}
|
return view;
|
}
|
|
private void lockSiblingSets(String tenantId, UUID... parentIds) {
|
List<UUID> ids = new ArrayList<>();
|
for (UUID parentId : parentIds) {
|
if (!ids.contains(parentId)) {
|
ids.add(parentId);
|
}
|
}
|
ids.sort(Comparator.comparing(id -> id == null ? "" : id.toString()));
|
for (UUID parentId : ids) {
|
folderMapper.lockSiblingSet(tenantId, parentId);
|
}
|
}
|
|
private DmsPermissionException revisionConflict() {
|
return DmsPermissionException.of(DmsPermissionError.REVISION_CONFLICT,
|
"目录已被其他管理员修改,请刷新后重试");
|
}
|
|
private DmsPermissionException notFound(UUID id) {
|
return DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND,
|
"目录不存在或已删除: " + id);
|
}
|
|
private DmsPermissionException conflict(String message, Throwable cause) {
|
DmsPermissionException exception = DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT, message);
|
if (cause != null) {
|
exception.initCause(cause);
|
}
|
return exception;
|
}
|
|
private DmsFolderAuditEvent folderEvent(UUID id, String actionCode, String actionLabel,
|
DmsFolderAuditSnapshot before,
|
DmsFolderAuditSnapshot after,
|
Long requestRevision,
|
FolderAuditSignForm signForm) {
|
return DmsFolderAuditEvent.builder()
|
.folderId(id)
|
.actionCode(actionCode)
|
.actionLabel(actionLabel)
|
.before(before)
|
.after(after)
|
.requestRevision(requestRevision)
|
.relatedSigns(DmsAuditRequestSignResolver.resolve(
|
signForm == null ? null : signForm.getAuditSignParameters()))
|
.entryType("MENU")
|
.entryId("dms-folder-management")
|
.entryName("DMS 文件夹管理")
|
.build();
|
}
|
|
private DmsFolderAuditSnapshot folderSnapshot(DmsFolderEntity folder, String path) {
|
return folderSnapshot(folder.getTenantId(), folder.getId(), folder.getParentId(), folder.getName(),
|
folder.getRevision(), path);
|
}
|
|
private DmsFolderAuditSnapshot folderSnapshot(String tenantId, UUID id, UUID parentId, String name,
|
Long revision, String path) {
|
return DmsFolderAuditSnapshot.builder()
|
.id(id)
|
.parentId(parentId)
|
.name(name)
|
.parentName(parentName(tenantId, parentId))
|
.revision(revision)
|
.path(path)
|
.build();
|
}
|
|
private String parentName(String tenantId, UUID parentId) {
|
if (parentId == null) {
|
return "根目录";
|
}
|
DmsFolderEntity parent = folderMapper.selectByTenantAndId(tenantId, parentId);
|
return parent == null ? null : parent.getName();
|
}
|
|
private static class Actor {
|
private final String tenantId;
|
private final String userId;
|
|
private Actor(String tenantId, String userId) {
|
this.tenantId = tenantId;
|
this.userId = userId;
|
}
|
}
|
}
|