【网盘系统】递归删除批量文件、从回收站恢复文件、彻底删除文件

为何需要用到递归?

在网盘系统中,文件的类型分为文件和文件夹两种类型。当我们想要批量删除文件时,不乏其中会包含文件夹,而想要删除这个文件夹,自然其中所包含的文件都要删除,而其中所包含的文件也有可能是文件夹,所以需要用到递归。

将文件移进回收站的代码实现(Service层)

java 复制代码
    /**
     * 将文件移到回收站
     * @param fileIds
     * @param userId
     */
    @Override
    public void removeFileToRecycleBin(String fileIds, String userId) {
        String[] fileIdArray = fileIds.split(",");
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FileInfo::getUserId, userId)
                .in(FileInfo::getFileId, Arrays.asList(fileIdArray))
                .eq(FileInfo::getDelFlag, FileDelFlagEnums.USING.getFlag());
        List<FileInfo> fileInfoList = this.baseMapper.selectList(queryWrapper);
        if(fileInfoList.isEmpty()){
            return;
        }
        // 待删除的所有目录Id
        List<String> allSubFolderList = new ArrayList<>();
        for (FileInfo fileInfo : fileInfoList) {
            if(fileInfo.getFolderType().equals(FileFolderTypeEnums.FOLDER.getType())){
                findAllSubFolderList(allSubFolderList, userId, fileInfo.getFileId(), FileDelFlagEnums.USING.getFlag());
            }
        }
        if(!allSubFolderList.isEmpty()){
            // 将这些目录下的所有文件标记为"已删除"
            LambdaUpdateWrapper<FileInfo> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(FileInfo::getUserId, userId)
                    .in(FileInfo::getFilePid, allSubFolderList)
                    .eq(FileInfo::getDelFlag, FileDelFlagEnums.USING.getFlag());
            FileInfo updateFileInfo = new FileInfo();
            updateFileInfo.setDelFlag(FileDelFlagEnums.DEL.getFlag());
            this.baseMapper.update(updateFileInfo, updateWrapper);
        }
        // 将选中的文件标记为"回收站"
        List<String> delFileIdList = Arrays.asList(fileIdArray);
        LambdaUpdateWrapper<FileInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(FileInfo::getUserId, userId)
                .in(FileInfo::getFileId, delFileIdList);
        FileInfo updateFileInfo = new FileInfo();
        updateFileInfo.setDelFlag(FileDelFlagEnums.RECYCLE.getFlag());
        updateFileInfo.setRecoveryTime(new Date());
        this.baseMapper.update(updateFileInfo, updateWrapper);
    }

文件的DelFlag类型有三种,使用中、回收站、删除。只有选中的文件会被标记为"回收站",而选中的文件夹内包含的文件则是标记为"已删除"。

为什么要这么设计呢?

可以参考我们windows的回收站,当我们把一个文件夹扔进回收站后,会发现在回收站中是不能把这个文件夹打开,自然也就无法看到文件夹中的文件了,所以文件夹中的文件就不需要标记为"回收站",直接标记为"已删除"。当我们需要去恢复时,只需要将那些父id为这个文件夹的文件重新标记为"使用中"即可。

上述代码中用到的"findAllSubFolderList"就是递归查找目标目录下的所有子目录的方法,这个方法不仅在此处用到,后续恢复文件和彻底删除文件也会用到,让我们看看实现的代码:

java 复制代码
    /**
     * 递归查找目标目录下的所有目录
     * @param fileIdList
     * @param userId
     * @param fileId
     * @param delFlag
     */
    private void findAllSubFolderList(List<String> fileIdList, String userId, String fileId, Integer delFlag){
        fileIdList.add(fileId);
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FileInfo::getUserId, userId)
                .eq(FileInfo::getFilePid, fileId)
                .eq(FileInfo::getDelFlag, delFlag)
                .eq(FileInfo::getFolderType, FileFolderTypeEnums.FOLDER.getType());
        //  找到这个目录的所有子目录
        List<FileInfo> fileInfoList = this.baseMapper.selectList(queryWrapper);
        // 递归查找所有子目录下的所有文件
        for (FileInfo fileInfo : fileInfoList) {
            findAllSubFolderList(fileIdList, userId, fileInfo.getFileId(), delFlag);
        }
    }

