fix:文件回收站功能:删除到回收站,7天(设置时间)后自动删除;回收站中也可以手动删除

This commit is contained in:
2026-02-05 14:53:39 +08:00
parent 4b152eb21b
commit 996e69dde3
13 changed files with 450 additions and 82 deletions

View File

@@ -128,11 +128,49 @@ public class DataFileController implements IDataFeignClient {
*/
@SysLog("删除文件")
@PostMapping("/delFile")
@Operation(summary = "删除文件", description = "根据请求参数删除指定的文件")
@Operation(summary = "删除文件", description = "根据请求参数删除指定的文件(移入回收站)")
public SdmResponse delFile(@RequestBody @Validated DelFileReq req) {
return IDataFileService.delFile(req);
}
/**
* 查询回收站列表
*
* @param req
* @return
*/
@PostMapping("/listRecycleBin")
@Operation(summary = "查询回收站列表", description = "查询当前租户的回收站文件/目录列表")
public SdmResponse listRecycleBin(@RequestBody @Validated ListRecycleBinReq req) {
return IDataFileService.listRecycleBin(req);
}
/**
* 从回收站还原
*
* @param req
* @return
*/
@SysLog("从回收站还原")
@PostMapping("/restoreFromRecycle")
@Operation(summary = "从回收站还原", description = "将文件/目录从回收站还原到原位置")
public SdmResponse restoreFromRecycle(@RequestBody @Validated RestoreFromRecycleReq req) {
return IDataFileService.restoreFromRecycle(req);
}
/**
* 从回收站彻底删除
*
* @param req
* @return
*/
@SysLog("从回收站彻底删除")
@PostMapping("/permanentDeleteFromRecycle")
@Operation(summary = "从回收站彻底删除", description = "从回收站彻底删除文件/目录(物理删除,不可恢复)")
public SdmResponse permanentDeleteFromRecycle(@RequestBody @Validated PermanentDeleteFromRecycleReq req) {
return IDataFileService.permanentDeleteFromRecycle(req);
}
/**
* 搜索文件
*

View File

@@ -206,6 +206,16 @@ public class FileMetadataInfo implements Serializable {
@TableField(value = "templateId")
private String templateId;
@Schema(description= "删除时间移入回收站的时间NULL表示未删除")
@TableField("deletedAt")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime deletedAt;
@Schema(description= "回收站过期时间超过此时间将自动物理删除NULL表示未删除")
@TableField("recycleExpireAt")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime recycleExpireAt;
@Schema(description= "cidFlowReviewer:cid审核电子流程里面的评审人,只有列表展示使用")
@TableField(value = "cidFlowReviewer", insertStrategy = FieldStrategy.NEVER,select = false,updateStrategy = FieldStrategy.NEVER)
private String cidFlowReviewer;

View File

@@ -0,0 +1,117 @@
package com.sdm.data.schedule;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sdm.data.model.entity.FileMetadataExtension;
import com.sdm.data.model.entity.FileMetadataInfo;
import com.sdm.data.model.entity.FileSimulationMapping;
import com.sdm.data.model.entity.FileStorage;
import com.sdm.data.model.entity.FileTagRel;
import com.sdm.data.model.entity.FileUserPermission;
import com.sdm.data.service.IFileMetadataExtensionService;
import com.sdm.data.service.IFileMetadataInfoService;
import com.sdm.data.service.IFileStorageService;
import com.sdm.data.service.IFileTagRelService;
import com.sdm.data.service.IFileUserPermissionService;
import com.sdm.data.service.IMinioService;
import com.sdm.data.service.IFileSimulationMappingService;
import com.sdm.data.service.impl.MinioFileIDataFileServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.List;
/**
* 回收站自动清理定时任务
* 定期清理回收站中已过期的文件/目录
*/
@Component
@Slf4j
public class RecycleBinCleanupSchedule {
@Autowired
private IFileMetadataInfoService fileMetadataInfoService;
@Autowired
private IMinioService minioService;
@Autowired
private IFileStorageService fileStorageService;
@Autowired
private IFileMetadataExtensionService fileMetadataExtensionService;
@Autowired
private IFileUserPermissionService fileUserPermissionService;
@Autowired
private IFileTagRelService fileTagRelService;
@Autowired
private IFileSimulationMappingService fileSimulationMappingService;
@Autowired
private MinioFileIDataFileServiceImpl minioFileIDataFileService;
/**
* 定时清理回收站中已过期的文件/目录
* 默认每天凌晨2点执行
*/
@Scheduled(cron = "${data.recycle.cleanup-cron:0 0 2 * * ?}")
public void cleanupExpiredRecycleBin() {
log.info("开始执行回收站自动清理任务");
try {
LocalDateTime now = LocalDateTime.now();
// 查询所有已过期的回收站项
List<FileMetadataInfo> expiredItems = fileMetadataInfoService.lambdaQuery()
.isNotNull(FileMetadataInfo::getDeletedAt)
.le(FileMetadataInfo::getRecycleExpireAt, now)
.list();
if (CollectionUtils.isEmpty(expiredItems)) {
log.info("回收站中没有已过期的文件/目录");
return;
}
log.info("发现 {} 个已过期的回收站项,开始物理删除", expiredItems.size());
int successCount = 0;
int failCount = 0;
for (FileMetadataInfo item : expiredItems) {
try {
// 如果是目录,需要递归删除所有子项
if (item.getDataType() != null && item.getDataType() == 1) { // 1表示目录
minioFileIDataFileService.executeDirectoryDeletion(
item.getId(),
item.getObjectKey(),
item.getBucketName()
);
} else {
// 单文件物理删除
minioService.deleteFile(item.getObjectKey(), item.getBucketName());
fileMetadataInfoService.removeById(item.getId());
// 删除关联数据
fileStorageService.remove(new LambdaQueryWrapper<FileStorage>().eq(FileStorage::getFileId, item.getId()));
fileMetadataExtensionService.remove(new LambdaQueryWrapper<FileMetadataExtension>().eq(FileMetadataExtension::getTFilemetaId, item.getId()));
fileUserPermissionService.remove(new LambdaQueryWrapper<FileUserPermission>().eq(FileUserPermission::getTFilemetaId, item.getId()));
fileSimulationMappingService.remove(new LambdaQueryWrapper<FileSimulationMapping>().eq(FileSimulationMapping::getFileId, item.getId()));
fileTagRelService.remove(new LambdaQueryWrapper<FileTagRel>().eq(FileTagRel::getFileId, item.getId()));
}
successCount++;
} catch (Exception e) {
log.error("删除回收站过期项失败: id={}, objectKey={}", item.getId(), item.getObjectKey(), e);
failCount++;
}
}
log.info("回收站自动清理任务执行完成: 成功={}, 失败={}", successCount, failCount);
} catch (Exception e) {
log.error("回收站自动清理任务执行失败", e);
}
}
}

View File

@@ -128,6 +128,33 @@ public interface IDataFileService {
*/
SdmResponse fileSearch(FileSearchReq req);
/**
* 查询回收站列表
* @param req 回收站列表查询请求参数
* @return 回收站列表响应
*/
default SdmResponse listRecycleBin(com.sdm.common.entity.req.data.ListRecycleBinReq req) {
return null;
}
/**
* 从回收站还原文件/目录
* @param req 还原请求参数
* @return 还原结果响应
*/
default SdmResponse restoreFromRecycle(com.sdm.common.entity.req.data.RestoreFromRecycleReq req) {
return null;
}
/**
* 从回收站彻底删除文件/目录
* @param req 彻底删除请求参数
* @return 删除结果响应
*/
default SdmResponse permanentDeleteFromRecycle(com.sdm.common.entity.req.data.PermanentDeleteFromRecycleReq req) {
return null;
}
/**
* 重命名文件
* @param req 重命名文件请求参数

View File

@@ -62,6 +62,7 @@ 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;
@@ -102,6 +103,9 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
private static final String FLOWABLE_SIMULATION_BASEDIR = "/home/simulation/";
@Value("${data.recycle.retention-days:7}")
private Integer recycleRetentionDays;
@Autowired
private IFileMetadataInfoService fileMetadataInfoService;
@@ -529,8 +533,6 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
Long rootDirId = deleteDirMetadataInfo.getId();
String rootDirObjectKey = deleteDirMetadataInfo.getObjectKey();
String bucketName = deleteDirMetadataInfo.getBucketName();
Integer dirType = deleteDirMetadataInfo.getDirType();
// 1. 权限校验(仅校验根目录删除权限)
@@ -546,18 +548,15 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
.eq(FileMetadataInfo::getParentId, rootDirId)
.exists();
// 3. 知识库且非空目录判定:需要走审批流程
// 3. 知识库且非空目录:走审批流程,审批通过后由 DeleteApproveStrategy 移入回收站
boolean isKnowledgeDir = Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirType);
if (isKnowledgeDir && hasChildren) {
// 递归收集所有待删除的子项
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
collectRecursiveIds(rootDirId, allFileIds, allDirIds);
// 发起审批(仅传父目录元数据,但 fileIds 包含所有子项)
return launchKnowledgeBaseDeletionApproval(
List.of(deleteDirMetadataInfo), // 仅父目录
Sets.union(allFileIds,allDirIds), // allFileIds+allDirIds 所有受影响的 ID用于批量状态更新
List.of(deleteDirMetadataInfo),
Sets.union(allFileIds, allDirIds),
deleteDirMetadataInfo,
"知识库目录删除",
req.getTemplateId(),
@@ -565,8 +564,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
);
}
// 4. 非知识库目录 或 空知识库目录:直接删除
return executeDirectoryDeletion(rootDirId, rootDirObjectKey, bucketName);
// 4. 非知识库目录 或 空知识库目录:直接移入回收站
return moveDirectoryToRecycle(rootDirId);
} catch (Exception e) {
log.error("删除目录失败", e);
@@ -624,9 +623,34 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
/**
* 执行目录的物理删除(数据库 + MinIO
* 将目录及其子项移入回收站(设置 deletedAt、recycleExpireAt
*/
private SdmResponse executeDirectoryDeletion(Long rootDirId, String rootDirObjectKey, String bucketName) {
private SdmResponse moveDirectoryToRecycle(Long rootDirId) {
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
collectRecursiveIds(rootDirId, allFileIds, allDirIds);
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
if (CollectionUtils.isNotEmpty(allFileIds)) {
List<FileMetadataInfo> allMetadataList = fileMetadataInfoService.listByIds(allFileIds);
allMetadataList.forEach(item -> {
item.setDeletedAt(now);
item.setRecycleExpireAt(expireAt);
item.setUpdateTime(now);
});
fileMetadataInfoService.updateBatchById(allMetadataList);
}
log.info("目录及子项已移入回收站: id={}, 过期时间={}", rootDirId, expireAt);
return SdmResponse.success("已移入回收站,将在" + recycleRetentionDays + "天后自动删除");
}
/**
* 执行目录的物理删除(数据库 + MinIO- 仅用于回收站过期或手动彻底删除
*/
public SdmResponse executeDirectoryDeletion(Long rootDirId, String rootDirObjectKey, String bucketName) {
// 递归收集所有待删除的 ID
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
@@ -642,6 +666,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
fileUserPermissionService.lambdaUpdate().in(FileUserPermission::getTFilemetaId, allFileIds).remove();
// 删除仿真映射
fileSimulationMappingService.lambdaUpdate().in(FileSimulationMapping::getFileId, allFileIds).remove();
// 删除文件标签关系
fileTagRelService.lambdaUpdate().in(FileTagRel::getFileId, allFileIds).or().in(FileTagRel::getDirId, allFileIds).remove();
// 删除存储统计(包含 fileId 关联和 dirId 关联)
fileStorageService.lambdaUpdate()
.in(FileStorage::getFileId, allFileIds)
@@ -673,18 +699,14 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
return SdmResponse.failed("文件不存在");
}
// 文件夹
FileMetadataInfo dirMetadataInfo = fileMetadataInfoService.lambdaQuery().eq(FileMetadataInfo::getId, deleteFileMetadataInfo.getParentId()).eq(FileMetadataInfo::getDataType, DataTypeEnum.DIRECTORY.getValue()).one();
// 所属文件夹(用于判断是否知识库)
FileMetadataInfo dirMetadataInfo = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getId, deleteFileMetadataInfo.getParentId())
.eq(FileMetadataInfo::getDataType, DataTypeEnum.DIRECTORY.getValue())
.one();
/*boolean hasDeletePermission = fileUserPermissionService.hasFilePermission(deleteFileMetadataInfo.getId(), ThreadLocalContext.getUserId(), FilePermissionEnum.DELETE);
if (!hasDeletePermission) {
return SdmResponse.failed("没有删除权限");
}*/
// 知识库
if(dirMetadataInfo!=null&&Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType())){
// 发起审批
// 知识库文件:走审批流程,审批通过后由 DeleteApproveStrategy 移入回收站
if (dirMetadataInfo != null && Objects.equals(DirTypeEnum.KNOWLEDGE_BASE_DIR.getValue(), dirMetadataInfo.getDirType())) {
return launchKnowledgeBaseDeletionApproval(
List.of(deleteFileMetadataInfo),
Set.of(delFileId),
@@ -693,17 +715,16 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
req.getTemplateId(),
req.getTemplateName()
);
}else{
// 删除MinIO文件
minioService.deleteFile(deleteFileMetadataInfo.getObjectKey(), deleteFileMetadataInfo.getBucketName());
// 删除数据库记录
fileMetadataInfoService.removeById(deleteFileMetadataInfo.getId());
fileStorageService.remove(new LambdaQueryWrapper<FileStorage>().eq(FileStorage::getFileId, deleteFileMetadataInfo.getId()));
fileMetadataExtensionService.remove(new LambdaQueryWrapper<FileMetadataExtension>().eq(FileMetadataExtension::getTFilemetaId, deleteFileMetadataInfo.getId()));
fileUserPermissionService.remove(new LambdaQueryWrapper<FileUserPermission>().eq(FileUserPermission::getTFilemetaId, deleteFileMetadataInfo.getId()));
fileTagRelService.remove(new LambdaQueryWrapper<FileTagRel>().eq(FileTagRel::getFileId, deleteFileMetadataInfo.getId()));
return SdmResponse.success("操作成功");
}
// 非知识库文件:直接移入回收站
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
deleteFileMetadataInfo.setDeletedAt(now);
deleteFileMetadataInfo.setRecycleExpireAt(expireAt);
deleteFileMetadataInfo.setUpdateTime(now);
fileMetadataInfoService.updateById(deleteFileMetadataInfo);
return SdmResponse.success("已移入回收站,将在" + recycleRetentionDays + "天后自动删除");
} catch (Exception e) {
log.error("删除文件失败", e);
throw new RuntimeException("删除文件失败: " + e.getMessage(), e);
@@ -873,6 +894,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
.eq(ObjectUtils.isNotEmpty(req.getQueryTarget()), FileMetadataInfo::getDataType, req.getQueryTarget())
.like(ObjectUtils.isNotEmpty(req.getFileName()), FileMetadataInfo::getOriginalName, req.getFileName())
.eq(FileMetadataInfo::getIsLatest, FileIsLastEnum.YES.getValue())
// 排除已删除(回收站中的文件)
.isNull(FileMetadataInfo::getDeletedAt)
// 审核完成 ,元数据修改审核中,文件修改审核中,删除文件审核中
.in(FileMetadataInfo::getApproveType,fileDatdList)
// 文件夹在前(dataType=1),文件在后(dataType=2),同类型内按名称升序
@@ -4454,4 +4477,113 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse listRecycleBin(com.sdm.common.entity.req.data.ListRecycleBinReq req) {
Long tenantId = ThreadLocalContext.getTenantId();
PageHelper.startPage(req.getCurrent(), req.getSize());
List<FileMetadataInfo> list = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getTenantId, tenantId)
.isNotNull(FileMetadataInfo::getDeletedAt)
.like(ObjectUtils.isNotEmpty(req.getFileName()), FileMetadataInfo::getOriginalName, req.getFileName())
.eq(ObjectUtils.isNotEmpty(req.getDirType()), FileMetadataInfo::getDirType, req.getDirType())
.eq(ObjectUtils.isNotEmpty(req.getDataType()), FileMetadataInfo::getDataType, req.getDataType())
.orderByDesc(FileMetadataInfo::getDeletedAt)
.list();
setCreatorNames(list);
setCidInfos(list);
setProjectName(list);
setAnalysisDirectionName(list);
setSimulationPoolAndTaskInfo(list);
PageInfo<FileMetadataInfo> page = new PageInfo<>(list);
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);
page1.setTotal(page.getTotal());
return PageUtils.getJsonObjectSdmResponse(dtoList, page1);
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse restoreFromRecycle(com.sdm.common.entity.req.data.RestoreFromRecycleReq req) {
FileMetadataInfo metadata = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getId, req.getId())
.isNotNull(FileMetadataInfo::getDeletedAt)
.one();
if (ObjectUtils.isEmpty(metadata)) {
return SdmResponse.failed("文件/目录不存在或不在回收站中");
}
// 如果是目录,需要递归还原所有子项
if (Objects.equals(DataTypeEnum.DIRECTORY.getValue(), metadata.getDataType())) {
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
collectRecursiveIds(metadata.getId(), allFileIds, allDirIds);
// 批量还原
if (CollectionUtils.isNotEmpty(allFileIds)) {
List<FileMetadataInfo> allMetadataList = fileMetadataInfoService.listByIds(allFileIds);
LocalDateTime now = LocalDateTime.now();
allMetadataList.forEach(item -> {
item.setDeletedAt(null);
item.setRecycleExpireAt(null);
item.setUpdateTime(now);
});
fileMetadataInfoService.updateBatchById(allMetadataList);
}
log.info("成功从回收站还原目录及所有子项: id={}", metadata.getId());
return SdmResponse.success("还原成功");
} else {
// 单文件还原
LocalDateTime now = LocalDateTime.now();
metadata.setDeletedAt(null);
metadata.setRecycleExpireAt(null);
metadata.setUpdateTime(now);
fileMetadataInfoService.updateById(metadata);
log.info("成功从回收站还原文件: id={}", metadata.getId());
return SdmResponse.success("还原成功");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse permanentDeleteFromRecycle(com.sdm.common.entity.req.data.PermanentDeleteFromRecycleReq req) {
FileMetadataInfo metadata = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getId, req.getId())
.isNotNull(FileMetadataInfo::getDeletedAt)
.one();
if (ObjectUtils.isEmpty(metadata)) {
return SdmResponse.failed("文件/目录不存在或不在回收站中");
}
// 如果是目录,需要递归物理删除所有子项
if (Objects.equals(DataTypeEnum.DIRECTORY.getValue(), metadata.getDataType())) {
return executeDirectoryDeletion(metadata.getId(), metadata.getObjectKey(), metadata.getBucketName());
} else {
// 单文件物理删除
minioService.deleteFile(metadata.getObjectKey(), metadata.getBucketName());
fileMetadataInfoService.removeById(metadata.getId());
fileStorageService.remove(new LambdaQueryWrapper<FileStorage>().eq(FileStorage::getFileId, metadata.getId()));
fileMetadataExtensionService.remove(new LambdaQueryWrapper<FileMetadataExtension>().eq(FileMetadataExtension::getTFilemetaId, metadata.getId()));
fileUserPermissionService.remove(new LambdaQueryWrapper<FileUserPermission>().eq(FileUserPermission::getTFilemetaId, metadata.getId()));
fileSimulationMappingService.remove(new LambdaQueryWrapper<FileSimulationMapping>().eq(FileSimulationMapping::getFileId, metadata.getId()));
fileTagRelService.remove(new LambdaQueryWrapper<FileTagRel>().eq(FileTagRel::getFileId, metadata.getId()));
log.info("成功从回收站彻底删除文件: id={}", metadata.getId());
return SdmResponse.success("彻底删除成功");
}
}
}

