fix:优化回收站全量删除
This commit is contained in:
@@ -178,7 +178,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
|
||||
@Autowired
|
||||
private LocalFileService localFileService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private DictTagHelper dictTagHelper;
|
||||
|
||||
@@ -209,31 +209,31 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
// 单文件逻辑
|
||||
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);
|
||||
@@ -689,7 +689,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime expireAt = now.plusDays(recycleRetentionDays);
|
||||
|
||||
|
||||
String oldKey = rootDir.getObjectKey();
|
||||
String suffix = "_del_" + System.currentTimeMillis();
|
||||
// 目录 key 规范处理
|
||||
@@ -785,10 +785,16 @@ 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 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 重命名
|
||||
@@ -1505,7 +1511,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
// 回滚失败,抛出异常以确保 DB 回滚
|
||||
throw new RuntimeException("重命名目录失败: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
||||
// 手动标记事务回滚,但不抛出异常,以便返回友好的错误信息
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return SdmResponse.failed("重命名目录失败: " + e.getMessage());
|
||||
@@ -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
|
||||
@@ -1556,7 +1567,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
if (currentKey.startsWith(oldPrefix)) {
|
||||
String suffix = currentKey.substring(oldPrefix.length());
|
||||
String newKey = newPrefix + suffix;
|
||||
|
||||
|
||||
child.setObjectKey(newKey);
|
||||
if (updateStatus) {
|
||||
child.setDeletedAt(deletedAt);
|
||||
@@ -1566,9 +1577,17 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
updates.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2148,7 +2167,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
/**
|
||||
* 更新文件标签
|
||||
* 删除旧标签,插入新标签
|
||||
*
|
||||
*
|
||||
* @param req 更新文件请求
|
||||
* @param fileMetadataInfo 文件元数据
|
||||
* @param dirMetadataInfo 目录元数据
|
||||
@@ -2158,10 +2177,10 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
Long creatorId = ThreadLocalContext.getUserId();
|
||||
Long fileId = fileMetadataInfo.getId();
|
||||
Long dirId = dirMetadataInfo != null ? dirMetadataInfo.getId() : null;
|
||||
|
||||
|
||||
// 1. 收集当前目录和所有祖先目录
|
||||
List<Long> ancestorDirIds = dirId != null ? collectAncestorDirIds(dirId) : new ArrayList<>();
|
||||
|
||||
|
||||
// 2. 删除文件在所有目录下的旧标签关系
|
||||
if (!ancestorDirIds.isEmpty()) {
|
||||
fileTagRelService.lambdaUpdate()
|
||||
@@ -2170,7 +2189,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
.in(FileTagRel::getDirId, ancestorDirIds)
|
||||
.remove();
|
||||
}
|
||||
|
||||
|
||||
// 3. 从缓存获取字典标签ID(已由AOP切面自动填充)
|
||||
Map<String, Map<String, Integer>> dictIdMap = req.getDictTagIdsCache();
|
||||
if (dictIdMap == null || dictIdMap.isEmpty()) {
|
||||
@@ -2181,19 +2200,19 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 4. 构建新的标签关系
|
||||
List<FileTagRel> newTagRelList = new ArrayList<>();
|
||||
long fileSize = fileMetadataInfo.getFileSize() != null ? fileMetadataInfo.getFileSize() : 0L;
|
||||
|
||||
|
||||
for (Map.Entry<String, Map<String, Integer>> classEntry : dictIdMap.entrySet()) {
|
||||
Map<String, Integer> valueMap = classEntry.getValue();
|
||||
|
||||
|
||||
for (Integer dictId : valueMap.values()) {
|
||||
if (dictId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 为所有目录(当前目录 + 祖先目录)创建标签关系
|
||||
for (Long dirIdItem : ancestorDirIds) {
|
||||
FileTagRel tagRel = new FileTagRel();
|
||||
@@ -2207,7 +2226,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 5. 批量插入新标签关系
|
||||
if (CollectionUtils.isNotEmpty(newTagRelList)) {
|
||||
fileTagRelService.saveBatch(newTagRelList);
|
||||
@@ -2242,7 +2261,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
// 遍历查询结果,构造文件标签关系
|
||||
for (Map.Entry<String, Map<String, Integer>> classEntry : dictIdMap.entrySet()) {
|
||||
Map<String, Integer> valueMap = classEntry.getValue();
|
||||
|
||||
|
||||
// 遍历该dictClass下的所有dictValue
|
||||
for (Integer dictId : valueMap.values()) {
|
||||
if (dictId == null) {
|
||||
@@ -2496,7 +2515,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
tempFileMetadataInfo.setCreateTime(fileMetadataInfo.getCreateTime());
|
||||
tempFileMetadataInfo.setUpdaterId(ThreadLocalContext.getUserId());
|
||||
tempFileMetadataInfo.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
|
||||
// 保存标签缓存到 tempFileMetadataInfo(如果有)
|
||||
if (CollectionUtils.isNotEmpty(req.getDictTags())) {
|
||||
Map<String, Map<String, Integer>> dictIdMap = req.getDictTagIdsCache();
|
||||
@@ -2565,7 +2584,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 更新标签(如果有)
|
||||
if (CollectionUtils.isNotEmpty(req.getDictTags())) {
|
||||
updateFileTags(req, fileMetadataInfo, dirMetadataInfo);
|
||||
@@ -3371,7 +3390,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
TaskBaseInfo::getFileId,
|
||||
Collectors.groupingBy(TaskBaseInfo::getPoolId)
|
||||
));
|
||||
|
||||
|
||||
// 按 fileId 和 poolId 分组收集 simulationPoolTaskId
|
||||
Map<Long, Map<Integer, List<String>>> filePoolTaskIdsMap = fileSimulationMappingByFileId.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
@@ -3381,20 +3400,20 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
Collectors.mapping(FileSimulationMappingResp::getSimulationPoolTaskId, Collectors.toList())
|
||||
)
|
||||
));
|
||||
|
||||
|
||||
// 为每个文件创建 PoolInfo 列表
|
||||
for (FileMetadataInfo fileMetadataInfo : list) {
|
||||
Long fileId = fileMetadataInfo.getId();
|
||||
if (fileTaskPoolMap.containsKey(fileId)) {
|
||||
Map<Integer, List<TaskBaseInfo>> poolMap = fileTaskPoolMap.get(fileId);
|
||||
Map<Integer, List<String>> poolTaskIdsMap = filePoolTaskIdsMap.getOrDefault(fileId, new HashMap<>());
|
||||
|
||||
|
||||
List<PoolInfo> poolInfos = new ArrayList<>();
|
||||
for (Map.Entry<Integer, List<TaskBaseInfo>> poolEntry : poolMap.entrySet()) {
|
||||
Integer poolId = poolEntry.getKey();
|
||||
List<TaskBaseInfo> taskList = poolEntry.getValue();
|
||||
List<String> taskIds = poolTaskIdsMap.getOrDefault(poolId, new ArrayList<>());
|
||||
|
||||
|
||||
PoolInfo poolInfo = new PoolInfo();
|
||||
poolInfo.setSimulationPoolId(poolId);
|
||||
poolInfo.setSimulationPoolName("");
|
||||
@@ -3405,10 +3424,10 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
poolInfo.setSimulationPoolName(taskList.get(0).getPoolName());
|
||||
poolInfo.setSimulationPoolVersion(taskList.get(0).getVersion());
|
||||
}
|
||||
|
||||
|
||||
poolInfos.add(poolInfo);
|
||||
}
|
||||
|
||||
|
||||
fileMetadataInfo.setPoolInfos(poolInfos);
|
||||
}
|
||||
}
|
||||
@@ -4084,7 +4103,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SdmResponse<List<Long>> batchCreateDir(BatchCreateDirReq req) {
|
||||
log.info("开始执行批量创建目录,dirType: {}, items数量: {}", req.getDirType(),
|
||||
log.info("开始执行批量创建目录,dirType: {}, items数量: {}", req.getDirType(),
|
||||
req.getItems() == null ? 0 : req.getItems().size());
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
@@ -4139,7 +4158,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
return SdmResponse.success(ctx.createdDirIds);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量创建目录异常,已创建MinIO Key数量: {}, 错误信息: {}",
|
||||
log.error("批量创建目录异常,已创建MinIO Key数量: {}, 错误信息: {}",
|
||||
ctx.createdMinioKeys.size(), e.getMessage(), e);
|
||||
compensateDeleteMinioKeys(ctx.createdMinioKeys);
|
||||
throw new RuntimeException("批量创建目录失败: " + e.getMessage(), e);
|
||||
@@ -4206,7 +4225,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
.filter(element -> element.getParentUuId() == null)
|
||||
.collect(Collectors.toList());
|
||||
ctx.setFirstLevelNodes(firstLevelNodes);
|
||||
|
||||
|
||||
// 提取第一层节点UUID集合
|
||||
ctx.setFirstLevelParentUuIds(firstLevelNodes.stream()
|
||||
.map(DirNodeInfo::getUuId)
|
||||
@@ -4315,7 +4334,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
// 5. 批量创建权限记录
|
||||
createBatchPermissions(entities, ctx.tenantId);
|
||||
|
||||
log.info("第{}层处理完成,创建了{}个目录,总耗时: {}ms",
|
||||
log.info("第{}层处理完成,创建了{}个目录,总耗时: {}ms",
|
||||
levelIndex + 1, entities.size(), System.currentTimeMillis() - levelStartTime);
|
||||
}
|
||||
|
||||
@@ -4389,12 +4408,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
List<DirNodeInfo> firstLevelNodes) {
|
||||
|
||||
List<List<DirNodeInfo>> levelNodes = new ArrayList<>();
|
||||
|
||||
|
||||
// 先添加第一层节点
|
||||
if (CollectionUtils.isNotEmpty(firstLevelNodes)) {
|
||||
levelNodes.add(new ArrayList<>(firstLevelNodes));
|
||||
}
|
||||
|
||||
|
||||
// 提取第一层节点的UUID作为初始父节点集合
|
||||
Set<String> currentLevelParents = firstLevelNodes.stream()
|
||||
.map(DirNodeInfo::getUuId)
|
||||
@@ -4436,14 +4455,14 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
log.info("开始执行批量创建普通文件夹,父目录ID: {}, 父目录UUID: {}, folderItems数量: {}, 跳过权限校验: {}",
|
||||
req.getParentId(), req.getParentUUId(), req.getFolderItems() == null ? 0 : req.getFolderItems().size(), req.getSkipPermissionCheck());
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
// 1. 参数校验
|
||||
SdmResponse<Void> validateResult = validateBatchCreateNormalDirRequest(req);
|
||||
if (!validateResult.isSuccess()) {
|
||||
log.error("批量创建普通文件夹参数校验失败: {}", validateResult.getMessage());
|
||||
return SdmResponse.failed(validateResult.getMessage());
|
||||
}
|
||||
|
||||
|
||||
// 2. 父目录校验与权限检查
|
||||
SdmResponse<FileMetadataInfo> parentDirResult = validateParentDirAndPermission(
|
||||
req.getParentId(), req.getParentUUId(), req.getSkipPermissionCheck());
|
||||
@@ -4452,16 +4471,16 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
return SdmResponse.failed(parentDirResult.getMessage());
|
||||
}
|
||||
FileMetadataInfo parentDir = parentDirResult.getData();
|
||||
|
||||
|
||||
BatchCreateNormalDirResp resp = new BatchCreateNormalDirResp();
|
||||
|
||||
|
||||
// 3. 过滤并验证文件夹项
|
||||
List<FolderItemReq> validFolderItems = filterAndValidateFolderItems(req.getFolderItems());
|
||||
if (validFolderItems.isEmpty()) {
|
||||
log.error("有效文件夹项为空");
|
||||
return SdmResponse.failed("有效文件夹项为空");
|
||||
}
|
||||
|
||||
|
||||
// 4. 检查已存在的文件夹,分离出需要创建的项
|
||||
List<FolderItemReq> toCreateItems = checkExistingFoldersAndFilter(
|
||||
validFolderItems, parentDir, resp);
|
||||
@@ -4469,40 +4488,40 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
log.info("所有文件夹均已存在,无需创建");
|
||||
return SdmResponse.success(resp);
|
||||
}
|
||||
|
||||
|
||||
// 5. 准备创建数据(元数据对象 + MinIO Key)
|
||||
List<FileMetadataInfo> newDirs = new ArrayList<>();
|
||||
List<String> minioKeys = new ArrayList<>();
|
||||
prepareBatchCreateData(toCreateItems, parentDir, newDirs, minioKeys);
|
||||
|
||||
|
||||
try {
|
||||
// 6. 批量MinIO创建
|
||||
minioService.batchCreateDirectories(minioKeys, minioService.getCurrentTenantBucketName());
|
||||
log.info("批量MinIO创建完成,数量: {}", minioKeys.size());
|
||||
|
||||
|
||||
// 7. 数据库批量插入
|
||||
fileMetadataInfoService.saveBatch(newDirs, 500);
|
||||
log.info("数据库批量插入完成,数量: {}", newDirs.size());
|
||||
|
||||
|
||||
// 8. 批量添加权限
|
||||
createBatchPermissions(newDirs, ThreadLocalContext.getTenantId());
|
||||
|
||||
|
||||
// 9. 填充成功列表
|
||||
fillSuccessResponse(newDirs, resp);
|
||||
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("批量创建普通文件夹成功,耗时: {}ms, 共创建{}个目录", duration, newDirs.size());
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量创建普通文件夹失败", e);
|
||||
// 补偿删除MinIO目录
|
||||
compensateDeleteMinioKeys(minioKeys);
|
||||
throw new RuntimeException("批量创建失败: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
||||
return SdmResponse.success(resp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验批量创建普通目录请求参数
|
||||
*/
|
||||
@@ -4518,13 +4537,13 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
}
|
||||
return SdmResponse.success(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证父目录并检查权限
|
||||
*/
|
||||
private SdmResponse<FileMetadataInfo> validateParentDirAndPermission(Long parentId, String parentUuid, boolean skipPermissionCheck) {
|
||||
FileMetadataInfo parentDir;
|
||||
|
||||
|
||||
// 优先使用 parentId 查找,如果为空则使用 parentUuid
|
||||
if (parentId != null) {
|
||||
parentDir = fileMetadataInfoService.getById(parentId);
|
||||
@@ -4535,11 +4554,11 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
} else {
|
||||
return SdmResponse.failed("父文件夹ID和UUID不能同时为空");
|
||||
}
|
||||
|
||||
|
||||
if (parentDir == null) {
|
||||
return SdmResponse.failed("父文件夹不存在");
|
||||
}
|
||||
|
||||
|
||||
// 如果不跳过权限校验,则检查权限
|
||||
if (!skipPermissionCheck) {
|
||||
// 权限检查(需要写入权限)
|
||||
@@ -4549,10 +4568,10 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
return SdmResponse.failed("没有写入权限");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return SdmResponse.success(parentDir);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 过滤并验证文件夹项:去重、过滤空名
|
||||
*/
|
||||
@@ -4567,7 +4586,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
}
|
||||
return new ArrayList<>(uniqueMap.values());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查已存在的文件夹,过滤出需要创建的项
|
||||
*/
|
||||
@@ -4575,12 +4594,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
List<FolderItemReq> validFolderItems,
|
||||
FileMetadataInfo parentDir,
|
||||
BatchCreateNormalDirResp resp) {
|
||||
|
||||
|
||||
// 提取所有文件夹名称
|
||||
List<String> folderNames = validFolderItems.stream()
|
||||
.map(FolderItemReq::getFolderName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
// 查询数据库中是否已存在同名目录
|
||||
List<FileMetadataInfo> existingFiles = fileMetadataInfoService.lambdaQuery()
|
||||
.eq(FileMetadataInfo::getParentId, parentDir.getId())
|
||||
@@ -4588,12 +4607,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
.eq(FileMetadataInfo::getDataType, DataTypeEnum.DIRECTORY.getValue())
|
||||
.in(FileMetadataInfo::getOriginalName, folderNames)
|
||||
.list();
|
||||
|
||||
|
||||
// 构建已存在的文件夹名称集合
|
||||
Set<String> existingNames = existingFiles.stream()
|
||||
.map(FileMetadataInfo::getOriginalName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
|
||||
// 过滤出需要创建的项
|
||||
List<FolderItemReq> toCreateItems = new ArrayList<>();
|
||||
for (FolderItemReq item : validFolderItems) {
|
||||
@@ -4603,10 +4622,10 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
toCreateItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return toCreateItems;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 准备批量创建数据:构建元数据对象和MinIO Key
|
||||
*/
|
||||
@@ -4615,17 +4634,17 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
FileMetadataInfo parentDir,
|
||||
List<FileMetadataInfo> newDirs,
|
||||
List<String> minioKeys) {
|
||||
|
||||
|
||||
String parentObjectKey = parentDir.getObjectKey();
|
||||
// 确保 parentObjectKey 以 / 结尾
|
||||
if (!parentObjectKey.endsWith("/")) {
|
||||
parentObjectKey += "/";
|
||||
}
|
||||
|
||||
|
||||
for (FolderItemReq item : toCreateItems) {
|
||||
// 构造对象Key
|
||||
String objectKey = getDirMinioObjectKey(parentObjectKey + item.getFolderName());
|
||||
|
||||
|
||||
// 创建元数据对象(复用现有的 createDirectoryMetadata 方法)
|
||||
// 传入 folderUuid 作为 relatedResourceUuid
|
||||
FileMetadataInfo dirInfo = createDirectoryMetadata(
|
||||
@@ -4636,12 +4655,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
item.getFolderUuid(), // 保存 UUID
|
||||
null,
|
||||
parentDir.getDirType());
|
||||
|
||||
|
||||
newDirs.add(dirInfo);
|
||||
minioKeys.add(objectKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 填充成功响应结果
|
||||
*/
|
||||
@@ -4656,7 +4675,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
public SdmResponse listRecycleBin(ListRecycleBinReq req) {
|
||||
Long tenantId = ThreadLocalContext.getTenantId();
|
||||
PageHelper.startPage(req.getCurrent(), req.getSize());
|
||||
|
||||
|
||||
List<FileMetadataInfo> list = fileMetadataInfoService.lambdaQuery()
|
||||
.eq(FileMetadataInfo::getTenantId, tenantId)
|
||||
.isNotNull(FileMetadataInfo::getDeletedAt)
|
||||
@@ -4664,16 +4683,16 @@ 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();
|
||||
|
||||
|
||||
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();
|
||||
@@ -4681,7 +4700,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
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);
|
||||
@@ -4716,7 +4735,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
.eq(FileMetadataInfo::getId, id)
|
||||
.isNotNull(FileMetadataInfo::getDeletedAt)
|
||||
.one();
|
||||
|
||||
|
||||
if (ObjectUtils.isEmpty(metadata)) {
|
||||
return SdmResponse.failed("文件/目录不存在或不在回收站中");
|
||||
}
|
||||
@@ -4733,60 +4752,40 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
}
|
||||
parentPath = parent.getObjectKey();
|
||||
}
|
||||
|
||||
|
||||
String oldKey = metadata.getObjectKey();
|
||||
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).png;objectKey 保持版本号不变,改成 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();
|
||||
|
||||
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 + ")";
|
||||
}
|
||||
}
|
||||
while (fileMetadataInfoService.lambdaQuery()
|
||||
.eq(FileMetadataInfo::getParentId, metadata.getParentId())
|
||||
.eq(FileMetadataInfo::getOriginalName, restoreName)
|
||||
.eq(FileMetadataInfo::getTenantId, ThreadLocalContext.getTenantId())
|
||||
.isNull(FileMetadataInfo::getDeletedAt)
|
||||
.exists()) {
|
||||
|
||||
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 表示清除删除状态
|
||||
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);
|
||||
@@ -4794,7 +4793,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
metadata.setRecycleExpireAt(null);
|
||||
metadata.setUpdateTime(LocalDateTime.now());
|
||||
fileMetadataInfoService.updateById(metadata);
|
||||
|
||||
|
||||
// 6. 如果是文件,更新 fileStorage
|
||||
if (Objects.equals(metadata.getDataType(), DataTypeEnum.FILE.getValue())) {
|
||||
fileStorageService.lambdaUpdate()
|
||||
@@ -4805,7 +4804,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
|
||||
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);
|
||||
@@ -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,16 +4838,66 @@ 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)
|
||||
.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());
|
||||
@@ -4873,22 +4922,22 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
if (ObjectUtils.isEmpty(req.getIds())) {
|
||||
return SdmResponse.failed("未选择要更新的文件");
|
||||
}
|
||||
|
||||
|
||||
List<FileMetadataInfo> metadataList = fileMetadataInfoService.listByIds(req.getIds());
|
||||
if (CollectionUtils.isEmpty(metadataList)) {
|
||||
return SdmResponse.failed("文件不存在");
|
||||
}
|
||||
|
||||
|
||||
int successCount = 0;
|
||||
for (FileMetadataInfo metadata : metadataList) {
|
||||
// 仅处理已删除的文件
|
||||
if (metadata.getDeletedAt() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 计算新的过期时间:基于 deletedAt + days
|
||||
LocalDateTime newExpireAt = metadata.getDeletedAt().plusDays(req.getDays());
|
||||
|
||||
|
||||
// 如果是目录,递归更新子项
|
||||
if (Objects.equals(DataTypeEnum.DIRECTORY.getValue(), metadata.getDataType())) {
|
||||
updateDirectoryRecycleExpire(metadata, newExpireAt);
|
||||
@@ -4900,7 +4949,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
}
|
||||
successCount++;
|
||||
}
|
||||
|
||||
|
||||
return SdmResponse.success("成功更新 " + successCount + " 个文件的回收站过期时间");
|
||||
}
|
||||
|
||||
@@ -4909,14 +4958,14 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
rootDir.setRecycleExpireAt(newExpireAt);
|
||||
rootDir.setUpdateTime(LocalDateTime.now());
|
||||
fileMetadataInfoService.updateById(rootDir);
|
||||
|
||||
|
||||
// 2. 批量更新子项(包括子目录和文件)
|
||||
// 只要是该目录下的(objectKey以目录key为前缀),且已删除的,都更新
|
||||
String dirKey = rootDir.getObjectKey();
|
||||
if (!dirKey.endsWith("/")) {
|
||||
dirKey += "/";
|
||||
}
|
||||
|
||||
|
||||
// 使用 LambdaUpdateWrapper 批量更新,效率更高
|
||||
fileMetadataInfoService.lambdaUpdate()
|
||||
.likeRight(FileMetadataInfo::getObjectKey, dirKey) // objectKey like 'dirKey%'
|
||||
|
||||
Reference in New Issue
Block a user