从回收站恢复文件的代码实现(Service层)

java 复制代码
    /**
     * 从回收站恢复文件
     * @param fileIds
     * @param userId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void recoverFileBatch(String fileIds, String userId) {
        String[] fileIdArray = fileIds.split(",");
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FileInfo::getUserId, userId)
                .eq(FileInfo::getDelFlag, FileDelFlagEnums.RECYCLE.getFlag())
                .in(FileInfo::getFileId, Arrays.asList(fileIdArray));
        List<FileInfo> fileInfoList = this.baseMapper.selectList(queryWrapper);
        if(fileInfoList.isEmpty()){
            return;
        }
        // 待恢复的所有目录Id
        List<String> allSubFolderList = new ArrayList<>();
        for (FileInfo fileInfo : fileInfoList) {
            if(fileInfo.getFolderType().equals(FileFolderTypeEnums.FOLDER.getType())){
                findAllSubFolderList(allSubFolderList, userId, fileInfo.getFileId(), FileDelFlagEnums.DEL.getFlag());
            }
        }
        // 查询所有根目录文件
        queryWrapper.clear();
        queryWrapper.eq(FileInfo::getUserId, userId)
                .eq(FileInfo::getDelFlag, FileDelFlagEnums.USING.getFlag())
                .eq(FileInfo::getFilePid, Constants.ZERO_STR);
        List<FileInfo> rootFileList = this.baseMapper.selectList(queryWrapper);
        Map<String, FileInfo> rootFileNameMap = rootFileList.stream()
                .collect(Collectors.toMap(FileInfo::getFileName, Function.identity(), (k1, k2)->k2));
        // 查询目录下所有被删除的文件,将目录下的所有被删除的文件更新为"使用中"
        if(!allSubFolderList.isEmpty()){
            FileInfo updateFileInfo = new FileInfo();
            updateFileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());
            LambdaUpdateWrapper<FileInfo> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(FileInfo::getUserId, userId)
                    .eq(FileInfo::getDelFlag, FileDelFlagEnums.DEL.getFlag())
                    .in(FileInfo::getFilePid, allSubFolderList);
            this.baseMapper.update(updateFileInfo, updateWrapper);
        }
        // 将选中的文件恢复到"使用中",且父级目录设置为根目录
        List<String> delFileIdList = Arrays.asList(fileIdArray);
        FileInfo updateFileInfo = new FileInfo();
        updateFileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());
        updateFileInfo.setFilePid(Constants.ZERO_STR);
        updateFileInfo.setLastUpdateTime(new Date());
        LambdaUpdateWrapper<FileInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(FileInfo::getUserId, userId)
                .eq(FileInfo::getDelFlag, FileDelFlagEnums.RECYCLE.getFlag())
                .in(FileInfo::getFileId, delFileIdList);
        this.baseMapper.update(updateFileInfo, updateWrapper);
        // 将所选文件重命名
        for (FileInfo fileInfo : fileInfoList) {
            FileInfo rootFileInfo = rootFileNameMap.get(fileInfo.getFileName());
            // 根目录下已经存在同名文件,需要进行重命名
            if(rootFileInfo != null){
                String newFileName = StringTools.rename(fileInfo.getFileName());
                this.baseMapper.updateFileNameByFileIdAndUserId(fileInfo.getFileId(), newFileName, userId);
            }
        }
    }

在我的网盘系统中,所有从回收站被恢复的文件都会被恢复到根目录中,不然移进回收站的时候还要记录下原先的文件路径,而原先的文件路径很可能在恢复之前已经被删除了,所以选择默认恢复到根目录中。

当然,在将回收站的文件恢复到根目录之前,还要检查是否需要重命名,因为可能与根目录中已存在的文件发生重名。

彻底删除文件的代码实现(Service层)

java 复制代码
    /**
     * 彻底删除文件
     * @param fileIds
     * @param userId
     * @param adminOp
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delFileBatch(String fileIds, String userId, Boolean adminOp) {
        String[] fileIdArray = fileIds.split(",");
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FileInfo::getUserId, userId)
                .eq(FileInfo::getDelFlag, FileDelFlagEnums.RECYCLE.getFlag())
                .in(FileInfo::getFileId, Arrays.asList(fileIdArray));
        List<FileInfo> fileInfoList = this.baseMapper.selectList(queryWrapper);
        if(fileInfoList.isEmpty()){
            return;
        }
        List<String> allSubFolderList = new ArrayList<>();
        // 找到所选文件子目录文件Id
        for (FileInfo fileInfo : fileInfoList) {
            if(fileInfo.getFolderType().equals(FileFolderTypeEnums.FOLDER.getType())){
                findAllSubFolderList(allSubFolderList, userId, fileInfo.getFileId(), FileDelFlagEnums.DEL.getFlag());
            }
        }
        // 删除所选文件子目录中的文件
        if(!allSubFolderList.isEmpty()){
            LambdaQueryWrapper<FileInfo> delWrapper = new LambdaQueryWrapper<>();
            delWrapper.eq(FileInfo::getUserId, userId)
                    .in(FileInfo::getFilePid, allSubFolderList);
            if(!adminOp){
                delWrapper.eq(FileInfo::getDelFlag, FileDelFlagEnums.DEL.getFlag());
            }
            this.baseMapper.delete(delWrapper); // 从数据库中彻底删除
        }
        // 删除所选文件
        LambdaQueryWrapper<FileInfo> delWrapper = new LambdaQueryWrapper<>();
        delWrapper.eq(FileInfo::getUserId, userId)
                .in(FileInfo::getFileId, Arrays.asList(fileIdArray));
        if(!adminOp){
            delWrapper.eq(FileInfo::getDelFlag, FileDelFlagEnums.RECYCLE.getFlag());
        }
        this.baseMapper.delete(delWrapper);
        // 更新用户使用空间
        Long useSpace = this.baseMapper.selectUseSpaceByUserId(userId);
        UserInfo userInfo = new UserInfo();
        userInfo.setUseSpace(useSpace);
        LambdaQueryWrapper<UserInfo> userQueryWrapper = new LambdaQueryWrapper<>();
        userQueryWrapper.eq(UserInfo::getUserId, userId);
        userInfoMapper.update(userInfo, userQueryWrapper);
        // 更新缓存
        UserSpaceDto userSpaceDto = redisComponent.getUserSpaceUse(userId);
        userSpaceDto.setUseSpace(useSpace);
        redisComponent.saveUserSpaceUse(userId, userSpaceDto);
    }

彻底删除的方法有两种人会调用,一种是用户彻底删除自己在回收站中的文件,一种是管理用彻底删除某个文件。区别在于如果是用户,那么只能彻底删除在回收站中的文件,所以需要传入一个adminOp 的参数,如果adminOp为false,也就是用户,那么必须校验文件的DelFlag是否为RECYCLE(文件处于回收站)。

在彻底删除文件之后应当更新用户的使用空间(归还用户使用空间),同时要把Redis中的缓存(用户使用空间)进行更新。

相关推荐
遇印记2 小时前
传输层,网络层
运维·服务器·网络·windows·学习
又見山3 小时前
Obsidian 跨平台同步实测:Nutstore Sync 在 Windows、Mac、Linux、iOS、Android 全平台综合测评
windows·macos·obsidian·obsidian同步·obsidian教程·obsidian入门·obsidian笔记库搭建
jearry4 小时前
一个编辑器两个内核:Markdown 实时视图与源码视图并存时的竞态、光标锚点与 PDF 导出
windows·markdown
逆向编程5 小时前
DirectX修复工具下载安装教程-Windows系统DLL缺失检测修复完整图文步骤
windows
钱栈up6 小时前
电脑卡死7分钟,日志断在13:56:03:一次Windows硬挂死的根因复盘
windows
我是海飞7 小时前
使用杰理 AC7925B 实现 Windows电脑 USB 副屏
windows·单片机·电脑·嵌入式·杰理
律宏阔9 小时前
Windows 双网卡配置:通过跃点数实现网线访问局域网、Wi-Fi 访问公网
windows
Source.Liu10 小时前
【A11】拥有者模式平台架构笔记
windows·笔记·架构·rust
可爱系程序猿13 小时前
Windows 11 下惠普 M126nw 驱动报错溯源:完整包、WSD 端口与类驱动记录
windows·程序人生·电脑
技术不好的崎鸣同学14 小时前
第七章:模块踩踏以覆写导出函数绕过CFG
c++·windows·安全·系统安全