【EasyPan】recoverFileBatch回收站文件批量还原方法解析

【EasyPan】项目常见问题解答(自用&持续更新中…)汇总版

回收站文件批量还原方法解析

一、方法总述

recoverFileBatch方法实现将回收站中的文件/文件夹批量还原到根目录的业务逻辑,主要特点:

  • 事务保障:使用@Transactional确保操作原子性
  • 层级恢复:递归处理文件夹及其子内容
  • 冲突解决:自动重命名与根目录同名的文件
  • 状态管理:精确控制文件状态转换(RECYCLE→USING)

二、方法流程及时序图

核心处理流程

客户端 服务层 持久层 数据库 调用recoverFileBatch(userId, fileIds) 解析fileIds为数组 查询回收站文件(query) SELECT...WHERE del_flag=2 返回fileInfoList 递归查询子内容(DEL状态) SELECT...WHERE file_pid=? 返回子文件列表 loop [遍历文件夹] 查询根目录文件(为后续的重命名做准备) SELECT...WHERE file_pid=0 返回rootFileList 批量更新子文件状态(DEL→USING) 批量更新选中文件状态(RECYCLE→USING) 检查根目录同名文件 执行重命名操作 alt [存在冲突] loop [文件名冲突检测] 客户端 服务层 持久层 数据库

三、方法模块化解析

1. 数据准备模块

// 输入参数处理
String[] fileIdArray = fileIds.split(","); 

// 构建回收站文件查询条件
FileInfoQuery query = new FileInfoQuery();
query.setUserId(userId);              // 用户隔离
query.setFileIdArray(fileIdArray);   // 目标文件ID集合
query.setDelFlag(FileDelFlagEnums.RECYCLE.getFlag()); // del_flag=2

安全设计

  • 严格限制用户操作权限
  • 只操作回收站状态的文件

2. 递归处理模块

// 收集待处理的子文件夹ID
List<String> delFileSubFolderFileIdList = new ArrayList<>(); 

// 递归查找DEL状态的子内容
findAllSubFolderFileList(delFileSubFolderFileIdList, userId, 
    fileInfo.getFileId(), FileDelFlagEnums.DEL.getFlag());

递归特点

  • 深度优先遍历文件夹树
  • 仅处理DEL状态的子内容
  • 结果自动收集到List

3. 根目录检测模块

// 查询根目录下的正常文件
query = new FileInfoQuery();
query.setUserId(userId);
query.setDelFlag(FileDelFlagEnums.USING.getFlag()); // del_flag=0
query.setFilePid(Constants.ZERO_STR);              // file_pid=0

// 转换为文件名映射表
Map<String, FileInfo> rootFileMap = allRootFileList.stream()
    .collect(Collectors.toMap(FileInfo::getFileName, 
            Function.identity(), (data1, data2) -> data2));

优化点

  • 使用Map提升文件名查找效率
  • 后出现的重复项自动覆盖(OOTB处理)

4. 状态更新模块

子文件状态更新
FileInfo fileInfo = new FileInfo();
fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag()); // DEL→USING
fileInfoMapper.updateFileDelFlagBatch(fileInfo, userId, 
    delFileSubFolderFileIdList, null, FileDelFlagEnums.DEL.getFlag());
主文件状态更新
FileInfo fileInfo = new FileInfo();
fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag()); // RECYCLE→USING
fileInfo.setFilePid(Constants.ZERO_STR);               // 重置到根目录
fileInfo.setLastUpdateTime(new Date());                 // 更新时间
fileInfoMapper.updateFileDelFlagBatch(fileInfo, userId, 
    null, delFileIdList, FileDelFlagEnums.RECYCLE.getFlag());

5. 冲突处理模块

// 检查根目录同名文件
FileInfo rootFileInfo = rootFileMap.get(item.getFileName());
if (rootFileInfo != null) {
    String fileName = StringTools.rename(item.getFileName()); // 自动重命名
    FileInfo updateInfo = new FileInfo();
    updateInfo.setFileName(fileName);
    fileInfoMapper.updateByFileIdAndUserId(updateInfo, 
        item.getFileId(), userId);
}

四、SQL执行示例

1. 回收站文件查询

SELECT * FROM file_info 
WHERE user_id = 'user123' 
  AND file_id IN ('file1','file2') 
  AND del_flag = 2

2. 子内容状态更新

UPDATE file_info SET 
  del_flag = 0 
WHERE user_id = 'user123' 
  AND file_pid IN ('folder1','folder2') 
  AND del_flag = 1

3. 主文件状态更新

