fix:优化回收站全量删除

This commit is contained in:
2026-02-12 15:56:53 +08:00
parent a3f3373582
commit 911967e64b

View File

@@ -788,7 +788,13 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
String oldKey = deleteFileMetadataInfo.getObjectKey();
String suffix = "_del_" + System.currentTimeMillis();
String newKey = oldKey + suffix;
String newKey;
int dotIndex = oldKey.lastIndexOf('.');
if (dotIndex > -1) {
newKey = oldKey.substring(0, dotIndex) + suffix + oldKey.substring(dotIndex);
} else {
newKey = oldKey + suffix;
}
String bucketName = deleteFileMetadataInfo.getBucketName();
// 1. MinIO 重命名
@@ -1539,7 +1545,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
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);
// 根据路径特征决定是递归目录还是单文件重命名
if (oldPrefix.endsWith("/")) {
minioService.renameDirectoryRecursively(oldPrefix, newPrefix, bucketName);
} else {
minioService.renameFile(oldPrefix, newPrefix, bucketName);
}
}
// 2. 数据库批量更新 Metadata
@@ -1568,7 +1579,15 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
}
if (CollectionUtils.isNotEmpty(updates)) {
fileMetadataInfoService.updateBatchById(updates);
for (FileMetadataInfo child : updates) {
fileMetadataInfoService.lambdaUpdate()
.eq(FileMetadataInfo::getId, child.getId())
.set(FileMetadataInfo::getObjectKey, child.getObjectKey())
.set(updateStatus, FileMetadataInfo::getDeletedAt, deletedAt)
.set(updateStatus, FileMetadataInfo::getRecycleExpireAt, expireAt)
.set(FileMetadataInfo::getUpdateTime, child.getUpdateTime())
.update();
}
}
}
}
@@ -4664,7 +4683,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
.eq(ObjectUtils.isNotEmpty(req.getDirType()), FileMetadataInfo::getDirType, req.getDirType())
.eq(ObjectUtils.isNotEmpty(req.getDataType()), FileMetadataInfo::getDataType, req.getDataType())
// 仅显示顶层删除项:即其父级未被删除(或者无父级)
.apply("(parentId IS NULL OR NOT EXISTS (SELECT 1 FROM file_metadata_info p WHERE p.id = parentId AND p.deletedAt IS NOT NULL))")
.apply("(parentId IS NULL OR EXISTS (SELECT 1 FROM file_metadata_info p WHERE p.id = file_metadata_info.parentId AND p.deletedAt IS NULL))")
.orderByDesc(FileMetadataInfo::getDeletedAt)
.list();
@@ -4738,45 +4757,25 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
String originalName = metadata.getOriginalName();
String bucketName = metadata.getBucketName();
// 2. 冲突检测与自动重命名
// 循环检测 parentId 下是否存在同名文件(未删除的)
// 2. 计算还原用的 objectKey从回收站 key 移除 "_del_时间戳" 后缀(文件在扩展名前插入,目录在末尾插入)
boolean isDirectory = Objects.equals(metadata.getDataType(), DataTypeEnum.DIRECTORY.getValue());
String restoreName = originalName;
String restoreKey;
String restoreKey = removeRecycleDeleteSuffix(oldKey, isDirectory);
// 3. 同名冲突originalName不带版本号改名为 xxx(1).pngobjectKey 保持版本号不变,改成 xxx(1)_V1.png
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();
while (fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getParentId, metadata.getParentId())
.eq(FileMetadataInfo::getOriginalName, restoreName)
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
.isNull(FileMetadataInfo::getDeletedAt)
.exists()) {
if (!exists) {
break;
}
// 自动重命名: 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 + ")";
}
}
restoreName = buildOriginalNameWithCounter(originalName, isDirectory, counter);
restoreKey = buildObjectKeyWithCounterKeepingVersion(restoreKey, isDirectory, counter);
counter++;
}
// 3. 构建新的 ObjectKey
if (Objects.equals(metadata.getDataType(), DataTypeEnum.DIRECTORY.getValue())) {
restoreKey = getDirMinioObjectKey(parentPath + restoreName);
} else {
restoreKey = getFileMinioObjectKey(parentPath + restoreName);
}
try {
// 4. 执行恢复MinIO Rename + DB Recursive Update + Status Update
// 传递 null 给 deletedAt 和 expireAt 表示清除删除状态
@@ -4816,7 +4815,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
@Transactional(rollbackFor = Exception.class)
public SdmResponse permanentDeleteFromRecycle(PermanentDeleteFromRecycleReq req) {
List<Long> ids = req.getIds();
if (ObjectUtils.isEmpty(ids)) {
if (CollectionUtils.isEmpty(ids)) {
Long userId = ThreadLocalContext.getUserId();
// 查询回收站中的顶层节点(避免对子节点重复触发递归删除)
List<FileMetadataInfo> list = fileMetadataInfoService.lambdaQuery()
@@ -4839,6 +4838,56 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
return SdmResponse.success("彻底删除成功");
}
private static String removeRecycleDeleteSuffix(String objectKey, boolean isDirectory) {
if (!StringUtils.hasText(objectKey)) return objectKey;
// 移除 _del_123456789 后缀
int delIndex = objectKey.lastIndexOf("_del_");
if (delIndex == -1) return objectKey;
if (isDirectory) {
// 目录:.../folder_del_timestamp/ -> .../folder/
return objectKey.substring(0, delIndex) + "/";
} else {
// 文件:.../name_V1_del_timestamp.png -> .../name_V1.png
int dotIndex = objectKey.lastIndexOf('.');
if (dotIndex > delIndex) {
return objectKey.substring(0, delIndex) + objectKey.substring(dotIndex);
}
return objectKey.substring(0, delIndex);
}
}
private static String buildOriginalNameWithCounter(String originalName, boolean isDirectory, int counter) {
if (isDirectory) return originalName + "(" + counter + ")";
int dotIndex = originalName.lastIndexOf('.');
if (dotIndex > -1) {
return originalName.substring(0, dotIndex) + "(" + counter + ")" + originalName.substring(dotIndex);
}
return originalName + "(" + counter + ")";
}
private static String buildObjectKeyWithCounterKeepingVersion(String restoreKey, boolean isDirectory, int counter) {
if (isDirectory) {
String key = restoreKey.endsWith("/") ? restoreKey.substring(0, restoreKey.length() - 1) : restoreKey;
return key + "(" + counter + ")/";
}
// 文件处理:在 _Vn 之前插入 (n)
// restoreKey 此时已移除 _del_格式为 .../name_V1.png
int versionIdx = restoreKey.lastIndexOf("_V");
if (versionIdx > -1) {
return restoreKey.substring(0, versionIdx) + "(" + counter + ")" + restoreKey.substring(versionIdx);
}
// 兜底(如果不带 _V按扩展名插
int dotIndex = restoreKey.lastIndexOf('.');
if (dotIndex > -1) {
return restoreKey.substring(0, dotIndex) + "(" + counter + ")" + restoreKey.substring(dotIndex);
}
return restoreKey + "(" + counter + ")";
}
private SdmResponse permanentDeleteSingleFile(Long id) {
FileMetadataInfo metadata = fileMetadataInfoService.lambdaQuery()
.eq(FileMetadataInfo::getId, id)