View File

@@ -19,6 +19,8 @@ import java.util.stream.Collectors;
@Service
@Slf4j
public class DeleteApproveStrategy implements ApproveStrategy {
@org.springframework.beans.factory.annotation.Value("${data.recycle.retention-days:7}")
private Integer recycleRetentionDays;
@Override
public boolean handle(ApproveContext context) {
FileMetadataInfo metadata = context.getApproveMetadataInfos().get(0);
@@ -44,77 +46,52 @@ public class DeleteApproveStrategy implements ApproveStrategy {
}
/**
* 处理文件删除审批通过
* 处理文件删除审批通过 - 移入回收站
*/
private boolean handleFileDeletion(ApproveContext context, FileMetadataInfo metadata, int type) {
IFileMetadataInfoService service = context.getFileMetadataInfoService();
IMinioService minioService = context.getMinioService();
IFileMetadataExtensionService fileMetadataExtensionService = context.getFileMetadataExtensionService();
IFileUserPermissionService fileUserPermissionService = context.getFileUserPermissionService();
IFileStorageService fileStorageService = context.getFileStorageService();
ISimulationParameterLibraryCategoryObjectService paramObjectService = context.getParamObjectService();
IFileSimulationMappingService fileSimulationMappingService = context.getFileSimulationMappingService();
IFileTagRelService fileTagRelService = context.getFileTagRelService();
// 删除MinIO文件
minioService.deleteFile(metadata.getObjectKey(), metadata.getBucketName());
// 移入回收站
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
metadata.setDeletedAt(now);
metadata.setRecycleExpireAt(expireAt);
metadata.setUpdateTime(now);
service.updateById(metadata);
// 删除数据库记录
service.removeById(metadata.getId());
fileStorageService.remove(new LambdaQueryWrapper<FileStorage>().eq(FileStorage::getFileId, metadata.getId()));
fileMetadataExtensionService.remove(new LambdaQueryWrapper<FileMetadataExtension>().eq(FileMetadataExtension::getTFilemetaId, metadata.getId()));
fileUserPermissionService.remove(new LambdaQueryWrapper<FileUserPermission>().eq(FileUserPermission::getTFilemetaId, metadata.getId()));
fileSimulationMappingService.remove(new LambdaQueryWrapper<FileSimulationMapping>().eq(FileSimulationMapping::getFileId, metadata.getId()));
fileTagRelService.remove(new LambdaQueryWrapper<FileTagRel>().in(FileTagRel::getFileId, metadata.getId()));
// 如果是参数库审批 删除参数库对象
if (ApproveTypeEnum.PARAM_APPROVE.getCode() == type) {
paramObjectService.remove(new LambdaQueryWrapper<SimulationParameterLibraryCategoryObject>().eq(SimulationParameterLibraryCategoryObject::getFileId, metadata.getId()));
}
log.info("审批通过,成功删除文件: id={}, objectKey={}", metadata.getId(), metadata.getObjectKey());
log.info("审批通过,文件已移入回收站: id={}, objectKey={}, 过期时间={}", metadata.getId(), metadata.getObjectKey(), expireAt);
return true;
}
/**
* 处理目录删除审批通过
* 处理目录删除审批通过 - 移入回收站
*/
private boolean handleDirDeletion(ApproveContext context, FileMetadataInfo rootDirMetadata) {
IFileMetadataInfoService service = context.getFileMetadataInfoService();
IMinioService minioService = context.getMinioService();
IFileMetadataExtensionService fileMetadataExtensionService = context.getFileMetadataExtensionService();
IFileUserPermissionService fileUserPermissionService = context.getFileUserPermissionService();
IFileStorageService fileStorageService = context.getFileStorageService();
IFileSimulationMappingService fileSimulationMappingService = context.getFileSimulationMappingService();
IFileTagRelService fileTagRelService = context.getFileTagRelService();
Long rootDirId = rootDirMetadata.getId();
String rootDirObjectKey = rootDirMetadata.getObjectKey();
String bucketName = rootDirMetadata.getBucketName();
// 递归收集所有待删除的 ID
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
collectRecursiveIds(service, rootDirId, allFileIds, allDirIds);
// 批量删除数据库元数据
// 设置回收站时间
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
// 批量更新为回收站状态
if (CollectionUtils.isNotEmpty(allFileIds)) {
service.removeByIds(allFileIds);
fileMetadataExtensionService.lambdaUpdate().in(FileMetadataExtension::getTFilemetaId, allFileIds).remove();
fileUserPermissionService.lambdaUpdate().in(FileUserPermission::getTFilemetaId, allFileIds).remove();
fileSimulationMappingService.lambdaUpdate().in(FileSimulationMapping::getFileId, allFileIds).remove();
fileStorageService.lambdaUpdate()
.in(FileStorage::getFileId, allFileIds)
.or()
.in(FileStorage::getDirId, allDirIds)
.remove();
fileTagRelService.lambdaUpdate().in(FileTagRel::getDirId, allFileIds).remove();
List<FileMetadataInfo> allMetadataList = service.listByIds(allFileIds);
allMetadataList.forEach(item -> {
item.setDeletedAt(now);
item.setRecycleExpireAt(expireAt);
item.setUpdateTime(now);
});
service.updateBatchById(allMetadataList);
}
// MinIO 递归删除
minioService.deleteDirectoryRecursively(rootDirObjectKey, bucketName);
log.info("审批通过,成功删除目录及所有子项: id={}, objectKey={}", rootDirId, rootDirObjectKey);
log.info("审批通过,目录及所有子项已移入回收站: id={}, 过期时间={}", rootDirId, expireAt);
return true;
}

View File

@@ -139,6 +139,12 @@ fileSystem:
system: system
chose: minio # 这里选择minio或者system
# 回收站配置
data:
recycle:
retention-days: 7 # 回收站保留天数默认7天
cleanup-cron: "0 0 2 * * ?" # 定时清理任务每天凌晨2点执行
#地址: https://play.min.io
#凭据:
#账号: minioadmin / 密码: minioadmin

View File

@@ -15,6 +15,7 @@
#{dirId}
</foreach>
)
AND file_metadata_info.deletedAt IS NULL
<if test="filterEmptyData != null and filterEmptyData">
AND file_storage.fileId IS NOT NULL
@@ -35,6 +36,7 @@
and tenantId = #{tenantId}
AND dataType = 2
AND isLatest = true
AND deletedAt IS NULL
<!-- 动态判断uuids 不为空且有元素时,才拼接 UNION ALL + 第二个子查询:普通文件夹和节点文件夹 -->
<if test="fileIds != null and fileIds.size() > 0">
@@ -54,6 +56,7 @@
#{fileId}
</foreach>
)
AND file_metadata_info.deletedAt IS NULL
<if test="filterEmptyData != null and filterEmptyData">
AND file_storage.fileId IS NOT NULL

View File

@@ -166,6 +166,7 @@
and file_metadata_info.isLatest = #{queryBigFileReq.isLatest}
and file_metadata_info.tenantId = #{tenantId}
and file_storage.tenantId = #{tenantId}
and file_metadata_info.deletedAt IS NULL
<if test="queryBigFileReq.approveTypeList != null and queryBigFileReq.approveTypeList.size()>0">
AND file_metadata_info.approveType IN
<foreach collection="queryBigFileReq.approveTypeList" item="approveType" open="(" separator="," close=")">