数据总览 前端按钮权限设置

This commit is contained in:
2025-12-29 16:41:41 +08:00
parent 059884ecc0
commit e8e2061b59
8 changed files with 180 additions and 15 deletions

View File

@@ -114,6 +114,9 @@ public class FileMetadataInfoResp implements Serializable {
@Schema(description = "完整访问URL拼接minio网关地址 + objectKey")
private String fileUrl;
@Schema(description = "权限值")
private Integer permissionValue;
private String approvalStatus;
private String approveType;
private String tempMetadata;

View File

@@ -98,6 +98,10 @@ public class FileMetadataInfo implements Serializable {
@TableField(value = "creatorName", insertStrategy = FieldStrategy.NEVER,select = false,updateStrategy = FieldStrategy.NEVER)
private String creatorName;
@Schema(description= "更新者名称,列表展示使用")
@TableField(value = "updaterName", insertStrategy = FieldStrategy.NEVER,select = false,updateStrategy = FieldStrategy.NEVER)
private String updaterName;
@Schema(description= "创建时间")
@TableField("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

View File

@@ -21,4 +21,12 @@ public interface IFileUserPermissionService extends IService<FileUserPermission>
* @return 是否有权限
*/
boolean hasFilePermission(Long fileId, Long userId, FilePermissionEnum permission);
/**
* 获取文件权限位掩码
* @param fileId 文件ID
* @param userId 用户ID
* @return 权限位掩码
*/
Integer getMergedPermission(Long fileId, Long userId);
}

View File

@@ -83,4 +83,48 @@ public class FileUserPermissionServiceImpl extends ServiceImpl<FileUserPermissio
return validPermission(parentId, userId, permission);
}
@Override
public Integer getMergedPermission(Long fileId, Long userId) {
if (fileId == null || userId == null) return (int) FilePermissionEnum.ZERO.getValue();
FileMetadataInfo fileInfo = fileMetadataInfoService.getById(fileId);
if (fileInfo == null) return (int) FilePermissionEnum.ZERO.getValue();
// 1. 系统内置基础文件夹,通常给满权限
for (DirTypeEnum dirType : DirTypeEnum.getInitSpmdDir()) {
if (fileInfo.getOriginalName().equals(dirType.getDirName())) {
return (int) FilePermissionEnum.ALL.getValue(); // 1|2|4|8|16 = 31 (全权限)
}
}
// 开始递归计算合并权限
return calculateRecursiveMergedPermission(fileId, userId);
}
private int calculateRecursiveMergedPermission(Long fileId, Long userId) {
// 默认只读权限
byte currentPerm = FilePermissionEnum.READ.getValue();
// 查询当前节点该用户的显式权限记录
FileUserPermission userPermRecord = this.lambdaQuery()
.eq(FileUserPermission::getTFilemetaId, fileId)
.eq(FileUserPermission::getUserId, userId)
.one();
if (userPermRecord != null) {
currentPerm = userPermRecord.getPermission();
}
// 获取父节点
FileMetadataInfo currentFile = fileMetadataInfoService.getById(fileId);
// 如果有父级且不是根目录,则继续向上合并
if (currentFile != null && currentFile.getParentId() != null && currentFile.getParentId() > 0) {
// 使用 | (按位或) 合并当前权限与父级权限
return (byte) (currentPerm | calculateRecursiveMergedPermission(currentFile.getParentId(), userId));
}
return currentPerm;
}
}

View File

