fix:优化文件删除回收站功能,支持批量设置过期时间

This commit is contained in:
2026-02-11 16:56:45 +08:00
parent 0086b41b66
commit 303ba868c5
6 changed files with 165 additions and 18 deletions

View File

@@ -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<Long> ids;
}

View File

@@ -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<Long> ids;
}

View File

@@ -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<Long> ids;
@Schema(description = "保留天数(从删除时间开始计算)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "保留天数不能为空")
@Min(value = 0, message = "保留天数不能小于0")
private Integer days;
}

View File

@@ -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);
}
/**
* 搜索文件
*

View File

@@ -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;
}

View File

@@ -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<String> 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<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);
} 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();
}
}