package jnpf.dmsService;
|
|
import jnpf.audit.sdk.annotation.AuditLog;
|
import jnpf.base.UserInfo;
|
import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity;
|
import jnpf.dmsEntity.permission.enums.DmsPersonalFolderType;
|
import jnpf.dmsEntity.permission.form.DmsPersonalFolderBorrowForm;
|
import jnpf.dmsEntity.permission.form.DmsPersonalFolderDownloadForm;
|
import jnpf.dmsEntity.permission.form.DmsPersonalFolderFavoriteForm;
|
import jnpf.dmsEntity.permission.form.DmsPersonalFolderFileForm;
|
import jnpf.dmsEntity.permission.enums.DmsPermissionAction;
|
import jnpf.dmsEntity.permission.enums.DmsResourceType;
|
import jnpf.dmsEntity.permission.vo.DmsPersonalFolderAddResultVO;
|
import jnpf.dmsEntity.permission.vo.DmsPersonalFolderFileVO;
|
import jnpf.dmsEntity.permission.vo.DmsPersonalFolderVO;
|
import jnpf.dmsEntity.print.form.DmsWorkflowPrintFilesGrantForm;
|
import jnpf.dmsEntity.print.vo.DmsWorkflowPrintFilesGrantResultVO;
|
import jnpf.dmsMapper.DmsPersonalFolderMapper;
|
import jnpf.dmsPermission.DmsCurrentIdentity;
|
import jnpf.dmsPermission.DmsFileSearchPolicy;
|
import jnpf.dmsPermission.DmsPermissionError;
|
import jnpf.dmsPermission.DmsPermissionException;
|
import jnpf.dmsPermission.DmsPermissionExecutionContext;
|
import jnpf.dmsPermission.DmsPermissionGuard;
|
import jnpf.dmsPermission.DmsPermissionOperation;
|
import jnpf.dmsPermission.DmsPermissionProperties;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.nio.charset.StandardCharsets;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.Date;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.TreeMap;
|
import java.util.UUID;
|
import java.util.stream.Collectors;
|
|
@Service
|
@RequiredArgsConstructor
|
public class DmsPersonalFolderService {
|
private static final String BORROW_PERMISSION_SOURCE_PREFIX = "personal-folder-borrow:";
|
private static final String DOWNLOAD_PERMISSION_SOURCE_PREFIX = "personal-folder-download:";
|
private static final String PRINT_PERMISSION_SOURCE_PREFIX = "workflow-print-files:";
|
|
private final DmsCurrentIdentity currentIdentity;
|
private final DmsPermissionProperties properties;
|
private final DmsPersonalFolderMapper personalFolderMapper;
|
private final DmsPermissionGrantService permissionGrantService;
|
private final DmsPermissionGuard permissionGuard;
|
|
public List<DmsPersonalFolderVO> folders() {
|
currentIdentity.requireUser();
|
return Arrays.stream(DmsPersonalFolderType.values())
|
.map(type -> new DmsPersonalFolderVO(type.name(), type.getLabel()))
|
.collect(Collectors.toList());
|
}
|
|
@Transactional(readOnly = true)
|
public List<DmsPersonalFolderFileVO> files(String folderType, String keyword, Integer pageSize) {
|
UserInfo user = currentIdentity.requireUser();
|
DmsPersonalFolderType type = folderType(folderType);
|
String normalizedKeyword = DmsFileSearchPolicy.normalizeKeyword(keyword);
|
return personalFolderMapper.selectFiles(
|
currentIdentity.tenantId(user),
|
user.getUserId().trim(),
|
type.name(),
|
normalizedKeyword,
|
pageSize(pageSize));
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
@AuditLog(action = "ADD_FAVORITE_SNAPSHOT", label = "收藏文件加入个人文件夹",
|
bizModule = "DMS", bizType = "DMS_PERSONAL_FOLDER", bizCodeExpr = "",
|
bizCodeRequired = false, targetTable = "dms_personal_folder_items",
|
targetIdExpr = "#p0.fileVersionId",
|
extraExpr = "{'documentId': #p0.documentId, 'operationId': #p0.operationId}")
|
public DmsPersonalFolderAddResultVO addFavoriteSnapshot(
|
DmsPersonalFolderFavoriteForm form) {
|
UserInfo user = currentIdentity.requireUser();
|
if (form == null || form.getDocumentId() == null || form.getFileVersionId() == null
|
|| form.getOperationId() == null) {
|
throw invalidRequest("收藏文件ID、文件版本ID和操作ID不能为空");
|
}
|
String tenantId = currentIdentity.tenantId(user);
|
String userId = user.getUserId().trim();
|
DmsPersonalFolderFileForm.FileRelation relation =
|
new DmsPersonalFolderFileForm.FileRelation();
|
relation.setDocumentId(form.getDocumentId());
|
relation.setFileVersionId(form.getFileVersionId());
|
validateRelations(tenantId, Collections.singletonList(relation));
|
|
DmsPermissionExecutionContext context = new DmsPermissionExecutionContext();
|
context.setResourceType(DmsResourceType.FILE_VERSION);
|
context.setResourceId(form.getFileVersionId());
|
context.setActionCode(DmsPermissionAction.READ);
|
context.setOperationId(form.getOperationId());
|
int added = permissionGuard.execute(context, new DmsPermissionOperation<Integer>() {
|
@Override
|
public Integer execute() {
|
return insertFavoriteSnapshot();
|
}
|
|
@Override
|
public Integer replay() {
|
return insertFavoriteSnapshot();
|
}
|
|
private int insertFavoriteSnapshot() {
|
return personalFolderMapper.insertFavoriteSnapshot(
|
tenantId, userId, form.getDocumentId(), form.getFileVersionId());
|
}
|
});
|
return new DmsPersonalFolderAddResultVO(1, added);
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
@AuditLog(action = "ADD_BORROW_SNAPSHOT", label = "借阅文件加入个人文件夹",
|
bizModule = "DMS", bizType = "DMS_PERSONAL_FOLDER", bizCodeExpr = "",
|
bizCodeRequired = false, targetTable = "dms_personal_folder_items",
|
targetIdExpr = "#p0.files[0].fileVersionId",
|
targetIdsExpr = "#p0.files.![fileVersionId]",
|
extraExpr = "{'userId': #p0.userId, 'serviceId': #p1, 'files': #p0.files}")
|
public DmsPersonalFolderAddResultVO addBorrowSnapshots(
|
DmsPersonalFolderBorrowForm form, String serviceId) {
|
UserInfo currentUser = currentIdentity.requireUser();
|
String tenantId = currentIdentity.tenantId(currentUser);
|
String userId = requiredText(form == null ? null : form.getUserId(), "借阅用户ID不能为空");
|
List<DmsPersonalFolderFileForm.FileRelation> files = distinctFiles(form, "借阅");
|
validateRelations(tenantId, files);
|
grantBorrowReadPermissions(tenantId, userId, files, serviceId);
|
int added = personalFolderMapper.insertBorrowSnapshots(tenantId, userId, files);
|
return new DmsPersonalFolderAddResultVO(form.getFiles().size(), added);
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
@AuditLog(action = "ADD_DOWNLOAD_SNAPSHOT", label = "下载文件加入个人文件夹",
|
bizModule = "DMS", bizType = "DMS_PERSONAL_FOLDER", bizCodeExpr = "",
|
bizCodeRequired = false, targetTable = "dms_personal_folder_items",
|
targetIdExpr = "#p0.files[0].fileVersionId",
|
targetIdsExpr = "#p0.files.![fileVersionId]",
|
extraExpr = "{'userId': #p0.userId, 'serviceId': #p1, 'files': #p0.files}")
|
public DmsPersonalFolderAddResultVO addDownloadSnapshots(
|
DmsPersonalFolderDownloadForm form, String serviceId) {
|
UserInfo currentUser = currentIdentity.requireUser();
|
String tenantId = currentIdentity.tenantId(currentUser);
|
String userId = requiredText(form == null ? null : form.getUserId(), "下载用户ID不能为空");
|
List<DmsPersonalFolderFileForm.FileRelation> files = distinctFiles(form, "下载");
|
validateRelations(tenantId, files);
|
grantDownloadPermissions(tenantId, userId, files, serviceId, form.getRequestId());
|
int added = personalFolderMapper.insertDownloadSnapshots(tenantId, userId, files);
|
return new DmsPersonalFolderAddResultVO(form.getFiles().size(), added);
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public DmsWorkflowPrintFilesGrantResultVO grantPrintPermissions(
|
DmsWorkflowPrintFilesGrantForm form, String serviceId) {
|
UserInfo currentUser = currentIdentity.requireUser();
|
String tenantId = currentIdentity.tenantId(currentUser);
|
String userId = requiredText(form == null ? null : form.getUserId(), "打印用户ID不能为空");
|
List<DmsPersonalFolderFileForm.FileRelation> files = distinctFiles(form, "打印");
|
validateRelations(tenantId, files);
|
grantPrintPermissions(tenantId, userId, files, serviceId);
|
return new DmsWorkflowPrintFilesGrantResultVO(form.getFiles().size(), files.size());
|
}
|
|
private List<DmsPersonalFolderFileForm.FileRelation> distinctFiles(
|
DmsPersonalFolderFileForm form, String actionLabel) {
|
if (form == null || form.getFiles() == null || form.getFiles().isEmpty()) {
|
throw invalidRequest(actionLabel + "文件不能为空");
|
}
|
if (form.getFiles().size() > 200) {
|
throw invalidRequest("单次最多添加200个" + actionLabel + "文件");
|
}
|
Map<UUID, DmsPersonalFolderFileForm.FileRelation> byVersion = new LinkedHashMap<>();
|
Map<UUID, UUID> versionByDocument = new LinkedHashMap<>();
|
for (DmsPersonalFolderFileForm.FileRelation file : form.getFiles()) {
|
if (file == null || file.getDocumentId() == null || file.getFileVersionId() == null) {
|
throw invalidRequest(actionLabel + "文件ID和文件版本ID不能为空");
|
}
|
DmsPersonalFolderFileForm.FileRelation normalized = copyWithValidatedValidity(file);
|
UUID previousVersion = versionByDocument.putIfAbsent(
|
normalized.getDocumentId(), normalized.getFileVersionId());
|
if (previousVersion != null && !previousVersion.equals(normalized.getFileVersionId())) {
|
throw invalidRequest("同一文件不能关联多个" + actionLabel + "版本:" + normalized.getDocumentId());
|
}
|
DmsPersonalFolderFileForm.FileRelation previous = byVersion.putIfAbsent(
|
normalized.getFileVersionId(), normalized);
|
if (previous != null && !Objects.equals(
|
previous.getValidTo(), normalized.getValidTo())) {
|
throw invalidRequest("同一文件版本不能设置多个" + actionLabel + "有效期:"
|
+ normalized.getFileVersionId());
|
}
|
}
|
return new ArrayList<>(byVersion.values());
|
}
|
|
private DmsPersonalFolderFileForm.FileRelation copyWithValidatedValidity(
|
DmsPersonalFolderFileForm.FileRelation file) {
|
DmsPersonalFolderFileForm.FileRelation normalized =
|
new DmsPersonalFolderFileForm.FileRelation();
|
normalized.setDocumentId(file.getDocumentId());
|
normalized.setFileVersionId(file.getFileVersionId());
|
normalized.setValidTo(optionalFuture(file.getValidTo()));
|
return normalized;
|
}
|
|
private void grantBorrowReadPermissions(
|
String tenantId, String userId,
|
List<DmsPersonalFolderFileForm.FileRelation> files, String serviceId) {
|
Map<Long, List<DmsPersonalFolderFileForm.FileRelation>> filesByValidity = groupByValidity(files);
|
for (Map.Entry<Long, List<DmsPersonalFolderFileForm.FileRelation>> entry
|
: filesByValidity.entrySet()) {
|
Date validTo = entry.getKey() == null ? null : new Date(entry.getKey());
|
List<UUID> fileVersionIds = versionIds(entry.getValue());
|
permissionGrantService.ensureWorkflowBorrowReadPermissions(
|
tenantId, permissionSourceId(BORROW_PERMISSION_SOURCE_PREFIX, userId,
|
fileVersionIds, validTo), userId,
|
fileVersionIds, validTo, serviceId, Collections.emptyList());
|
}
|
}
|
|
private void grantDownloadPermissions(
|
String tenantId, String userId,
|
List<DmsPersonalFolderFileForm.FileRelation> files, String serviceId, String requestId) {
|
Map<Long, List<DmsPersonalFolderFileForm.FileRelation>> filesByValidity = groupByValidity(files);
|
for (Map.Entry<Long, List<DmsPersonalFolderFileForm.FileRelation>> entry
|
: filesByValidity.entrySet()) {
|
Date validTo = entry.getKey() == null ? null : new Date(entry.getKey());
|
List<UUID> fileVersionIds = versionIds(entry.getValue());
|
List<jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity> permissions =
|
permissionGrantService.ensureWorkflowDownloadPermissions(
|
tenantId, permissionSourceId(DOWNLOAD_PERMISSION_SOURCE_PREFIX,
|
requestId == null || requestId.trim().isEmpty()
|
? userId : userId + "|request:" + requestId.trim(),
|
fileVersionIds, validTo), userId,
|
fileVersionIds, validTo, serviceId, Collections.emptyList());
|
if (permissions != null) {
|
for (DmsPersonalFolderFileForm.FileRelation file : entry.getValue()) {
|
permissions.stream().filter(permission -> file.getFileVersionId().equals(permission.getResourceId()))
|
.findFirst().ifPresent(permission -> file.setValidTo(permission.getValidTo()));
|
}
|
}
|
}
|
}
|
|
private void grantPrintPermissions(
|
String tenantId, String userId,
|
List<DmsPersonalFolderFileForm.FileRelation> files, String serviceId) {
|
Map<Long, List<DmsPersonalFolderFileForm.FileRelation>> filesByValidity = groupByValidity(files);
|
for (Map.Entry<Long, List<DmsPersonalFolderFileForm.FileRelation>> entry
|
: filesByValidity.entrySet()) {
|
Date validTo = entry.getKey() == null ? null : new Date(entry.getKey());
|
List<UUID> fileVersionIds = versionIds(entry.getValue());
|
permissionGrantService.ensureWorkflowPrintPermissions(
|
tenantId, permissionSourceId(PRINT_PERMISSION_SOURCE_PREFIX, userId,
|
fileVersionIds, validTo), userId,
|
fileVersionIds, validTo, serviceId, Collections.emptyList());
|
}
|
}
|
|
private Map<Long, List<DmsPersonalFolderFileForm.FileRelation>> groupByValidity(
|
List<DmsPersonalFolderFileForm.FileRelation> files) {
|
Map<Long, List<DmsPersonalFolderFileForm.FileRelation>> filesByValidity =
|
new TreeMap<>(Comparator.nullsLast(Long::compareTo));
|
for (DmsPersonalFolderFileForm.FileRelation file : files) {
|
Long validTo = file.getValidTo() == null ? null : file.getValidTo().getTime();
|
filesByValidity.computeIfAbsent(validTo, ignored -> new ArrayList<>())
|
.add(file);
|
}
|
return filesByValidity;
|
}
|
|
private List<UUID> versionIds(List<DmsPersonalFolderFileForm.FileRelation> files) {
|
return files.stream().map(DmsPersonalFolderFileForm.FileRelation::getFileVersionId)
|
.collect(Collectors.toList());
|
}
|
|
private void validateRelations(
|
String tenantId, List<DmsPersonalFolderFileForm.FileRelation> files) {
|
List<UUID> versionIds = files.stream()
|
.map(DmsPersonalFolderFileForm.FileRelation::getFileVersionId)
|
.collect(Collectors.toList());
|
List<DmsFileVersionEntity> readableVersions = personalFolderMapper
|
.selectReadableVersions(tenantId, versionIds);
|
if (readableVersions == null) {
|
readableVersions = new ArrayList<>();
|
}
|
Map<UUID, DmsFileVersionEntity> versions = readableVersions.stream()
|
.collect(Collectors.toMap(DmsFileVersionEntity::getId, value -> value));
|
for (DmsPersonalFolderFileForm.FileRelation file : files) {
|
DmsFileVersionEntity version = versions.get(file.getFileVersionId());
|
if (version == null) {
|
throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND,
|
"文件版本不存在或不可用:" + file.getFileVersionId());
|
}
|
if (!file.getDocumentId().equals(version.getDocumentId())) {
|
throw invalidRequest("文件与文件版本关联不一致:" + file.getFileVersionId());
|
}
|
}
|
}
|
|
private DmsPersonalFolderType folderType(String value) {
|
try {
|
return DmsPersonalFolderType.valueOf(value == null ? "" : value.trim().toUpperCase(Locale.ROOT));
|
} catch (IllegalArgumentException ex) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
|
"个人文件夹类型无效");
|
}
|
}
|
|
private int pageSize(Integer requested) {
|
int value = requested == null ? properties.getDefaultPageSize() : requested;
|
if (value < 1 || value > properties.getMaxPageSize()) {
|
throw DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST,
|
"pageSize 必须在 1 到 " + properties.getMaxPageSize() + " 之间");
|
}
|
return value;
|
}
|
|
private String requiredText(String value, String message) {
|
if (value == null || value.trim().isEmpty()) {
|
throw invalidRequest(message);
|
}
|
return value.trim();
|
}
|
|
private Date optionalFuture(Date value) {
|
if (value == null) {
|
return null;
|
}
|
Date copy = new Date(value.getTime());
|
if (!copy.after(new Date())) {
|
throw invalidRequest("有效期必须晚于当前时间");
|
}
|
return copy;
|
}
|
|
private String permissionSourceId(String prefix, String userId,
|
List<UUID> fileVersionIds, Date validTo) {
|
List<UUID> sortedIds = new ArrayList<>(fileVersionIds);
|
sortedIds.sort(Comparator.comparing(UUID::toString));
|
String validity = validTo == null ? "PERMANENT" : Long.toString(validTo.getTime());
|
String canonical = userId + "|" + validity + "|" + sortedIds.stream()
|
.map(UUID::toString).collect(Collectors.joining(","));
|
UUID fingerprint = UUID.nameUUIDFromBytes(canonical.getBytes(StandardCharsets.UTF_8));
|
return prefix + fingerprint;
|
}
|
|
private DmsPermissionException invalidRequest(String message) {
|
return DmsPermissionException.of(DmsPermissionError.INVALID_REQUEST, message);
|
}
|
}
|