SpringBoot网盘项目学习
程序员老罗:https://space.bilibili.com/499388891
所有项目资料:https://docs.qq.com/doc/DY1VMamFaWUttWnhi
# Easypan云盘架构
- 登录
- 账号登录
- 钉钉登录(第三方组件接入)
- 文件上传
- 文件预览
- 文件上传秒传
- ES
- ceph,OSS对象存储接入
# 功能模块
# 修改头像
vCode.write(...)
这行代码调用了对象 vCode
的 write
方法,将数据写入了 response
的输出流中。::::通过 write
方法向 response
的输出流中写入数据。
# 使用注解完成AOP
# 注解完成 AOP DEMO
当使用注解完成 AOP(面向切面编程)时,通常需要以下步骤:
- 创建一个切面类,定义通知方法,并使用注解标记这些方法。
- 使用注解将切面织入到目标方法中。
下面是一个简单的 Java 示例,演示如何使用注解完成 AOP 通知的代码示例:
// 导入相关的包
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
// 创建切面类
@Aspect
@Component
public class LoggingAspect {
// 定义前置通知方法,并使用注解标记
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice() {
System.out.println("前置通知:调用目标方法之前执行日志记录操作...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在这个示例中:
@Aspect
注解标记了这是一个切面类。@Before
注解标记了beforeAdvice()
方法是一个前置通知,它指定了一个切入点表达式"execution(* com.example.service.*.*(..))"
,表示匹配com.example.service
包中所有类的所有方法。
然后,你需要确保在 Spring 应用程序的配置文件中启用了基于注解的 AOP,以便让 Spring 框架能够识别并应用这些切面。例如,在 Spring Boot 应用程序中,通常会在主应用程序类上使用 @EnableAspectJAutoProxy
注解来启用自动代理功能。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
这样,当应用程序中的任何一个在 com.example.service
包下的方法被调用时,都会触发 LoggingAspect
类中定义的前置通知方法,即在目标方法执行前会先执行日志记录操作。
# 获取QQ授权
/ /第一步通过回调code 获取accesssToken
String accessToken = getQQAccessToken(code);
/ /第二步获取gq openId
string openId = getgoopenId(accessToken);
2
3
4
- 第一步是通过回调获得 code 并使用它来获取 access token。在这一步中,你会向 QQ 的认证服务器发送用户授权后的 code,然后获取到 access token。这个 access token 是用来代表用户的身份信息和权限的令牌,在后续的 API 请求中需要使用它来验证用户身份。
- 第二步是使用上一步获取的 access token 来获取用户的 QQ 号码(openId)。在这一步中,你会将之前获得的 access token 发送给 QQ 的 API 服务端,然后获取用户的 QQ 号码,这样你就可以识别和区分不同的用户了。
# 文件上传设计
- 实现高效的文件分片和上传顺序控制以支撑大文件的断点续传。
- 通过文件散列实现秒传功能以快速识别并跳过已上传内容。
- 提供鲁棒的错误处理和上传重试机制以应对网络等问题。
- 提供实时进度反馈,确保用户可以监控上传过程中的文件分片和整体进度。
主要思路
- 处理文件的第一个分片,先判断文件是否已存在,用于分片秒传
- 通过 api 计算分片的 md5,校验分片的哈希是否一致
- 暂存目录的创建(出现异常了,要删除这些临时文件)
- 分片文件的合并merge
- 校验合并后的文件 MB5
@Override
public UploadResultDto uploadFile(SessionWebUserDto webUserDto, String fileld, MultipartFile file, String fileName, String filepid, Integer chunkIndex, Integer chunks) {
UploadResultDto resultDto = new UploadResultDto();
if (stringTools.isEmpty(fileId)) { // 如果文件ID为空
fileId = StringTools.getRandomNumber(Constants.LENGTH, 10); // 生成随机文件ID
}
resultDto.setFileId(fileId); // 设置文件ID
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.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;
}
// 处理文件分片上传和合并逻辑,用于秒传,如果数据库中未找到文件
// 添加处理秒传的文件分片上传和合并逻辑...
}
// 处理非零chunkIndex和chunks的文件分片上传流程的继续
// 添加处理文件分片上传流程的继续逻辑...
return resultDto;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
库表设计:
CREATE TABLE `file_info` (
`file_id` varchar(10) NOT NULL COMMENT '文件ID',
`user_id` varchar(10) NOT NULL COMMENT '用户ID',
`file_md5` varchar(32) DEFAULT NULL COMMENT '文件MD5值',
`file_pid` varchar(10) DEFAULT NULL COMMENT '父级ID',
`file_size` bigint(20) DEFAULT NULL COMMENT '文件大小',
`file_name` varchar(200) DEFAULT NULL COMMENT '文件名',
`file_cover` varchar(100) DEFAULT NULL COMMENT '封面',
`file_path` varchar(100) DEFAULT NULL COMMENT '文件路径',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '最后更新时间',
`folder_type` tinyint(1) DEFAULT NULL COMMENT '文件夹类型:0文件,1目录',
`file_category` tinyint(1) DEFAULT NULL COMMENT '文件分类:1视频,2音频,3图片,4文档,5其他',
`file_type` tinyint(1) DEFAULT NULL COMMENT '文件类型:1视频,2音频,3图片,4pdf,5doc,6excel,7txt,8code,9zip,10其他',
`status` tinyint(1) DEFAULT NULL COMMENT '文件状态:0转码中,1转码失败,2转码成功',
`recovery_time` datetime DEFAULT NULL COMMENT '进入回收站时间',
`del_flag` tinyint(1) DEFAULT NULL COMMENT '删除标记:0正常,1回收站,2标记删除',
PRIMARY KEY (`file_id`, `user_id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_user_id` (`user_id`),
KEY `idx_md5` (`file_md5`),
KEY `idx_file_pid` (`file_pid`),
KEY `idx_del_flag` (`del_flag`),
KEY `idx_recovery_time` (`recovery_time`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件信息';
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 文件分享码
在实现文件分享码功能时,需要考虑用户体验、安全性和可扩展性。可以结合不同的技术和工具(如加密算法、数据库设计、权限控制等)来实现这一功能。
- 生成分享码:当用户选择分享文件时,系统需要生成一个唯一的分享码,并将文件与分享码相关联。
- 分享链接:将生成的分享码作为链接的一部分,形成一个可以访问到文件的URL。
- 访问权限:分享码应该具有一定的访问权限控制,例如设定分享码的有效期、限制访问次数等。
- 鉴权与访问:当用户访问带有分享码的链接时,系统需要验证分享码的有效性,并根据访问权限控制来决定是否允许用户访问文件。
- 过期时间等等。
# 个人理解
# 收获
- 看了一遍登录那里,果然 crud 其实没什么新意,最重要的就是一些
领域定义
,功能定义之类的。 - 具体的实现不重要,网上也能搜索到,或者 GPT 也能给一些灵感,直接手写也很简单
# 项目总结
# 优化版本:
担负设计与开发文件管理模块,提供全面的文件管理和交互体验,涵盖秒传、断点续传、进度条等功能。通过精细化权限和角色管理,用户能自主控制文件共享和数据安全。
# 秒传
为什么我们要选用 Elasticsearch (ES) ?
- Elasticsearch 提供的全文搜索和查询功能更为灵活,因为我存储的数据都是包括了文件的大小、图片大小、图片哈希以及图片位置等等。ES 更适合存储这类非结构化的数据
- 根据预估,我们存储的文件是千万级别以上的,因为后续会存储大规模的文件基本信息,以及需要高性能的数据检索和分析能力,ES 提供了分布式的存储和查询,适合处理大规模数据集。
- 如果应用对实时性有较高要求,尤其是需要在大规模数据上进行实时索引和分析时,ES因其实时性能更胜一筹。它设计用于快速的索引、搜索和分析,特别适用于日志、实时监控和实时数据处理等场景。
利用 Elasticsearch (ES) 实现秒传功能的步骤如下:
前端生成文件 Hash 值: 前端在用户选择文件后,会先在客户端生成文件的Hash值,通常选用如MD5或SHA-256等单向Hash函数。
前端发送Hash列表: 用户端将生成的Hash值列表发送给后台服务。
后端检查ES索引: 后端服务接收到前端发送的Hash列表后,批量查询Elasticsearch以确定哪些文件已经存在。后端根据查询结果会创建一个需要上传的文件的Hash列表。
示例ES批量查询(MGET或者Bulk API):
POST /file_hashes/_mget { "ids": ["hash1", "hash2", "hash3"] // 你的文件Hash值列表 }
1
2
3
4或使用Terms Query来一次检索多个值:
POST /file_hashes/_search { "query": { "terms": { "_id": ["hash1", "hash2", "hash3"] // 文件Hash作为文档ID } } }
1
2
3
4
5
6
7
8服务器返回需要上传的文件列表: 服务器根据Elasticsearch返回的结果过滤掉已经存在的Hash,只保留那些未曾上传的文件Hash值,传回给前端。
前端执行上传操作: 前端根据返回的需要上传的文件列表执行实际的文件上传操作。
后端更新ES索引: 当新文件上传完成后,后端将新文件的Hash值及其对应存储信息更新到ES索引中,为下一次搜索做好准备。
POST /file_hashes/_doc/hash_value // hash_value 是新上传文件的Hash { "file_path": "path_to_storage", // 文件存储的路径 "timestamp": "upload_time", // 上传的时间戳 // 其他相关信息... }
1
2
3
4
5
6
在此整个流程中,Elasticsearch主要用来快速定位文件是否已经存在,这样可以省去重复上传,节约带宽和存储空间。这个方法尤其在处理大量小文件时效率显著,因为通常文件的Hash值远小于文件本身,查询和对比Hash值消耗的资源比上传整个文件要少得多。
# 断点续传
断点续传是指在文件传输过程中,即使因为网络问题、用户取消上传或其他原因导致传输中断,用户也能够在网络条件好转或再次发起上传时,从上次中断的位置继续上传,而不是重新上传整个文件。这种机制可以显著提升大文件的上传体验和效率。
要实现断点续传的机制,你需要在客户端和服务器端都做一些工作:
客户端:
- 分片上传:
将大文件分割成多个小片段(分片)。这可以使用HTML5的
File API
来读取本地文件的特定部分。 - 记录上传进度: 记录每个分片是否已成功上传。通常需要一个本地存储机制(例如localStorage、IndexedDB或设备存储)来保持上传进度。
- 处理网络请求: 每个分片使用独立的网络请求进行上传。如果一个分片上传失败,可以只重试该分片。
服务器端:
- 接收分片: 准备好接收文件分片,并对每个分片进行校验,确保它没被损坏(例如通过比对分片的MD5)。
- 存储和管理分片: 将上传的每个分片存储在服务器上的临时位置,并记录每个分片的上传进度。
- 支持查询进度: 提供一个API接口用于查询上传进度,客户端可以通过此接口了解哪些分片需要重传。
- 合并分片: 当所有分片都上传完成后,服务器应该将它们按顺序合并成最终的文件。
- 断点续传逻辑: 当客户端重新发起上传请求时,服务器需要能够检测到已有的分片进度,并告知客户端从哪里开始继续上传。
# 进度条
前端处理(前50%):
前端可以首先计算部分进度,这通常涉及到文件上传到服务器的部分。对于文件上传的部分进度,你可以使用一些流行的文件上传库(如Resumable.js
或Dropzone.js
)来获取上传进度。一旦前50%的进度计算完成,前端可以通过 WebSocket 连接将这一部分进度数据发送给后端。
后端处理(后50%):
后端接收到前50%的进度数据后,开始后续处理,比如文件的后续处理、分片的合并等。后端通过 WebSocket 将后50%的进度数据发送给前端。
WebSocket 连接:
通过 WebSocket,前后端可以建立双向通信的连接。在前50%进度计算完成后,前端通过 WebSocket 将这部分进度发送给后端,后端接收到数据后开始处理后50%的进度,并将进度信息发送回前端。
# 文件共享
文件支持数据集、训练集、测试集的文件共享,可自定义共享范围,控制数据访问
# 前端
项目名称:Easy云盘
项目描述: 一个仿百度云盘面向C端用户的网盘项目,包括用户注册,QQ快捷登录,文件上传,分片上传,断点续传,秒传,文件在线预览,包括文本,图片,视频,音频,excel,word ,pdf 等文件在线预览,文件分享等功能。
技术选型:Vue3 + vite3 + vuex
负责内容:
\1. 用户注册,登录,QQ快捷登录,发送邮箱验证码,找回密码。
\2. 文件分片上传,断点续传,秒传,上传进度展示,文件预览,新建目录,文件重命名,文件移动,文件分享,删除,下载 等功能。
\3. 文件分享列表,取消分享。
\4. 回收站功能,还原文件,彻底删除。
\5. 设置模块 1、超级管理员可以看到所有用户上传的文件,可以对文件下载,删除。 2、超级管理员可以对用户进行管理,给用户分配空间,禁用、启用用户3、超级管理员可以对系统进行设置,设置邮箱模板,设置用户注册初始化空间大小。
\6. 用户通过分享链接和分享码预览下载其他人分享的文件,同时也可以将文件保存到自己的网盘。
项目难点:
\1. 为了实现秒传,在文件上传前需要对文件进行MD5,避免文件过大,浏览器内存溢出,采用SparkMD5对文件分片MD5,文件分片上传,使用Promise 完成多文件同时上传,文件开始上传,暂停上传。
\2. 多级目录线性展示,结合路由,刷新后任保持目录层级。
\3. 文件预览,根据不同的文件类型,接受不同的数据类型,包括arraybuffer,blob,json类型的数据。
\4. 文件分享,用户打开链接后需要输入分享码才可以读取文件,输入后,在一个会话内状态一直保持,session会话结束后,需要再次输入分享码。
项目收获:
熟悉了第三方登录接入流程,比如QQ登录。云盘项目不是简单的增删改查,对大文件采用递归调用完成分片MD5处理,大文件分片上传,解决了实际项目开发中对大文件的上传处理。根据不同的文件类型,使用不同的插件完成文件的在线预览。比如视频的分片播放,pdf,excel,word 在线预览。通过这个项目让我对vue的相关知识点,比如数据双向绑定,组合式Api,生命周期函数,状态管理,路由,watch,父子组件,组件封装,表单校验,页面布局,css编写等有了更加深刻的认识,此项目将所学的前端知识进行了综合运用。
# 后端
项目名称:Easy云盘
项目描述: 一个仿百度云盘面向C端用户的网盘项目,包括用户注册,QQ快捷登录,文件上传,分片上传,断点续传,秒传,文件在线预览,包括文本,图片,视频,音频,excel,word ,pdf 等文件在线预览,文件分享等功能。
技术选型:Springboot + mybatis + mysql+redis+ffmpeg
负责内容:
\1. 用户注册,登录,QQ快捷登录,发送邮箱验证码,找回密码。
\2. 文件分片上传,秒传,新建目录,预览,文件重命名,文件移动,文件分享,删除,下载 等功能。
\3. 文件分享列表,取消分享。
\4. 回收站功能,还原文件,彻底删除。
\5. 设置模块 (1)超级管理员角色查询所有用户上传的文件,可以对文件下载,删除。 (2)超级管理员可以对用户进行管理,给用户分配空间,禁用、启用用户(3)超级管理员可以对系统进行设置,设置邮箱模板,设置用户注册初始化空间大小。
\6. 用户通过分享链接和分享码预览下载其他人分享的文件,同时也可以将文件保存到自己的网盘。
项目难点:
\1. 文件分片上传,通过文件MD5实现文件秒传,文件分片上传后,异步对文件进行合并处理,视频文件,调用ffmpeg生成视频缩略图,将文件分片成ts文件。
\2. 通过redis缓存实时计算用户上传过程中空间占用情况。
\3. 多级目录线性展示,通过递归查询,查询目录的所有父级目录。
\4. 用户上传文件,同一级目录重名文件自动重命名,文件移动,同名文件重命名。
项目收获:
熟悉了第三方登录接入流程,比如QQ登录。让我熟练使用Springboot,采用spring的aop 的注解方式 实现了不同的接口权限不一样,比如普通用户和超级管理员权限的区别,同时使用aop和java反射实现了后端参数校验。使用redis缓存了一些系统设置,用户上传过程中空间使用实时计算,避免反复查询数据库。项目中解决了如何实现异步调用事务的问题,解决循环依赖的问题,如何调用第三方插件比如ffmpeg来实现对文件的分片处理,合并处理。 云盘项目让我学习到如何从功能点去设计数据库,在数据库设计的时候考虑到后续的扩展,比如文件数据的分表处理,可以根据用户id hash 取模的方式对文件数据进行分表处理,在开发过程中,通过spring的核心 aop来实现与业务的解耦。 该项目让我对java,数据库 所学知识进行了综合运用,让我能够使用java从0开发一个完整的项目。