基于Springboot + Vue3的云盘系统

news2024/11/25 11:40:16

目录

  • 一. 🦁 前言
  • 二. 🦁 主要技术栈
  • 三. 🦁 架构搭建
    • 1. 项目搭建效果
    • 2. 各部分作用
  • 四. 🦁 主要功能
    • 1.功能图
    • 2. 主要功能
      • 2.1 分片上传文件
      • 2.2 存储分享记录
  • 五. 🦁 效果显示

一. 🦁 前言

源码获取地址:https://download.csdn.net/download/m0_58847451/87789468?spm=1001.2014.3001.5503

本系统是一个文件存储和共享平台,提供了强大的功能和可靠的数据保护,方便用户随时随地进行文件的管理和分享。本系统基于Springboot+Vue实现,操作简便,界面美观,拥有良好的用户体验和稳定的性能。无论是个人用户还是团队合作,都能够在此享受到高效和便捷的文件存储和共享服务。

二. 🦁 主要技术栈

  1. 后端
    SpringBootMySQLMybatisSpringMVCRedisffmpeg
  2. 前端
    Vue3Element-Plus

三. 🦁 架构搭建

tips:
该项目的项目结构改编参考了天罡大佬的《Maven 三层项目结构搭建》一文(天罡大佬人很好,很热心解答狮子疑惑的问题,推荐关注哦!!!),但是又有写不同的地方,狮子根据自己的理解整理成如下结构:

image-20230428112738573

1. 项目搭建效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 各部分作用

  • 表现层: web层,提供controller,处理http请求,提供给前端的API;
  • 业务逻辑层: service层,和业务相关,主要处理业务逻辑;
  • 数据访问层: dal层,做数据访问,主要使用Mybatis与MySQL交互数据;
  • 通用层:common层,主要存放实体类,常量,工具,异常类等。

四. 🦁 主要功能

1.功能图

云盘系统

2. 主要功能

2.1 分片上传文件

