fix:文件回收站功能:删除到回收站,7天(设置时间)后自动删除;回收站中也可以手动删除
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索文件
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 重命名文件请求参数
|
||||
|
||||
@@ -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("彻底删除成功");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=")">
|
||||
|
||||
Reference in New Issue
Block a user