This commit is contained in:
2026-02-11 11:49:31 +08:00
4 changed files with 289 additions and 114 deletions

View File

@@ -8,6 +8,7 @@ import com.sdm.common.entity.resp.PageDataResp;
import com.sdm.common.entity.resp.data.BatchAddFileInfoResp;
import com.sdm.common.entity.resp.data.ChunkUploadMinioFileResp;
import com.sdm.common.entity.resp.data.FileMetadataInfoResp;
import com.sdm.data.model.entity.FileMetadataInfo;
import com.sdm.data.model.req.*;
import com.sdm.data.model.resp.KKFileViewURLFromMinioResp;
import com.sdm.data.model.resp.MinioDownloadUrlResp;
@@ -28,6 +29,12 @@ import com.sdm.common.entity.resp.data.BatchCreateNormalDirResp;
@Service
public interface IDataFileService {
/**
* 将文件或目录移入回收站(支持自动重命名释放路径)
* 内部方法,不校验权限
*/
default void moveFileToRecycleBin(FileMetadataInfo fileMetadataInfo) {}
/**
* 创建目录
* @param req 创建目录请求参数

View File

@@ -51,9 +51,13 @@ public class FileSimulationMappingServiceImpl extends ServiceImpl<FileSimulation
fileSimulationMapping.setSimulationPoolTaskId(fileSimulationMappingReq.getSimulationPoolTaskId());
fileSimulationMappings.add(fileSimulationMapping);
});
// 先删除原先所有的文件绑定关系
this.lambdaUpdate()
.in(FileSimulationMapping::getFileId, fileIds)
.in(FileSimulationMapping::getSimulationPoolId, fileSimulationMappingReq.getSimulationPoolId())
.in(FileSimulationMapping::getSimulationPoolVersion, fileSimulationMappingReq.getSimulationPoolVersion())
.remove();
}
// 先删除原先所有的文件绑定关系
this.lambdaUpdate().in(FileSimulationMapping::getFileId, fileIds).remove();
this.saveBatch(fileSimulationMappings);
return SdmResponse.success();
}

View File

@@ -68,6 +68,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
@@ -185,6 +186,58 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
@Override
public void moveFileToRecycleBin(FileMetadataInfo fileMetadataInfo) {
if (fileMetadataInfo == null) return;
// 如果是文件夹,调用现有的目录移动逻辑
if (Objects.equals(DataTypeEnum.DIRECTORY.getValue(), fileMetadataInfo.getDataType())) {
moveDirectoryToRecycle(fileMetadataInfo.getId());
// 尝试刷新对象状态
FileMetadataInfo updated = fileMetadataInfoService.getById(fileMetadataInfo.getId());
if (updated != null) {
fileMetadataInfo.setObjectKey(updated.getObjectKey());
fileMetadataInfo.setDeletedAt(updated.getDeletedAt());
fileMetadataInfo.setRecycleExpireAt(updated.getRecycleExpireAt());
fileMetadataInfo.setUpdateTime(updated.getUpdateTime());
}
} else {
// 单文件逻辑
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
String oldKey = fileMetadataInfo.getObjectKey();
String suffix = "_del_" + System.currentTimeMillis();
String newKey;
int dotIndex = oldKey.lastIndexOf('.');
if (dotIndex > -1) {
newKey = oldKey.substring(0, dotIndex) + suffix + oldKey.substring(dotIndex);
} else {
newKey = oldKey + suffix;
}
String bucketName = fileMetadataInfo.getBucketName();
try {
// 复用 updatePathRecursively 处理单文件移动和 DB 更新
// updatePathRecursively 会处理 FileMetadataInfo 和 FileStorage
updatePathRecursively(oldKey, newKey, bucketName, now, expireAt, true);
// 更新传入的对象
fileMetadataInfo.setObjectKey(newKey);
fileMetadataInfo.setDeletedAt(now);
fileMetadataInfo.setRecycleExpireAt(expireAt);
fileMetadataInfo.setUpdateTime(now);
log.info("文件已移入回收站: id={}, oldKey={}, newKey={}", fileMetadataInfo.getId(), oldKey, newKey);
} catch (Exception e) {
log.error("移入回收站失败", e);
throw new RuntimeException("移入回收站失败: " + e.getMessage(), e);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse createDir(CreateDirReq req) {
@@ -623,28 +676,35 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
/**
* 将目录及其子项移入回收站(设置 deletedAt、recycleExpireAt
* 将目录及其子项移入回收站(设置 deletedAt、recycleExpireAt,并修改 MinIO objectKey
*/
private SdmResponse moveDirectoryToRecycle(Long rootDirId) {
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
collectRecursiveIds(rootDirId, allFileIds, allDirIds);
FileMetadataInfo rootDir = fileMetadataInfoService.getById(rootDirId);
if (rootDir == null) return SdmResponse.failed("目录不存在");
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);
String oldKey = rootDir.getObjectKey();
String suffix = "_del_" + System.currentTimeMillis();
// 目录 key 规范处理
String newKey;
if (oldKey.endsWith("/")) {
newKey = oldKey.substring(0, oldKey.length() - 1) + suffix + "/";
} else {
newKey = oldKey + suffix + "/";
}
String bucketName = rootDir.getBucketName();
log.info("目录及子项已移入回收站: id={}, 过期时间={}", rootDirId, expireAt);
return SdmResponse.success("已移入回收站,将在" + recycleRetentionDays + "天后自动删除");
try {
// 递归移动并更新状态
updatePathRecursively(oldKey, newKey, bucketName, now, expireAt, true);
log.info("目录及子项已移入回收站: id={}, objectKey={}, newKey={}", rootDirId, oldKey, newKey);
return SdmResponse.success("已移入回收站,将在" + recycleRetentionDays + "天后自动删除");
} catch (Exception e) {
log.error("移入回收站失败", e);
throw new RuntimeException("移入回收站失败", e);
}
}
/**
@@ -717,11 +777,22 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
);
}
// 非知识库文件:直接移入回收站
// 非知识库文件:直接移入回收站 (Rename + Soft Delete)
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
String oldKey = deleteFileMetadataInfo.getObjectKey();
String suffix = "_del_" + System.currentTimeMillis();
String newKey = oldKey + suffix;
String bucketName = deleteFileMetadataInfo.getBucketName();
// 1. MinIO 重命名
minioService.renameFile(oldKey, newKey, bucketName);
// 2. DB 更新
deleteFileMetadataInfo.setDeletedAt(now);
deleteFileMetadataInfo.setRecycleExpireAt(expireAt);
deleteFileMetadataInfo.setObjectKey(newKey);
deleteFileMetadataInfo.setUpdateTime(now);
fileMetadataInfoService.updateById(deleteFileMetadataInfo);
return SdmResponse.success("已移入回收站,将在" + recycleRetentionDays + "天后自动删除");
@@ -1381,16 +1452,17 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
String newName = req.getNewName();
String oldName = dirMetadataInfo.getOriginalName();
// 3. 名称变化校验:若名称未改变,直接返回,避免 MinIO 原地拷贝报错
// 3. 名称变化校验:若名称未改变,直接返回
if (Objects.equals(newName, oldName)) {
return SdmResponse.success("名称未改变");
}
// 4. 同名冲突校验:检查同级目录下是否存在同名文件夹
// 4. 同名冲突校验:检查同级目录下是否存在同名文件夹 (排除已删除的)
boolean exists = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getParentId, dirMetadataInfo.getParentId())
.eq(FileMetadataInfo::getOriginalName, newName)
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.isNull(FileMetadataInfo::getDeletedAt)
.exists();
if (exists) {
return SdmResponse.failed("当前目录下已存在同名文件夹");
@@ -1403,13 +1475,10 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
String newDirMinioObjectKey = getDirMinioObjectKey(parentPath + newName);
try {
// 5. MinIO 递归重命名:将目录下所有对象从旧前缀迁移到新前缀
minioService.renameDirectoryRecursively(oldDirMinioObjectKey, newDirMinioObjectKey, bucketName);
// 5. 统一更新路径MinIO + DB递归
updatePathRecursively(oldDirMinioObjectKey, newDirMinioObjectKey, bucketName, null, null, false);
// 6. 数据库递归更新:更新所有子文件/子目录的 objectKey
updateChildrenObjectKeys(oldDirMinioObjectKey, newDirMinioObjectKey, bucketName);
// 7. 更新当前目录记录
// 6. 更新当前目录记录
fileMetadataInfoService.lambdaUpdate()
.set(FileMetadataInfo::getObjectKey, newDirMinioObjectKey)
.set(FileMetadataInfo::getOriginalName, newName)
@@ -1418,22 +1487,23 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
.eq(FileMetadataInfo::getId, dirMetadataInfo.getId())
.update();
// 8. 更新 file_storage 表中当前目录的 fileName
fileStorageService.lambdaUpdate()
.set(FileStorage::getFileName, newName)
.eq(FileStorage::getDirId, dirMetadataInfo.getId())
.update();
return SdmResponse.success("重命名目录成功");
} catch (Exception e) {
log.error("重命名目录失败, oldKey={}, newKey={}", oldDirMinioObjectKey, newDirMinioObjectKey, e);
// 尝试回滚 MinIO若部分成功部分失败回滚可能不完整需人工介入
// 尝试回滚
try {
minioService.renameDirectoryRecursively(newDirMinioObjectKey, oldDirMinioObjectKey, bucketName);
updatePathRecursively(newDirMinioObjectKey, oldDirMinioObjectKey, bucketName, null, null, false);
// 注意:这里不需要手动回滚 DB因为下面会 setRollbackOnly() 或抛出异常Spring 会自动回滚 DB。
// 这里的 updatePathRecursively 虽然会执行 DB 更新,但最终都会被回滚。
} catch (Exception re) {
log.error("重命名失败后回滚 MinIO 路径失败", re);
log.error("重命名失败后回滚失败", re);
// 回滚失败,抛出异常以确保 DB 回滚
throw new RuntimeException("重命名目录失败: " + e.getMessage(), e);
}
throw new RuntimeException("重命名目录失败: " + e.getMessage(), e);
// 手动标记事务回滚,但不抛出异常,以便返回友好的错误信息
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return SdmResponse.failed("重命名目录失败: " + e.getMessage());
}
}
@@ -1452,29 +1522,50 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
/**
* 递归更新数据库中所有子项的 objectKey
* 递归更新路径和状态 (核心复用方法)
*
* @param oldPrefix 旧的 objectKey 前缀
* @param newPrefix 新的 objectKey 前缀
* @param oldPrefix 旧路径前缀
* @param newPrefix 新路径前缀
* @param bucketName 桶名称
* @param deletedAt 删除时间(传 null 则不更新)
* @param expireAt 过期时间(传 null 则不更新)
* @param updateStatus 是否更新删除状态
*/
private void updateChildrenObjectKeys(String oldPrefix, String newPrefix, String bucketName) {
// 使用 SQL 的 REPLACE 函数批量更新所有以 oldPrefix 开头的 objectKey
fileMetadataInfoService.lambdaQuery()
public void updatePathRecursively(String oldPrefix, String newPrefix, String bucketName, LocalDateTime deletedAt, LocalDateTime expireAt, boolean updateStatus) {
// 1. MinIO 移动 (如果路径不同)
if (!Objects.equals(oldPrefix, newPrefix)) {
minioService.renameDirectoryRecursively(oldPrefix, newPrefix, bucketName);
}
// 2. 数据库批量更新 Metadata
List<FileMetadataInfo> children = fileMetadataInfoService.lambdaQuery()
.likeRight(FileMetadataInfo::getObjectKey, oldPrefix)
.eq(FileMetadataInfo::getBucketName, bucketName)
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.list()
.forEach(child -> {
String oldKey = child.getObjectKey();
String newKey = newPrefix + oldKey.substring(oldPrefix.length());
fileMetadataInfoService.lambdaUpdate()
.set(FileMetadataInfo::getObjectKey, newKey)
.set(FileMetadataInfo::getUpdateTime, LocalDateTime.now())
.set(FileMetadataInfo::getUpdaterId, ThreadLocalContext.getUserId())
.eq(FileMetadataInfo::getId, child.getId())
.update();
});
.list();
if (CollectionUtils.isNotEmpty(children)) {
List<FileMetadataInfo> updates = new ArrayList<>();
for (FileMetadataInfo child : children) {
String currentKey = child.getObjectKey();
if (currentKey.startsWith(oldPrefix)) {
String suffix = currentKey.substring(oldPrefix.length());
String newKey = newPrefix + suffix;
child.setObjectKey(newKey);
if (updateStatus) {
child.setDeletedAt(deletedAt);
child.setRecycleExpireAt(expireAt);
}
child.setUpdateTime(LocalDateTime.now());
updates.add(child);
}
}
if (CollectionUtils.isNotEmpty(updates)) {
fileMetadataInfoService.updateBatchById(updates);
}
}
}
@Override
@@ -4501,7 +4592,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse listRecycleBin(com.sdm.common.entity.req.data.ListRecycleBinReq req) {
public SdmResponse listRecycleBin(ListRecycleBinReq req) {
Long tenantId = ThreadLocalContext.getTenantId();
PageHelper.startPage(req.getCurrent(), req.getSize());
@@ -4511,6 +4602,8 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
.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())
// 仅显示顶层删除项:即其父级未被删除(或者无父级)
.apply("(parent_id IS NULL OR NOT EXISTS (SELECT 1 FROM file_metadata_info p WHERE p.id = parent_id AND p.deleted_at IS NOT NULL))")
.orderByDesc(FileMetadataInfo::getDeletedAt)
.list();
@@ -4535,7 +4628,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse restoreFromRecycle(com.sdm.common.entity.req.data.RestoreFromRecycleReq req) {
public SdmResponse restoreFromRecycle(RestoreFromRecycleReq req) {
FileMetadataInfo metadata = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getId, req.getId())
.isNotNull(FileMetadataInfo::getDeletedAt)
@@ -4544,43 +4637,101 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
if (ObjectUtils.isEmpty(metadata)) {
return SdmResponse.failed("文件/目录不存在或不在回收站中");
}
// 1. 检查父目录状态
String parentPath = "";
if (metadata.getParentId() != null) {
FileMetadataInfo parent = fileMetadataInfoService.getById(metadata.getParentId());
if (parent == null) {
return SdmResponse.failed("父文件夹不存在,无法还原");
}
if (parent.getDeletedAt() != null) {
return SdmResponse.failed("请先恢复父文件夹: " + parent.getOriginalName());
}
parentPath = parent.getObjectKey();
}
// 如果是目录,需要递归还原所有子项
if (Objects.equals(DataTypeEnum.DIRECTORY.getValue(), metadata.getDataType())) {
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
collectRecursiveIds(metadata.getId(), allFileIds, allDirIds);
String oldKey = metadata.getObjectKey();
String originalName = metadata.getOriginalName();
String bucketName = metadata.getBucketName();
// 2. 冲突检测与自动重命名
// 循环检测 parentId 下是否存在同名文件(未删除的)
String restoreName = originalName;
String restoreKey;
int counter = 1;
while (true) {
boolean exists = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getParentId, metadata.getParentId())
.eq(FileMetadataInfo::getOriginalName, restoreName)
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.isNull(FileMetadataInfo::getDeletedAt)
.exists();
// 批量还原
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);
if (!exists) {
break;
}
log.info("成功从回收站还原目录及所有子项: id={}", metadata.getId());
return SdmResponse.success("还原成功");
// 自动重命名: name(1).txt 或 folder(1)
if (Objects.equals(metadata.getDataType(), DataTypeEnum.DIRECTORY.getValue())) {
restoreName = originalName + "(" + counter + ")";
} else {
int dotIndex = originalName.lastIndexOf('.');
if (dotIndex > -1) {
restoreName = originalName.substring(0, dotIndex) + "(" + counter + ")" + originalName.substring(dotIndex);
} else {
restoreName = originalName + "(" + counter + ")";
}
}
counter++;
}
// 3. 构建新的 ObjectKey
if (Objects.equals(metadata.getDataType(), DataTypeEnum.DIRECTORY.getValue())) {
restoreKey = getDirMinioObjectKey(parentPath + restoreName);
} else {
// 单文件还原
LocalDateTime now = LocalDateTime.now();
restoreKey = getFileMinioObjectKey(parentPath + restoreName);
}
try {
// 4. 执行恢复MinIO Rename + DB Recursive Update + Status Update
// 传递 null 给 deletedAt 和 expireAt 表示清除删除状态
updatePathRecursively(oldKey, restoreKey, bucketName, null, null, true);
// 5. 如果名称发生了变化,更新当前记录的 originalName
if (!Objects.equals(restoreName, originalName)) {
metadata.setOriginalName(restoreName);
}
// updatePathRecursively 内部已经批量更新了子项和当前项的 key/status
// 但 metadata 对象是旧的,手动 update 确保一致性(尤其是 originalName
metadata.setObjectKey(restoreKey);
metadata.setDeletedAt(null);
metadata.setRecycleExpireAt(null);
metadata.setUpdateTime(now);
metadata.setUpdateTime(LocalDateTime.now());
fileMetadataInfoService.updateById(metadata);
log.info("成功从回收站还原文件: id={}", metadata.getId());
return SdmResponse.success("还原成功");
// 6. 如果是文件,更新 Storage fullPath
if (Objects.equals(metadata.getDataType(), DataTypeEnum.FILE.getValue())) {
fileStorageService.lambdaUpdate()
.eq(FileStorage::getFileId, metadata.getId())
.set(FileStorage::getFileName, restoreName)
.update();
}
log.info("成功从回收站还原: id={}, oldKey={}, newKey={}, newName={}", metadata.getId(), oldKey, restoreKey, restoreName);
return SdmResponse.success(Objects.equals(restoreName, originalName) ? "还原成功" : "还原成功,原路径已存在,已重命名为: " + restoreName);
} catch (Exception e) {
log.error("还原失败", e);
throw new RuntimeException("还原失败: " + e.getMessage(), e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public SdmResponse permanentDeleteFromRecycle(com.sdm.common.entity.req.data.PermanentDeleteFromRecycleReq req) {
public SdmResponse permanentDeleteFromRecycle(PermanentDeleteFromRecycleReq req) {
FileMetadataInfo metadata = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getId, req.getId())
.isNotNull(FileMetadataInfo::getDeletedAt)

View File

@@ -8,8 +8,11 @@ import com.sdm.common.entity.enums.ApproveTypeEnum;
import com.sdm.common.entity.enums.DataTypeEnum;
import com.sdm.data.model.entity.*;
import com.sdm.data.service.*;
import com.sdm.data.service.IDataFileService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@@ -21,6 +24,11 @@ import java.util.stream.Collectors;
public class DeleteApproveStrategy implements ApproveStrategy {
@org.springframework.beans.factory.annotation.Value("${data.recycle.retention-days:7}")
private Integer recycleRetentionDays;
@Autowired
@Lazy
private IDataFileService dataFileService;
@Override
public boolean handle(ApproveContext context) {
FileMetadataInfo metadata = context.getApproveMetadataInfos().get(0);
@@ -51,20 +59,23 @@ public class DeleteApproveStrategy implements ApproveStrategy {
private boolean handleFileDeletion(ApproveContext context, FileMetadataInfo metadata, int type) {
IFileMetadataInfoService service = context.getFileMetadataInfoService();
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
// 更新审批状态 + 移入回收站
metadata.setTempMetadata(null);
metadata.setApprovalStatus(ApprovalFileDataStatusEnum.APPROVED.getKey());
metadata.setApproveType(ApproveFileDataTypeEnum.COMPLETED.getCode());
metadata.setDeletedAt(now);
metadata.setRecycleExpireAt(expireAt);
metadata.setUpdateTime(now);
service.updateById(metadata);
try {
// 1. 移入回收站 (MinIO Rename + DB Path Update + DB DeleteStatus Update)
dataFileService.moveFileToRecycleBin(metadata);
log.info("审批通过,文件已移入回收站: id={}, objectKey={}, 过期时间={}", metadata.getId(), metadata.getObjectKey(), expireAt);
return true;
// 2. 更新审批状态
metadata.setTempMetadata(null);
metadata.setApprovalStatus(ApprovalFileDataStatusEnum.APPROVED.getKey());
metadata.setApproveType(ApproveFileDataTypeEnum.COMPLETED.getCode());
metadata.setUpdateTime(LocalDateTime.now());
service.updateById(metadata);
log.info("审批通过,文件已移入回收站: id={}, objectKey={}", metadata.getId(), metadata.getObjectKey());
return true;
} catch (Exception e) {
log.error("审批通过处理文件删除失败: id={}", metadata.getId(), e);
return false;
}
}
/**
@@ -72,34 +83,36 @@ public class DeleteApproveStrategy implements ApproveStrategy {
*/
private boolean handleDirDeletion(ApproveContext context, FileMetadataInfo rootDirMetadata) {
IFileMetadataInfoService service = context.getFileMetadataInfoService();
Long rootDirId = rootDirMetadata.getId();
// 递归收集所有待删除的 ID
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
collectRecursiveIds(service, rootDirId, allFileIds, allDirIds);
try {
// 1. 移入回收站 (MinIO Rename + DB Path Update + DB DeleteStatus Update)
dataFileService.moveFileToRecycleBin(rootDirMetadata);
// 设置回收站时间
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
// 2. 递归收集所有 ID 用于更新审批状态
Set<Long> allFileIds = new HashSet<>();
Set<Long> allDirIds = new HashSet<>();
collectRecursiveIds(service, rootDirId, allFileIds, allDirIds);
// 批量更新审批状态 + 回收站状态
if (CollectionUtils.isNotEmpty(allFileIds)) {
List<FileMetadataInfo> allMetadataList = service.listByIds(allFileIds);
allMetadataList.forEach(item -> {
item.setTempMetadata(null);
item.setApprovalStatus(ApprovalFileDataStatusEnum.APPROVED.getKey());
item.setApproveType(ApproveFileDataTypeEnum.COMPLETED.getCode());
item.setDeletedAt(now);
item.setRecycleExpireAt(expireAt);
item.setUpdateTime(now);
});
service.updateBatchById(allMetadataList);
// 3. 批量更新审批状态
if (CollectionUtils.isNotEmpty(allFileIds)) {
List<FileMetadataInfo> allMetadataList = service.listByIds(allFileIds);
LocalDateTime now = LocalDateTime.now();
allMetadataList.forEach(item -> {
item.setTempMetadata(null);
item.setApprovalStatus(ApprovalFileDataStatusEnum.APPROVED.getKey());
item.setApproveType(ApproveFileDataTypeEnum.COMPLETED.getCode());
item.setUpdateTime(now);
});
service.updateBatchById(allMetadataList);
}
log.info("审批通过,目录及所有子项已移入回收站: id={}", rootDirId);
return true;
} catch (Exception e) {
log.error("审批通过处理目录删除失败: id={}", rootDirId, e);
return false;
}
log.info("审批通过,目录及所有子项已移入回收站: id={}, 过期时间={}", rootDirId, expireAt);
return true;
}
/**