这个方法是本系统的核心功能,前端将文件分片后将参数传回,后台接收上传文件的参数,将文件暂存在临时目录中。如果是第一个分片,则判断是否存在相同的文件(根据文件的 MD5 值判断),如果存在相同的文件,则将其直接作为秒传处理,否则继续将文件保存在临时目录中。如果是最后一个分片,则将其记录在数据库中,并异步调用文件合并的方法。在整个上传过程中,还需要进行磁盘空间和用户可用空间的判断。最后返回上传结果。

    @Transactional(rollbackFor = Exception.class)
    public UploadResultDto uploadFile(SessionWebUserDto webUserDto, String fileId, MultipartFile file, String fileName, String filePid, String fileMd5,
                                      Integer chunkIndex,
                                      Integer chunks) {
        File tempFileFolder = null;
        Boolean uploadSuccess = true;
        try {
            UploadResultDto resultDto = new UploadResultDto();
            if (StringTools.isEmpty(fileId)) {
                fileId = StringTools.getRandomString(Constants.LENGTH_10);
            }
            resultDto.setFileId(fileId);
            Date curDate = new Date();
            UserSpaceDto spaceDto = redisComponent.getUserSpaceUse(webUserDto.getUserId());
            if (chunkIndex == 0) {
                FileInfoQuery infoQuery = new FileInfoQuery();
                infoQuery.setFileMd5(fileMd5);
                infoQuery.setSimplePage(new SimplePage(0, 1));
                infoQuery.setStatus(FileStatusEnums.USING.getStatus());
                List<FileInfo> dbFileList = this.fileInfoMapper.selectList(infoQuery);
                //秒传
                if (!dbFileList.isEmpty()) {
                    FileInfo dbFile = dbFileList.get(0);
                    //判断文件状态
                    if (dbFile.getFileSize() + spaceDto.getUseSpace() > spaceDto.getTotalSpace()) {
                        throw new BusinessException(ResponseCodeEnum.CODE_904);
                    }
                    dbFile.setFileId(fileId);
                    dbFile.setFilePid(filePid);
                    dbFile.setUserId(webUserDto.getUserId());
                    dbFile.setFileMd5(null);
                    dbFile.setCreateTime(curDate);
                    dbFile.setLastUpdateTime(curDate);
                    dbFile.setStatus(FileStatusEnums.USING.getStatus());
                    dbFile.setDelFlag(FileDelFlagEnums.USING.getFlag());
                    dbFile.setFileMd5(fileMd5);
                    fileName = autoRename(filePid, webUserDto.getUserId(), fileName);
                    dbFile.setFileName(fileName);
                    this.fileInfoMapper.insert(dbFile);
                    resultDto.setStatus(UploadStatusEnums.UPLOAD_SECONDS.getCode());
                    //更新用户空间使用
                    updateUserSpace(webUserDto, dbFile.getFileSize());

                    return resultDto;
                }
            }
            //暂存在临时目录
            String tempFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_TEMP;
            String currentUserFolderName = webUserDto.getUserId() + fileId;
            //创建临时目录
            tempFileFolder = new File(tempFolderName + currentUserFolderName);
            if (!tempFileFolder.exists()) {
                tempFileFolder.mkdirs();
            }

            //判断磁盘空间
            Long currentTempSize = redisComponent.getFileTempSize(webUserDto.getUserId(), fileId);
            if (file.getSize() + currentTempSize + spaceDto.getUseSpace() > spaceDto.getTotalSpace()) {
                throw new BusinessException(ResponseCodeEnum.CODE_904);
            }

            File newFile = new File(tempFileFolder.getPath() + "/" + chunkIndex);
            file.transferTo(newFile);

            //保存临时大小
            redisComponent.saveFileTempSize(webUserDto.getUserId(), fileId, file.getSize());
            //不是最后一个分片,直接返回
            if (chunkIndex < chunks - 1) {
                resultDto.setStatus(UploadStatusEnums.UPLOADING.getCode());
                return resultDto;
            }
            redisComponent.saveFileTempSize(webUserDto.getUserId(), fileId, file.getSize());
            //最后一个分片上传完成,记录数据库,异步合并分片
            String month = DateUtil.format(curDate, DateTimePatternEnum.YYYYMM.getPattern());
            String fileSuffix = StringTools.getFileSuffix(fileName);
            //真实文件名
            String realFileName = currentUserFolderName + fileSuffix;
            FileTypeEnums fileTypeEnum = FileTypeEnums.getFileTypeBySuffix(fileSuffix);
            //自动重命名
            fileName = autoRename(filePid, webUserDto.getUserId(), fileName);
            FileInfo fileInfo = new FileInfo();
            fileInfo.setFileId(fileId);
            fileInfo.setUserId(webUserDto.getUserId());
            fileInfo.setFileMd5(fileMd5);
            fileInfo.setFileName(fileName);
            fileInfo.setFilePath(month + "/" + realFileName);
            fileInfo.setFilePid(filePid);
            fileInfo.setCreateTime(curDate);
            fileInfo.setLastUpdateTime(curDate);
            fileInfo.setFileCategory(fileTypeEnum.getCategory().getCategory());
            fileInfo.setFileType(fileTypeEnum.getType());
            fileInfo.setStatus(FileStatusEnums.TRANSFER.getStatus());
            fileInfo.setFolderType(FileFolderTypeEnums.FILE.getType());
            fileInfo.setDelFlag(FileDelFlagEnums.USING.getFlag());
            this.fileInfoMapper.insert(fileInfo);

            Long totalSize = redisComponent.getFileTempSize(webUserDto.getUserId(), fileId);
            updateUserSpace(webUserDto, totalSize);

            resultDto.setStatus(UploadStatusEnums.UPLOAD_FINISH.getCode());
            //事务提交后调用异步方法
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    fileInfoService.transferFile(fileInfo.getFileId(), webUserDto);
                }
            });
            return resultDto;
        } catch (BusinessException e) {
            uploadSuccess = false;
            logger.error("文件上传失败", e);
            throw e;
        } catch (Exception e) {
            uploadSuccess = false;
            logger.error("文件上传失败", e);
            throw new BusinessException("文件上传失败");
        } finally {
            //如果上传失败,清除临时目录
            if (tempFileFolder != null && !uploadSuccess) {
                try {
                    FileUtils.deleteDirectory(tempFileFolder);
                } catch (IOException e) {
                    logger.error("删除临时目录失败");
                }
            }
        }
    }

2.2 存储分享记录

这个方法用于存储分享记录,方法签名为public void saveShare(FileShare share)。方法的输入参数为一个FileShare对象。

