From 303ba868c5c8a20da29d9c55d15d7caa8aef4491 Mon Sep 17 00:00:00 2001 From: gulongcheng <474084054@qq.com> Date: Wed, 11 Feb 2026 16:56:45 +0800 Subject: [PATCH] =?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?= =?UTF-8?q?,=E6=94=AF=E6=8C=81=E6=89=B9=E9=87=8F=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E8=BF=87=E6=9C=9F=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/PermanentDeleteFromRecycleReq.java | 8 +- .../req/data/RestoreFromRecycleReq.java | 8 +- .../req/data/UpdateRecycleExpireReq.java | 21 ++++ .../data/controller/DataFileController.java | 21 +++- .../sdm/data/service/IDataFileService.java | 19 +++- .../impl/MinioFileIDataFileServiceImpl.java | 106 +++++++++++++++++- 6 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 common/src/main/java/com/sdm/common/entity/req/data/UpdateRecycleExpireReq.java diff --git a/common/src/main/java/com/sdm/common/entity/req/data/PermanentDeleteFromRecycleReq.java b/common/src/main/java/com/sdm/common/entity/req/data/PermanentDeleteFromRecycleReq.java index 51f11965..f647109f 100644 --- a/common/src/main/java/com/sdm/common/entity/req/data/PermanentDeleteFromRecycleReq.java +++ b/common/src/main/java/com/sdm/common/entity/req/data/PermanentDeleteFromRecycleReq.java @@ -4,10 +4,12 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.util.List; + @Data @Schema(description = "从回收站彻底删除请求") public class PermanentDeleteFromRecycleReq { - @Schema(description = "要彻底删除的文件/目录ID", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "文件/目录ID不能为空") - private Long id; + @Schema(description = "要彻底删除的文件/目录ID列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "文件/目录ID列表不能为空") + private List ids; } diff --git a/common/src/main/java/com/sdm/common/entity/req/data/RestoreFromRecycleReq.java b/common/src/main/java/com/sdm/common/entity/req/data/RestoreFromRecycleReq.java index 0e21bb2c..95deef5e 100644 --- a/common/src/main/java/com/sdm/common/entity/req/data/RestoreFromRecycleReq.java +++ b/common/src/main/java/com/sdm/common/entity/req/data/RestoreFromRecycleReq.java @@ -4,10 +4,12 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.util.List; + @Data @Schema(description = "从回收站还原请求") public class RestoreFromRecycleReq { - @Schema(description = "要还原的文件/目录ID", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "文件/目录ID不能为空") - private Long id; + @Schema(description = "要还原的文件/目录ID列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "文件/目录ID列表不能为空") + private List ids; } diff --git a/common/src/main/java/com/sdm/common/entity/req/data/UpdateRecycleExpireReq.java b/common/src/main/java/com/sdm/common/entity/req/data/UpdateRecycleExpireReq.java new file mode 100644 index 00000000..d889b8dc --- /dev/null +++ b/common/src/main/java/com/sdm/common/entity/req/data/UpdateRecycleExpireReq.java @@ -0,0 +1,21 @@ +package com.sdm.common.entity.req.data; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "批量更新回收站过期时间请求") +public class UpdateRecycleExpireReq { + @Schema(description = "文件/目录ID列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "文件/目录ID列表不能为空") + private List ids; + + @Schema(description = "保留天数(从删除时间开始计算)", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "保留天数不能为空") + @Min(value = 0, message = "保留天数不能小于0") + private Integer days; +} diff --git a/data/src/main/java/com/sdm/data/controller/DataFileController.java b/data/src/main/java/com/sdm/data/controller/DataFileController.java index b4431c42..c15d337e 100644 --- a/data/src/main/java/com/sdm/data/controller/DataFileController.java +++ b/data/src/main/java/com/sdm/data/controller/DataFileController.java @@ -146,31 +146,44 @@ public class DataFileController implements IDataFeignClient { } /** - * 从回收站还原 + * 批量从回收站还原 * * @param req * @return */ @SysLog("从回收站还原") @PostMapping("/restoreFromRecycle") - @Operation(summary = "从回收站还原", description = "将文件/目录从回收站还原到原位置") + @Operation(summary = "从回收站还原", description = "将文件/目录从回收站还原到原位置(支持批量)") public SdmResponse restoreFromRecycle(@RequestBody @Validated RestoreFromRecycleReq req) { return IDataFileService.restoreFromRecycle(req); } /** - * 从回收站彻底删除 + * 批量从回收站彻底删除 * * @param req * @return */ @SysLog("从回收站彻底删除") @PostMapping("/permanentDeleteFromRecycle") - @Operation(summary = "从回收站彻底删除", description = "从回收站彻底删除文件/目录(物理删除,不可恢复)") + @Operation(summary = "从回收站彻底删除", description = "从回收站彻底删除文件/目录(物理删除,不可恢复,支持批量)") public SdmResponse permanentDeleteFromRecycle(@RequestBody @Validated PermanentDeleteFromRecycleReq req) { return IDataFileService.permanentDeleteFromRecycle(req); } + /** + * 批量更新回收站过期时间 + * + * @param req + * @return + */ + @SysLog("更新回收站过期时间") + @PostMapping("/updateRecycleExpire") + @Operation(summary = "更新回收站过期时间", description = "批量更新回收站中文件/目录的过期时间") + public SdmResponse updateRecycleExpire(@RequestBody @Validated UpdateRecycleExpireReq req) { + return IDataFileService.updateRecycleExpire(req); + } + /** * 搜索文件 * 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 dc8a18c6..329ae09e 100644 --- a/data/src/main/java/com/sdm/data/service/IDataFileService.java +++ b/data/src/main/java/com/sdm/data/service/IDataFileService.java @@ -140,25 +140,34 @@ public interface IDataFileService { * @param req 回收站列表查询请求参数 * @return 回收站列表响应 */ - default SdmResponse listRecycleBin(com.sdm.common.entity.req.data.ListRecycleBinReq req) { + default SdmResponse listRecycleBin(ListRecycleBinReq req) { return null; } /** - * 从回收站还原文件/目录 + * 批量从回收站还原文件/目录 * @param req 还原请求参数 * @return 还原结果响应 */ - default SdmResponse restoreFromRecycle(com.sdm.common.entity.req.data.RestoreFromRecycleReq req) { + default SdmResponse restoreFromRecycle(RestoreFromRecycleReq req) { return null; } /** - * 从回收站彻底删除文件/目录 + * 批量从回收站彻底删除文件/目录 * @param req 彻底删除请求参数 * @return 删除结果响应 */ - default SdmResponse permanentDeleteFromRecycle(com.sdm.common.entity.req.data.PermanentDeleteFromRecycleReq req) { + default SdmResponse permanentDeleteFromRecycle(PermanentDeleteFromRecycleReq req) { + return null; + } + + /** + * 批量更新回收站过期时间 + * @param req 更新过期时间请求参数 + * @return 更新结果响应 + */ + default SdmResponse updateRecycleExpire(com.sdm.common.entity.req.data.UpdateRecycleExpireReq req) { return null; } 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 1c6498fd..9c51ff4c 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 @@ -105,6 +105,10 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { private static final String FLOWABLE_SIMULATION_BASEDIR = "/home/simulation/"; @Value("${data.recycle.retention-days:7}") + + + + private Integer recycleRetentionDays; @Autowired @@ -4686,8 +4690,30 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { @Override @Transactional(rollbackFor = Exception.class) public SdmResponse restoreFromRecycle(RestoreFromRecycleReq req) { + if (ObjectUtils.isEmpty(req.getIds())) { + return SdmResponse.failed("未选择要还原的文件"); + } + + List messages = new ArrayList<>(); + for (Long id : req.getIds()) { + SdmResponse resp = restoreSingleFile(id); + if (!resp.isSuccess()) { + throw new RuntimeException("还原文件(ID:" + id + ")失败: " + resp.getMessage()); + } + if (resp.getData() != null && !Objects.equals(resp.getData(), "还原成功")) { + messages.add(resp.getData().toString()); + } + } + + if (!messages.isEmpty()) { + return SdmResponse.success("还原成功,部分文件已重命名: " + String.join("; ", messages)); + } + return SdmResponse.success("批量还原成功"); + } + + private SdmResponse restoreSingleFile(Long id) { FileMetadataInfo metadata = fileMetadataInfoService.lambdaQuery() - .eq(FileMetadataInfo::getId, req.getId()) + .eq(FileMetadataInfo::getId, id) .isNotNull(FileMetadataInfo::getDeletedAt) .one(); @@ -4778,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); + return SdmResponse.success(Objects.equals(restoreName, originalName) ? "还原成功" : "已重命名为: " + restoreName); } catch (Exception e) { log.error("还原失败", e); @@ -4789,8 +4815,22 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { @Override @Transactional(rollbackFor = Exception.class) public SdmResponse permanentDeleteFromRecycle(PermanentDeleteFromRecycleReq req) { + if (ObjectUtils.isEmpty(req.getIds())) { + return SdmResponse.failed("未选择要删除的文件"); + } + + for (Long id : req.getIds()) { + SdmResponse resp = permanentDeleteSingleFile(id); + if (!resp.isSuccess()) { + throw new RuntimeException("删除文件(ID:" + id + ")失败: " + resp.getMessage()); + } + } + return SdmResponse.success("批量彻底删除成功"); + } + + private SdmResponse permanentDeleteSingleFile(Long id) { FileMetadataInfo metadata = fileMetadataInfoService.lambdaQuery() - .eq(FileMetadataInfo::getId, req.getId()) + .eq(FileMetadataInfo::getId, id) .isNotNull(FileMetadataInfo::getDeletedAt) .one(); @@ -4816,4 +4856,64 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService { } } + @Override + @Transactional(rollbackFor = Exception.class) + public SdmResponse updateRecycleExpire(UpdateRecycleExpireReq req) { + if (ObjectUtils.isEmpty(req.getIds())) { + return SdmResponse.failed("未选择要更新的文件"); + } + + List 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); + } else { + // 单文件更新 + metadata.setRecycleExpireAt(newExpireAt); + metadata.setUpdateTime(LocalDateTime.now()); + fileMetadataInfoService.updateById(metadata); + } + successCount++; + } + + return SdmResponse.success("成功更新 " + successCount + " 个文件的回收站过期时间"); + } + + private void updateDirectoryRecycleExpire(FileMetadataInfo rootDir, LocalDateTime newExpireAt) { + // 1. 更新目录本身 + 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%' + .eq(FileMetadataInfo::getBucketName, rootDir.getBucketName()) + .isNotNull(FileMetadataInfo::getDeletedAt) // 必须是已删除的 + .set(FileMetadataInfo::getRecycleExpireAt, newExpireAt) + .set(FileMetadataInfo::getUpdateTime, LocalDateTime.now()) + .update(); + } + } \ No newline at end of file