From 303ead37a77fe463382c8eeea173ea5ebd6df551 Mon Sep 17 00:00:00 2001 From: gulongcheng <474084054@qq.com> Date: Wed, 11 Feb 2026 11:01:13 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=E4=BC=98=E5=8C=96=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=9B=9E=E6=94=B6=E7=AB=99=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FileSimulationMappingServiceImpl.java | 8 +- .../impl/MinioFileIDataFileServiceImpl.java | 242 ++++++++++++------ 2 files changed, 173 insertions(+), 77 deletions(-) diff --git a/data/src/main/java/com/sdm/data/service/impl/FileSimulationMappingServiceImpl.java b/data/src/main/java/com/sdm/data/service/impl/FileSimulationMappingServiceImpl.java index a678e0da..77487a66 100644 --- a/data/src/main/java/com/sdm/data/service/impl/FileSimulationMappingServiceImpl.java +++ b/data/src/main/java/com/sdm/data/service/impl/FileSimulationMappingServiceImpl.java @@ -51,9 +51,13 @@ public class FileSimulationMappingServiceImpl extends ServiceImpl allFileIds = new HashSet<>(); - Set 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 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 +724,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 +1399,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 +1422,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,20 +1434,19 @@ 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); + fileMetadataInfoService.lambdaUpdate() + .set(FileMetadataInfo::getObjectKey, oldDirMinioObjectKey) + .set(FileMetadataInfo::getOriginalName, oldName) + .eq(FileMetadataInfo::getId, dirMetadataInfo.getId()) + .update(); } catch (Exception re) { - log.error("重命名失败后回滚 MinIO 路径失败", re); + log.error("重命名失败后回滚失败", re); } throw new RuntimeException("重命名目录失败: " + e.getMessage(), e); } @@ -1452,29 +1467,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() + private 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 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 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 +4537,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 +4547,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 +4573,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 +4582,97 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { if (ObjectUtils.isEmpty(metadata)) { return SdmResponse.failed("文件/目录不存在或不在回收站中"); } + + // 1. 检查父目录状态 + FileMetadataInfo parent = null; + if (metadata.getParentId() != null) { + parent = fileMetadataInfoService.getById(metadata.getParentId()); + if (parent != null && parent.getDeletedAt() != null) { + return SdmResponse.failed("请先恢复父文件夹: " + parent.getOriginalName()); + } + } - // 如果是目录,需要递归还原所有子项 - if (Objects.equals(DataTypeEnum.DIRECTORY.getValue(), metadata.getDataType())) { - Set allFileIds = new HashSet<>(); - Set allDirIds = new HashSet<>(); - collectRecursiveIds(metadata.getId(), allFileIds, allDirIds); + String oldKey = metadata.getObjectKey(); + String originalName = metadata.getOriginalName(); + String bucketName = metadata.getBucketName(); + String parentPath = parent != null ? parent.getObjectKey() : ""; + + // 2. 冲突检测与自动重命名 + 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 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) From a312da00afa5eb96224a4217680accd7d43fc951 Mon Sep 17 00:00:00 2001 From: gulongcheng <474084054@qq.com> Date: Wed, 11 Feb 2026 11:33:03 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=E4=BC=98=E5=8C=96=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=9B=9E=E6=94=B6=E7=AB=99=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sdm/data/service/IDataFileService.java | 7 ++ .../impl/MinioFileIDataFileServiceImpl.java | 81 +++++++++++++++--- .../dataFileHandle/DeleteApproveStrategy.java | 85 +++++++++++-------- 3 files changed, 126 insertions(+), 47 deletions(-) diff --git a/data/src/main/java/com/sdm/data/service/IDataFileService.java b/data/src/main/java/com/sdm/data/service/IDataFileService.java index b0170ab3..dc8a18c6 100644 --- a/data/src/main/java/com/sdm/data/service/IDataFileService.java +++ b/data/src/main/java/com/sdm/data/service/IDataFileService.java @@ -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 创建目录请求参数 diff --git a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java index 1c63395e..d498095b 100644 --- a/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java +++ b/data/src/main/java/com/sdm/data/service/impl/MinioFileIDataFileServiceImpl.java @@ -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) { @@ -1440,15 +1493,17 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { // 尝试回滚 try { updatePathRecursively(newDirMinioObjectKey, oldDirMinioObjectKey, bucketName, null, null, false); - fileMetadataInfoService.lambdaUpdate() - .set(FileMetadataInfo::getObjectKey, oldDirMinioObjectKey) - .set(FileMetadataInfo::getOriginalName, oldName) - .eq(FileMetadataInfo::getId, dirMetadataInfo.getId()) - .update(); + // 注意:这里不需要手动回滚 DB,因为下面会 setRollbackOnly() 或抛出异常,Spring 会自动回滚 DB。 + // 这里的 updatePathRecursively 虽然会执行 DB 更新,但最终都会被回滚。 } catch (Exception 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()); } } @@ -1476,7 +1531,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { * @param expireAt 过期时间(传 null 则不更新) * @param updateStatus 是否更新删除状态 */ - private void updatePathRecursively(String oldPrefix, String newPrefix, String bucketName, LocalDateTime deletedAt, LocalDateTime expireAt, boolean updateStatus) { + 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); @@ -4584,20 +4639,24 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { } // 1. 检查父目录状态 - FileMetadataInfo parent = null; + String parentPath = ""; if (metadata.getParentId() != null) { - parent = fileMetadataInfoService.getById(metadata.getParentId()); - if (parent != null && parent.getDeletedAt() != 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(); } String oldKey = metadata.getObjectKey(); String originalName = metadata.getOriginalName(); String bucketName = metadata.getBucketName(); - String parentPath = parent != null ? parent.getObjectKey() : ""; // 2. 冲突检测与自动重命名 + // 循环检测 parentId 下是否存在同名文件(未删除的) String restoreName = originalName; String restoreKey; diff --git a/data/src/main/java/com/sdm/data/service/impl/dataFileHandle/DeleteApproveStrategy.java b/data/src/main/java/com/sdm/data/service/impl/dataFileHandle/DeleteApproveStrategy.java index c503a22f..292f4ef1 100644 --- a/data/src/main/java/com/sdm/data/service/impl/dataFileHandle/DeleteApproveStrategy.java +++ b/data/src/main/java/com/sdm/data/service/impl/dataFileHandle/DeleteApproveStrategy.java @@ -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 allFileIds = new HashSet<>(); - Set 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 allFileIds = new HashSet<>(); + Set allDirIds = new HashSet<>(); + collectRecursiveIds(service, rootDirId, allFileIds, allDirIds); - // 批量更新审批状态 + 回收站状态 - if (CollectionUtils.isNotEmpty(allFileIds)) { - List 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 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; } /**