在方法内部,首先根据share对象中的validType属性获取对应的ShareValidTypeEnums枚举值,如果无法获取该枚举值,则抛出BusinessException异常并返回错误码ResponseCodeEnum.CODE_600。

接着,根据validType属性判断分享是否永久有效,如果不是,则设置该分享的过期时间为当前时间加上对应的天数。

然后,获取当前时间作为分享时间,并为分享记录设置随机的分享ID和提取码(如果提取码为空)。

最后,将分享记录保存至数据库中。

    @Override
    public void saveShare(FileShare share) {
        ShareValidTypeEnums typeEnum = ShareValidTypeEnums.getByType(share.getValidType());
        if (null == typeEnum) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        if (typeEnum != ShareValidTypeEnums.FOREVER) {
            share.setExpireTime(DateUtil.getAfterDate(typeEnum.getDays()));
        }
        Date curDate = new Date();
        share.setShareTime(curDate);
        if (StringTools.isEmpty(share.getCode())) {
            share.setCode(StringTools.getRandomString(Constants.LENGTH_5));
        }
        share.setShareId(StringTools.getRandomString(Constants.LENGTH_20));
        this.fileShareMapper.insert(share);
    }

五. 🦁 效果显示

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/584811.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python常用遥感模块Rasterio与Rioxarray的安装与使用

1. Rasterio与Rioxarray安装 Rasterio 是一个很多模块是基于 GDAL 的 Python 包&#xff0c;可用于处理地理空间栅格数据&#xff0c;例如 GeoTIFF 文件。为此&#xff0c;可以使用许多模块和函数&#xff0c;例如&#xff0c;处理来自卫星的原始数据、读取栅格数据、检索地理…

什么是API接口,API接口类型有哪些?

随着互联网技术的不断发展&#xff0c;全球网络数据呈现爆炸式增长&#xff0c;如何从这些数据中挖掘出有价值的信息变得愈发重要。API接口作为一种重要的数据获取方式&#xff0c;逐渐引起了人们的关注。 API&#xff08;Application Programming Interface&#xff09;是软件…

pip安装软件包报错: error subprocess-exited-with-error问题记录

问题修复 执行pip install dlib 在执行pip3 install virtualenvwrapper也会报同样的错误 问题修复 我们需要执行如下命令&#xff1a; pip install --upgrade setuptoolspip install --upgrade setuptools 命令用于升级 setuptools 库的版本。setuptools 是 Python 的一个开…

python+selenium做ui自动化测试用法必会

一、前言 大家都知道&#xff0c;基于Web端的测试的基础框架是需要Selenium做主要支撑的&#xff0c;这里边给大家介绍下Web测试核心之基于 Python 的 Selenium Selenium 是用于测试 Web 应用程序用户界面 (UI) 的常用框架。它是一款用于运行端到端功能测试的超强工具。您可以…

已签名驱动程序安装后提示“Windows无法验证此设备所需驱动程序数字签名”的原因和解决方法

在Windows 64位系统上&#xff0c;正常开启数字签名认证时&#xff0c;驱动程序软件需要经过微软数字签名的才允许被使用。否则在设备管理器下&#xff0c;安装完硬件驱动后设备上会有“黄色感叹号”标识&#xff0c;右键该设备属性提示&#xff1a;“Windows 无法验证此设备所…

项目管理的49个过程的数据流向

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 过程组主要数据流 启动过程组 规划过程组 执行过程组 监控过程组 收尾过程组 4、项目整合管理 4.1制定项目章程 4.2制定项目管理计划 4.3指导与管理项目工作 4.4…

【28】核心易中期刊推荐——医学影像识别及应用

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

容器与虚拟机有啥区别?常见的容器技术有哪些?

传统的虚拟机技术通过在物理硬件上运行虚拟化层&#xff08;Hypervisor&#xff09;&#xff0c;将物理资源&#xff08;如处理器、内存、存储等&#xff09;虚拟化为多个独立的虚拟机。每个虚拟机都有自己的操作系统和应用程序&#xff0c;它们在各自的虚拟环境中运行&#xf…

解决Edge总是闪退的方法

问题描述 最近在使用Edge的时候总是会闪退&#xff0c;太不方便了。所以去网上搜了一下解决办法。参考文章&#xff1a;http://www.65ly.com/a/20211110/163655036712573.html 解决办法 1. 使用管理员身份打开命令提示符 2. 修复系统 先后输入如下两条命令&#xff1a; DI…