@@ -57,13 +57,11 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.assertj.core.util.DateUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
@@ -644,7 +642,20 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
setAnalysisDirectionName(files);
setSimulationPoolAndTaskInfo(files);
PageInfo<FileMetadataInfo> page = new PageInfo<>(files);
return PageUtils.getJsonObjectSdmResponse(files, page);
long total = page.getTotal();
List<FileMetadataInfoResp> dtoList = files.stream().map(entity -> {
FileMetadataInfoResp dto = new FileMetadataInfoResp();
BeanUtils.copyProperties(entity, dto);
//计算当前用户对该文件的综合权限位
// 对于列表查询,如果层级很深,频繁递归会有性能问题。
dto.setPermissionValue(fileUserPermissionService.getMergedPermission(entity.getId(), ThreadLocalContext.getUserId()));
return dto;
}).collect(Collectors.toList());
PageInfo<FileMetadataInfoResp> page1 = new PageInfo<>(dtoList);
page1.setTotal(total);
return PageUtils.getJsonObjectSdmResponse(dtoList, page1);
}
@Override
@@ -661,6 +672,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
FileMetadataInfoResp fileMetadataInfoResp = new FileMetadataInfoResp();
BeanUtils.copyProperties(fileMetadataInfo, fileMetadataInfoResp);
fileMetadataInfoResp.setPermissionValue(fileUserPermissionService.getMergedPermission(fileMetadataInfo.getId(), ThreadLocalContext.getUserId()));
return SdmResponse.success(fileMetadataInfoResp);
}
@@ -707,6 +720,10 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
List<FileMetadataInfoResp> dtoList = list.stream().map(entity -> {
FileMetadataInfoResp dto = new FileMetadataInfoResp();
BeanUtils.copyProperties(entity, dto);
//计算当前用户对该文件的综合权限位
// 对于列表查询,如果层级很深,频繁递归会有性能问题。
dto.setPermissionValue(fileUserPermissionService.getMergedPermission(entity.getId(), ThreadLocalContext.getUserId()));
return dto;
}).collect(Collectors.toList());
PageInfo<FileMetadataInfoResp> page1 = new PageInfo<>(dtoList);
@@ -784,7 +801,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
newObjectKey = oldObjectKey.substring(0, oldObjectKey.lastIndexOf("/") + 1) + newName;
minioService.renameFile(oldObjectKey, newObjectKey,bucketName);
fileMetadataInfoService.lambdaUpdate().set(FileMetadataInfo::getObjectKey, newObjectKey).set(FileMetadataInfo::getOriginalName, newName).eq(FileMetadataInfo::getId, fileId).update();
fileMetadataInfoService.lambdaUpdate()
.set(FileMetadataInfo::getObjectKey, newObjectKey)
.set(FileMetadataInfo::getOriginalName, newName)
.set(FileMetadataInfo::getUpdateTime, new Date())
.set(FileMetadataInfo::getUpdaterId, ThreadLocalContext.getUserId())
.eq(FileMetadataInfo::getId, fileId).update();
return SdmResponse.success("重命名成功");
} catch (Exception e) {
log.error("重命名文件失败", e);
@@ -944,7 +966,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
try {
minioService.renameFile(oldDirMinioObjectKey, newDirMinioObjectKey,dirMetadataInfo.getBucketName());
fileMetadataInfoService.lambdaUpdate().set(FileMetadataInfo::getObjectKey, newDirMinioObjectKey).set(FileMetadataInfo::getOriginalName, req.getNewName()).eq(FileMetadataInfo::getId, dirMetadataInfo.getId()).update();
fileMetadataInfoService.lambdaUpdate()
.set(FileMetadataInfo::getObjectKey, newDirMinioObjectKey)
.set(FileMetadataInfo::getOriginalName, req.getNewName())
.set(FileMetadataInfo::getUpdateTime, new Date())
.set(FileMetadataInfo::getUpdaterId, ThreadLocalContext.getUserId())
.eq(FileMetadataInfo::getId, dirMetadataInfo.getId()).update();
return SdmResponse.success("重命名目录成功");
} catch (Exception e) {
log.error("重命名目录失败", e);
@@ -1522,13 +1549,14 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
fileMetadataInfoLambdaQueryChainWrapper.eq(FileMetadataInfo::getParentId, parentDirId);
}
List<FileMetadataInfo> list = fileMetadataInfoLambdaQueryChainWrapper.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId()).eq(FileMetadataInfo::getDataType, DataTypeEnum.DIRECTORY.getValue()).orderByDesc(FileMetadataInfo::getCreateTime).list();
setCreatorNames(list);
List<FileMetadataInfoResp> dtoList = list.stream().map(entity -> {
FileMetadataInfoResp dto = new FileMetadataInfoResp();
BeanUtils.copyProperties(entity, dto);
// todo 后面接入用户系统设置
dto.setCreatorName("");
//计算当前用户对该文件的综合权限位
// 对于列表查询,如果层级很深,频繁递归会有性能问题。
dto.setPermissionValue(fileUserPermissionService.getMergedPermission(entity.getId(), ThreadLocalContext.getUserId()));
return dto;
}).collect(Collectors.toList());
@@ -1577,10 +1605,11 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
tempFileMetadataInfo.setRemarks(req.getRemarks());
tempFileMetadataInfo.setSimulationPoolInfoList(req.getSimulationPoolInfoList());
tempFileMetadataInfo.setCreateTime(fileMetadataInfo.getCreateTime());
tempFileMetadataInfo.setUpdateTime(fileMetadataInfo.getCreateTime());
tempFileMetadataInfo.setUpdaterId(ThreadLocalContext.getUserId());
tempFileMetadataInfo.setUpdateTime(LocalDateTime.now());
fileMetadataInfo.setTempMetadata(JSONObject.toJSONString(tempFileMetadataInfo));
fileMetadataInfo.setUpdateTime(LocalDateTime.now());
fileMetadataInfo.setUpdaterId(ThreadLocalContext.getUserId());
//发起审批
FileApproveRequestBuilder updateFileMetaIntoApproveRequestBuilder = FileApproveRequestBuilder.builder()
@@ -1615,6 +1644,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
fileMetadataInfo.setProjectId(req.getProjectId());
fileMetadataInfo.setAnalysisDirectionId(req.getAnalysisDirectionId());
fileMetadataInfo.setRemarks(req.getRemarks());
fileMetadataInfo.setUpdateTime(LocalDateTime.now());
fileMetadataInfo.setUpdaterId(ThreadLocalContext.getUserId());
}
fileMetadataInfoService.updateById(fileMetadataInfo);
@@ -2172,12 +2203,20 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
try {
if (ObjectUtils.isNotEmpty(list)) {
// 提取去重的 creatorId
List<Long> creatorIds = list.stream()
List<Long> creatorIds = new ArrayList<>(list.stream()
.map(FileMetadataInfo::getCreatorId)
.filter(Objects::nonNull)
.distinct()
.toList());
List<Long> updaterIds = list.stream()
.map(FileMetadataInfo::getUpdaterId)
.filter(Objects::nonNull)
.distinct()
.toList();
creatorIds.addAll(updaterIds);
// 远程查询用户信息
SdmResponse<List<CIDUserResp>> userListSdmRsp = sysUserFeignClient.listUserByIds(
UserQueryReq.builder().userIds(creatorIds).build()
@@ -2194,6 +2233,15 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
cidUser.getRealName()
);
fileMetadataInfo.setCreatorName(username);
Long updaterId = fileMetadataInfo.getUpdaterId();
CIDUserResp cidUser1 = cidUserMap.get(updaterId);
String username1 = Objects.isNull(cidUser1) ? "" : org.apache.commons.lang3.StringUtils.firstNonBlank(
cidUser1.getNickname(),
cidUser1.getUsername(),
cidUser1.getRealName()
);
fileMetadataInfo.setUpdaterName(username1);
});
}
}