UPDATE file_info SET 
  del_flag = 0,
  file_pid = '0',
  last_update_time = NOW() 
WHERE user_id = 'user123' 
  AND file_id IN ('file1','file2') 
  AND del_flag = 2

五、状态转换验证

初始状态
主文件还原
子文件还原
RECYCLE(2)
RECYCLE
USING(0)
DEL(1)
USING

代码:

/**
 * 批量从回收站恢复文件/文件夹
 * @param userId 用户ID
 * @param fileIds 要恢复的文件ID列表,多个用逗号分隔
 */
@Override
@Transactional(rollbackFor = Exception.class) // 声明式事务管理,任何异常都会回滚
public void recoverFileBatch(String userId, String fileIds) {
    // 1. 参数预处理:将逗号分隔的字符串转为数组
    String[] fileIdArray = fileIds.split(",");
    
    // 2. 构建查询条件:查询用户选中的回收站文件
    FileInfoQuery query = new FileInfoQuery();
    query.setUserId(userId); // 用户隔离
    query.setFileIdArray(fileIdArray); // 目标文件ID集合
    query.setDelFlag(FileDelFlagEnums.RECYCLE.getFlag()); // 只查询回收站状态的文件(del_flag=2)
    
    // 3. 执行查询获取待恢复文件列表
    List<FileInfo> fileInfoList = this.fileInfoMapper.selectList(query);
    
    // 4. 递归查找所有需要恢复的子文件夹(深度优先遍历)
    List<String> delFileSubFolderFileIdList = new ArrayList<>(); // 存储所有子文件夹ID
    for (FileInfo fileInfo : fileInfoList) {
        // 只处理文件夹类型(文件不需要递归)
        if (FileFolderTypeEnums.FOLDER.getType().equals(fileInfo.getFolderType())) {
            findAllSubFolderFileList(delFileSubFolderFileIdList, userId, 
                fileInfo.getFileId(), FileDelFlagEnums.DEL.getFlag());
        }
    }

    // 5. 查询根目录现有文件(用于文件名冲突检测)
    query = new FileInfoQuery();
    query.setUserId(userId);
    query.setDelFlag(FileDelFlagEnums.USING.getFlag()); // 正常状态文件(del_flag=0)
    query.setFilePid(Constants.ZERO_STR); // 根目录文件(file_pid=0)
    List<FileInfo> allRootFileList = this.findListByParam(query);

    // 6. 构建文件名映射表(快速冲突检测)
    Map<String, FileInfo> rootFileMap = allRootFileList.stream()
        .collect(Collectors.toMap(
            FileInfo::getFileName, 
            Function.identity(), 
            (existing, replacement) -> replacement)); // 同名文件保留后者

    // 7. 批量恢复子文件状态(DEL→USING)
    if (!delFileSubFolderFileIdList.isEmpty()) {
        FileInfo fileInfo = new FileInfo();
        fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag()); // 设置为正常状态
        this.fileInfoMapper.updateFileDelFlagBatch(
            fileInfo, 
            userId, 
            delFileSubFolderFileIdList, // 基于子文件夹ID的条件
            null, 
            FileDelFlagEnums.DEL.getFlag()); // 只更新DEL状态的文件
    }

    // 8. 批量恢复主文件状态(RECYCLE→USING)并移动到根目录
    List<String> delFileIdList = Arrays.asList(fileIdArray); // 用户原始选择的文件ID
    FileInfo fileInfo = new FileInfo();
    fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag()); // 设置为正常状态
    fileInfo.setFilePid(Constants.ZERO_STR); // 重置到根目录
    fileInfo.setLastUpdateTime(new Date()); // 更新操作时间
    this.fileInfoMapper.updateFileDelFlagBatch(
        fileInfo, 
        userId, 
        null, 
        delFileIdList, // 基于自身ID的条件
        FileDelFlagEnums.RECYCLE.getFlag()); // 只更新RECYCLE状态的文件

    // 9. 处理文件名冲突(自动重命名)
    for (FileInfo item : fileInfoList) {
        FileInfo rootFileInfo = rootFileMap.get(item.getFileName());
        // 检测到同名文件时自动重命名
        if (rootFileInfo != null) {
            String fileName = StringTools.rename(item.getFileName()); // 生成新文件名
            FileInfo updateInfo = new FileInfo();
            updateInfo.setFileName(fileName);
            this.fileInfoMapper.updateByFileIdAndUserId(
                updateInfo, 
                item.getFileId(), 
                userId);
        }
    }
}

你可能感兴趣的:(EasyPan,数据库)