旋转设备轴承故障,有哪些常见原因及监测手段?

在各种旋转设备中&#xff0c;轴承是关键的构件之一。轴承的正常运行对于设备的稳定性、寿命和效率至关重要。然而&#xff0c;轴承也是容易出现故障的部件之一。为了及时发现轴承故障并采取维修措施&#xff0c;监测轴承状态变得至关重要。本文将介绍旋转设备轴承常见的故障问…

psd文件丢失了怎么恢复?分享原因及对应恢复方法

PSD文件在设计行业中非常重要。但是&#xff0c;不幸的是&#xff0c;有时这些文件可能会因多种原因而丢失。那么在未备份PSD文件的情况下&#xff0c;PSD文件丢失了怎么恢复呢&#xff1f;如果您遇到了这种问题&#xff0c;不要惊慌&#xff0c;在本篇文章中&#xff0c;我们将…

调用华为API实现情感分析

作者介绍 王新华&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向&#xff1a;人工智能与模式识别 电子邮件&#xff1a;996514274qq.com 魏小双&#xff0c;女&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向…

Web安全:PHP反序列化漏洞的 测试(向服务器写入一句话.)

Web安全&#xff1a;PHP反序列化漏洞的 测试 程序员在写代码时&#xff0c;没有对用户输入的序列化字符串做一个严格检测&#xff0c;导致恶意的用户可以控制反序列化的一个过程&#xff0c;因此导致XSS漏洞&#xff0c;代码执行&#xff0c;SQT注入&#xff0c;目录遍历等不可…

Shader Graph19-位置函数

一、打开Unreal&#xff0c;新建Material命名为DemoPosition&#xff0c;双击打开 因为中心点为物体中心&#xff0c;所以将坐标值连接到Base Color上是上面的效果&#xff0c;然后在场景中添加一个球体&#xff0c;将材质应用在该球体上&#xff0c;按下w我们可以在场景中改变…

Alibaba官方「SpringCloudAlibaba全彩学习手册」限时开源!

最近我在知乎上看过的一个热门回答&#xff1a; 初级 Java 开发面临的最大瓶颈在于&#xff0c;脱离不出自身业务带来的局限。日常工作中大部分时间在增删改查、写写接口、改改 bug&#xff0c;久而久之就会发现&#xff0c;自己的技术水平跟刚工作时相比没什么进步。 所以我们…

Linux压缩和归档命令的速查表

在Linux系统中&#xff0c;有多种命令可用于压缩和归档文件和目录。这些命令使我们能够将文件和目录打包成单个文件&#xff0c;并可以选择压缩以节省存储空间。本文将提供一个Linux压缩和归档命令的速查表&#xff0c;帮助您快速查找和了解各种常用命令及其用法。 压缩文件和目…

creator-热更

title: creator-热更 categories: Cocos2dx tags: [creator, 热更] date: 2023-04-20 10:42:26 comments: false mathjax: true toc: true creator-热更 前篇 热更新管理器 AssetsManager - https://docs.cocos.com/creator/manual/zh/advanced-topics/hot-update-manager.htm…

实验篇(7.2) 04. 映射服务器到公网IP 远程访问 ❀ Fortinet网络安全专家 NSE4

【简介】由于服务器的IP是内网地址&#xff0c;所以无法从公网直接访问服务器。要想远程访问服务器&#xff0c;最简单的办法就是将服务器映射到公网IP&#xff0c;然后通过公网IP加端口号的方式进行访问。 实验要求与环境 OldMei集团深圳总部部署了一台服务器&#xff0c;用来…

Vue学习-知识点总结

&#xff08;一&#xff09;基础知识 1、简单使用 <!DOCTYPE html> <html> <head><meta charset"utf-8" /><title></title><!--第一步&#xff1a;导入vue--><script src"js/vue-2.6.10.js"></script…

深度学习相关知识--池化

池化 概念 池化分为最大池化&#xff08;用的多一些&#xff09;和平均池化 最大池化是选出区域内最大值作为池化后的值&#xff0c;如下图所示&#xff1a; 平均池化是选择区域内平均值作为池化后的值&#xff0c;如下图所示&#xff1a; 概念很浅显&#xff0c;但是对于刚…