View File

@@ -19,7 +19,7 @@ public enum FlowElementTypeEnums {
public static FlowElementTypeEnums fromString(String type) {
for (FlowElementTypeEnums flowElementType : FlowElementTypeEnums.values()) {
if (flowElementType.type.equals(type)) {
if (flowElementType.type.equalsIgnoreCase(type)) {
return flowElementType;
}
}

View File

@@ -18,6 +18,13 @@ public enum NodeStateEnum {
* 挂起
*/
SUSPENDED("suspended"),
/**
* [新增] 等待用户确认(对应 Manual 模式卡在 _waitUser 节点)
* 前端看到这个状态应显示"执行/继续"按钮
*/
WAITING_FOR_USER("waiting_for_user"),
/**
* 错误

View File

@@ -286,7 +286,7 @@ public class ProcessService implements Iprocess{
// 4. 计算流程实例整体状态
// 只要 hasDeadLetterJobs 为 false流程状态就会显示为 running (或 suspended)
processInfo = buildProcessInstanceInfo(processInstanceId, context.isRunning(), context.isSuspended(), context.isHasDeadLetterJobs());
processInfo = buildProcessInstanceInfo(processInstanceId, context);
// 5. 计算每个节点的状态 (核心逻辑)
calculateNodeStates(allNodes, context);
@@ -303,6 +303,10 @@ public class ProcessService implements Iprocess{
boolean isRunning;
boolean isSuspended;
boolean hasDeadLetterJobs;
// 记录最早发生错误的时间(死信作业的创建时间)
Date earliestErrorTime;
// Map<ActivityId, ErrorMsg>
Map<String, String> errorMap = new HashMap<>();
// List<ActivityId>
@@ -331,6 +335,7 @@ public class ProcessService implements Iprocess{
ProcessStateContext ctx = new ProcessStateContext();
// 1. 获取运行时流程实例对象(判断是否运行中、是否挂起)
// ACT_RU_EXECUTION (运行时执行实例表)
ProcessInstance runtimeInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
@@ -338,18 +343,22 @@ public class ProcessService implements Iprocess{
ctx.setSuspended(ctx.isRunning() && runtimeInstance.isSuspended());
// 2. 准备历史数据
// 查询 ACT_HI_ACTINST 该流程实例下,所有的历史节点记录
List<HistoricActivityInstance> historicActivities = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.list();
for (HistoricActivityInstance hist : historicActivities) {
if (!ctx.historyMap.containsKey(hist.getActivityId()) ||
hist.getStartTime().after(ctx.historyMap.get(hist.getActivityId()).getStartTime())) {
// 只保留节点最新的开始时间记录,防止重复覆盖(例如:重试作业)
ctx.historyMap.put(hist.getActivityId(), hist);
}
}
// 3. 准备运行时 Active ID 列表
if (ctx.isRunning()) {
// 查 ACT_RU_EXECUTION 运行时活跃节点ID列表
// historicActivities 也一定会有活跃节点ID,当流程的流转入 serviceTask_id1 的那一瞬间,引擎会立即在 ACT_HI_ACTINST 表插入一条记录
ctx.activeActivityIds.addAll(runtimeService.getActiveActivityIds(processInstanceId));
// A. 查死信 (Error 来源)
@@ -362,6 +371,9 @@ public class ProcessService implements Iprocess{
// C. 只有当有 作业(死信或普通) 时,才去查 Execution 映射
if (!deadJobs.isEmpty() || !activeJobs.isEmpty()) {
// 查 Execution 表,获取 ExecutionId 和 ActivityId 映射
// Flowable 的 Job 表ACT_RU_JOB 或 ACT_RU_DEADLETTER_JOB存储的是“干活的任务单”里面只有 executionId执行指针 ID没有 activityIdBPMN 里的节点 ID如 ServiceTask_1)
// 就算已经进入 ACT_RU_DEADLETTER_JOB 死信队列了 , ACT_RU_EXECUTION 还是会有对应的 ExecutionId 和 ActivityId
List<Execution> executions = runtimeService.createExecutionQuery()
.processInstanceId(processInstanceId).list();
Map<String, String> executionToActivityMap = executions.stream()
@@ -371,6 +383,16 @@ public class ProcessService implements Iprocess{
// 处理死信 -> 放入 errorMap
if (!deadJobs.isEmpty()) {
ctx.setHasDeadLetterJobs(true);
// 找到最早的死信时间,作为流程“卡住”的时间点
Date firstError = deadJobs.stream()
.map(Job::getCreateTime)
.filter(Objects::nonNull)
.min(Date::compareTo)
.orElse(null);
ctx.setEarliestErrorTime(firstError);
for (Job job : deadJobs) {
if (job.getExceptionMessage() != null) {
String actId = executionToActivityMap.get(job.getExecutionId());
@@ -393,7 +415,11 @@ public class ProcessService implements Iprocess{
}
// 构建流程实例信息
private ProcessInstanceInfo buildProcessInstanceInfo(String processInstanceId, boolean isRunning, boolean isSuspended, boolean hasError) {
private ProcessInstanceInfo buildProcessInstanceInfo(String processInstanceId, ProcessStateContext context) {
boolean isRunning = context.isRunning();
boolean isSuspended = context.isSuspended();
boolean hasError = context.isHasDeadLetterJobs();
HistoricProcessInstance historicInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
@@ -410,9 +436,22 @@ public class ProcessService implements Iprocess{
// 计算耗时
Long duration = historicInstance.getDurationInMillis();
// 如果 duration 为空(说明流程没结束),且流程已开始
if (duration == null && historicInstance.getStartTime() != null && isRunning) {
duration = System.currentTimeMillis() - historicInstance.getStartTime().getTime();
if (hasError && context.getEarliestErrorTime() != null) {
// 情况 A: 流程报错了,耗时 = 报错时刻 - 开始时刻
// 这样时间就“定格”了,不会随着你查询时间而增加
duration = context.getEarliestErrorTime().getTime() - historicInstance.getStartTime().getTime();
} else {
// 情况 B: 正常运行或挂起,耗时 = 当前时刻 - 开始时刻
duration = System.currentTimeMillis() - historicInstance.getStartTime().getTime();
}
}
// 防止出现负数(极端并发情况下)
if (duration != null && duration < 0) duration = 0L;
info.setDurationInMillis(duration);
info.setDurationFormatted(duration != null ? formatDuration(duration) : null);
@@ -500,7 +539,19 @@ public class ProcessService implements Iprocess{
if (isActive) {
// 只要是 Active就根据流程整体状态来定颜色
node.setStatus(ctx.isSuspended() ? NodeStateEnum.SUSPENDED.getCode() : NodeStateEnum.ACTIVE.getCode());
// 如果流程整体被挂起,优先显示挂起
if (ctx.isSuspended()) {
node.setStatus(NodeStateEnum.SUSPENDED.getCode());
} else if (ctx.activeActivityIds.contains(waitUserId)) {
// 停留在 _waitUser -> 等待用户操作 (Manual模式)
node.setStatus(NodeStateEnum.WAITING_FOR_USER.getCode());
} else if (FlowElementTypeEnums.USERTASK.getType().equalsIgnoreCase(node.getType()) && ctx.activeActivityIds.contains(origId)) {
// 这是一个普通的 UserTask且当前正卡在这里
node.setStatus(NodeStateEnum.WAITING_FOR_USER.getCode());
}else {
// 停留在 origId 或 waitId 或 checkId-> 视为通用执行中
node.setStatus(NodeStateEnum.ACTIVE.getCode());
}
calculateAggregatedTime(node, ctx, waitUserId, origId, checkId);
return;
}