2021山东大学项目实训-李钰骢的开发记录
后端开发总体计划
本次项目的后端部分搭建,以数据库为基础,以API服务为导向,搭建一个围绕微服务展开的、可拓展的、分布式的后端及数据库。
我的任务
根据整体规划,我主要负责整个后端的架构设计,包括使用到的技术、架构的规范、使用到的思想等等。详细部分我负责所有Controller层,博客系统,分布式系统设计,文件系统,登录注册系统,项目团队工作情况反馈报告,高并发高可用设计以及面向用户的具有可读性的Log系统。
详细设计如下:
采用MVC思想,整个微服务分为Controller,Service和DAO,分别是接口层,微服务层,操作数据库层。在DAO层我们采用jpa的方式,即ORM的模式进行,为的是保证后期进行拓展时可以以最小的代价进行,但这样解耦带来的坏处仅仅只是代码上的增加。需要增加entity、repository两个层次。
在Controller层中,除了testController用作测试、loginController用作登录注册不进行拦截之外,都需要进行登录信息验证,否则无法访问资源。同时我们加入了swagger用作接口文档的形成,因此需要加入Configure对这些引入的包进行配置。
在单元测试中,采用SpringBootTest和SpringJUnit4ClassRunner进行测试,保证用最少消耗和最快速的方式进行最高效的测试。
在性能方面,我们采用Redis作为缓存,同时为后期的分布式设计提供分布式锁的可能性,同时也用Redis作数据库缓存,保证用户的请求不会在短时间内击垮MySQL数据库。同时引入消息队列,在有可能存在请求峰值的地方部署消息中间件的方式来实现削峰的效果。
在数据一致性方面,我们采用SpringBoot的Transactional来保证整个微服务中对数据库的连续操作具有原子性,以及在MYSQL中开启了可重复读的隔离级别,保证数据的正常。在缓存和数据库一致性的问题中,每当发生修改和删除等操作,都会把Redis中的缓存设置为失效,并且在下一次请求中需要重新存入。
在高并发高可用方面,我们采用线程池的方式处理请求。一共有四个线程池,登录注册线程池,文件线程池,项目线程池,博客线程池。
在后端的性能方面,加入了一条console线程,主要是用来在后台运行过程中,动态地获取和修改目前项目的运行配置,可以实现不停机的方式去适应场景的变化。
1、六大模块
项目信息系统
项目信息系统主要是负责对项目信息的设置。比如邀请某位用户加入,获取项目成员列表,修改项目信息等等。项目信息系统是我们的基础,基于项目信息,开发出博客系统、阶段-任务系统、文件空间系统等。
文件系统
文件空间(FileSpace)主要负责项目的文件持久化。文件有两类,一类是公共属性的文件,一类是归属于某个阶段的文件。在项目的文件信息表中,对这两类的区分在于数据表中file_activity属性。
文件空间会记录文件的创建者和创建时间,最后更新者和更新时间,同时也会为每个文件分配唯一的file_id,用于后端进行校验和操作。
博客系统
博客系统主要是负责对成员博客的持久化,同时加入了权限机制。整体思路上和文件系统有些像,但是博客系统仅允许单一格式文件,并且会对文件做处理。同时博客有三种情况,对于其他项目成员有可读可写、只读、不可读写三种状态,用于各种场景下的博客使用。
阶段-任务系统
阶段任务系统是开发的核心功能。阶段可以理解为是一张有向图,我们能保证图不会成环。同时每个图节点下有一系列的任务,隶属于这一阶段。项目成员可以把某个文件的归属设置为某一阶段。对于任务,则有指定的负责人和完成时间,我们会通过整体的完成时间计算出当前整个项目所有阶段的完成时间,并反馈给用户。
用户信息系统
用户信息系统主要是用于反馈项目邀请,和对用户信息的修改。当一个项目被创建时,创建者可以通过用户信息系统向某位用户发送邀请,允许其加入项目。受邀者则通过用户信息系统获取受邀信息,并进行反馈。
登录-注册系统
登录注册系统主要负责对用户的身份验证和权限的识别。登录注册系统的URI不会被拦截器拦截,可以自由访问,同时登录注册独享一个定长线程池,用于防止大量无效请求。我们也实现了邮箱验证模式,但是暂时未部署到应用中。
在登录注册系统中,用户的邮箱和手机号具有唯一性,也就是说两者都不能重复。
2、完成数据库设计
2.1 项目初步概念ER图
2.2 实体关系图
本次项目设计三种用户类型(学生、老师、管理员),以及一个可操作的实体(项目)。
早期考虑是面向用户对象,最后的实现是通过ORM的模式操作信息。采用SpringBoot提供的jpa进行开发具有更好的拓展性,相当于在数据库和DAO层之间进行解耦,允许根据部署的方式、特定的场景选择中间件,在拓展和应用上具有更高的灵活性。
2.3 学生信息的数据表
学生用户信息表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
UID | varchar | 32 | 否 | 主码 | |
UName | varchar | 32 | 否 | 用户姓名 | |
USex | Int | 1 | 否 | 0为女性.1为男性.2为未知 | |
UCollege | varchar | 32 | 否 | 大学 | |
UGrade | varchar | 32 | 否 | 年级 | |
UEmail | varchar | 256 | 否 | 邮箱 | |
UNumber | varchar | 16 | 否 | 手机号.考虑外国的 |
用户信息表保留可拓展性。
学生用户密码表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
UID | varchar | 32 | 否 | 主码 | |
UPassword | varchar | 32 | 否 | 用户口令.非明文 |
UID是唯一一个不能暴露给用户的ID。通过UID,可以获取到对应的密码。对用户的密码进行独立设表进行维护。将密码与基本信息区分开可以从根本上防止密码外泄,同时可以有效地区分开对密码进行改查操作和其他操作。
学生在线用户表
早期考虑到如何存储在线用户信息。后来采用Redis进行有效期限的临时存储,具有更好的分布式的部署方式,同时也能减少对数据库的操作。
2.4 项目信息类型的数据表
项目信息表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
PID | varchar | 32 | 否 | 主码 | |
PName | varchar | 32 | 否 | 项目名称 | |
PInstruction | varchar | 256 | 否 | 项目描述 | |
PCreateTime | dateTime | 否 | 项目创建时间 | ||
PDirector | varchar | 32 | 否 | 项目负责人UID |
项目成员表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
PID | varchar | 32 | 否 | 主码 | |
PMember | 否 | 项目成员 |
2.5 文件系统的数据表
文件信息表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
FID | varchar | 32 | 否 | 主码 | |
FName | varchar | 32 | 否 | 文件真实名称 | |
PID | varchar | 32 | 否 | 文件隶属项目的PID | |
file_path | varchar | 32 | 否 | 文件路径 | |
file_createTime | dateTime | 否 | 文件创建时间 | ||
file_editTime | dateTime | 否 | 文件修改时间 | ||
file_creator | varchar | 否 | 文件创建者 | ||
file_editor | varchar | 否 | 文件修改者 |
文件夹信息表
文件目录表
2.6 任务系统的数据表
任务信息表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
TID | String | 32 | 否 | 主码 | |
PID | String | 32 | 否 | 所属项目PID | |
TName | String | 32 | 否 | 任务名 | |
TInstruction | String | 32 | 否 | 任务描述 | |
TCreateTime | 否 | 任务创建时间 | |||
TEndTime | 否 | 任务截止时间 | |||
TDirector | String | 32 | 否 | 任务负责人UID | |
TIdentifier | String | 32 | 否 | 任务标识符 |
任务附件表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
TID | String | 32 | 否 | 主码 | |
FID | String | 32 | 是 | 附件为共享文件空间内的文件时 | |
LFID | String | 32 | 是 | 附件为新上传的本地文件时 |
任务关系表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
TID | String | 32 | 否 | 主码 | |
PID | String | 32 | 否 | 所属项目PID | |
ParentTID | String | 32 | 是 | 父节点的TID |
2.7 博客系统的数据表
博客信息表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
BFID | varchar | 32 | 否 | 主码 | |
PID | varchar | 32 | 否 | 所属项目PID | |
UID | varchar | 32 | 否 | 创建者 | |
BCreateTime | dateTime | 否 | 博客创建时间 | ||
BEditTime | dateTime | 否 | 博客最近修改时间 |
博客权限表
列名 | 数据类型 | 长度 | 小数位数 | 是否可为NULL | 说明 |
---|---|---|---|---|---|
BFID | varchar | 32 | 否 | 主码 | |
Authority | varchar | 32 | 否 | 被赋予读权限的UID |
不在此表中默认为可读不可写,在此表中,0为上锁,即其他人不可读不可写。1为其他人可读可写。
3、后端服务的接口及设计
3.1 登录系统
登录系统是在最终部署时唯一不进行拦截的URI。我通过登录请求时的request中包含的Session和请求登录的email信息作为key-value值存入redis中,以后对于该用户的其他资源请求都只需要携带Session即可正常访问。
3.2 博客系统
3.3 部分项目信息系统
3.4 部分用户信息系统
3.5 文件系统
文件系统的构建分为数据库表和文件存储空间。
数据库表存储项目的文件系统结构及其路径,文件存储空间则负责将后端接收到的文件进行重命名,并存放在存储空间内。
具体流程如图:
当用户在前端查看文件目录时,后端仅需将文件系统结构路径发给前端。当需要下载时,则查表找到对应的文件,并改名后发回给前端。
工作日志
2021-3-28
花费了两周时间,到目前为止对数据库的初步设计是已经完成了。接下来就是以SSM框架为基础,展开DAO层的编写工作。对于一些必要的功能,甚至要提供DTO层。在数据库设计的设想过程中,已经考虑到文件系统和任务系统未来实现的很多种方式,基本上都是在围绕性能和复杂度进行权衡,对于没有过多项目经验的我们来说是一次积累经验的机会。
在对文件系统的构造方式的探索中,我们小组尝试了很多种方案,但是大部分的效果都不太好。一个过于冗杂耦合的资源管理系统肯定是有很多弊端的,因此首要考虑应该是从功能角度出发,将查询与上传下载的功能分开,分开进行会让性能和效率都会有所提升。因此创造性地提出了将文件统一存储,用唯一标识符进行区分,文件路径仅仅体现在数据库的表中这样一个方案。
任务系统的设计在后续发展过程中会产生很多种方案。因此我们的首要设计原则应该是抓住任务系统最核心的功能进行开发 ,同时保留相当大的可拓展性,保证在未来迭代中出现新的特征时可以在不影响原有系统的情况下进行增量。同时在对核心任务系统的功能进行数据库设计时,我们发现它也需要特殊的数据结构来保证可拓展性,在前后端的交互上要考虑很多问题。
对于通知系统则有所保留。目前因为通知系统的准确规则尚未明确,因此通知系统应能实现特殊条件触发和自定义通知两种形式,保证在产品迭代过程中保有选择。
总体上看,接下来可以进入到DAO层的设计了。DAO层的存在是为了把Service层和数据库进行解耦,使其中某一方改变了规则而不会影响到另一方,因此这一层的设计必须十分系统客观。
2021-4-4
清明放假。本周主要内容是找一个后端云服务器,能够实现公网映射,这样前端才能访问到我们提供的接口。
跟老师进行交涉后,我们决定自行寻找服务器解决办法。我们同时对比了阿里云、腾讯云、华为云和京东云,从市场占有率上来说是阿里云>腾讯云>华为云>京东云。阿里云早期主要服务于自家的淘宝天猫等电商应用,在高并发高可用方面的技术和算法应该是最成熟的。腾讯云主要被用在政务云方面,跟几大银行都有云合作,可以说是最稳定安全的。华为云和京东云则在各自的领域都有突出特点,但是可能并没有阿里和腾讯云这样综合和全面。
经过小组讨论,我们最后决定采用腾讯的云服务器。部署服务器花费了相当长的时间,如何在WindowsServer2016中设置防火墙,如何设置安全组策略,如何部署我们的数据库……
这周所学十分的多。第一次涉及到了服务器方面的部署和应用问题,学到了很多应对高并发的技术和解决办法。也基本上实现了我们后端在公网的可见性。
2021-4-11
第一个官网完成了。我首先在本地利用IDEA进行SpringBoot开发,并在本地进行调试,所有一切都正常后就使用maven生命周期中的install功能(deploy功能可以直接部署到私服务器,以后可以试一试)打成war包,然后传到服务器上,在cmd界面输入java -jar 包名,即可完成部署,启动服务器。在客户端中尝试着用浏览器访问也成功了,美中不足是没有一个域名,在浏览器界面中裸着的IP和端口显得有些丑。
确定我的项目可以在本地调试,打包,和远程部署后,代码的coding工作就可以全面铺开了。
2021-4-18
本周主要完成了系统的登录注册。我负责这一模块的高并发处理和API实现,同时还有系统的架构。对于高并发问题,有服务器-服务器的解决方案,也有服务器之中的解决方案。因为经费紧张,只能考虑单个服务器内处理高并发。
服务器内的高并发主要通过线程池来解决。考虑到登录注册模块最容易受到大量的匿名攻击,我决定采用定长线程池,并让每个登录注册线程的执行时间尽可能短——这意味着以最少的操作完成整个功能。服务器提供缓存功能,要考虑到登陆注册模块的缓存被击穿后,是否会继续击穿数据库,导致整个数据库宕机。因此应该做一个隔离,在缓存溢出后,所有额外的请求都被抛弃,不允许额外的访问数据库的通道,直到缓存内的被处理或者超时空出新的空间。
登录注册模块的设计,是直接设计一个模块线程启动,相当于开辟了一块空间给登录注册功能,这个空间的大小就是线程池的大小。我对于登录注册线程池大小的考虑有几点:1、核心线程数能够保证流量低峰期使用。2、从流量低峰期到高压期应该是有一个上升的时间段,核心线程数被用完的时间应当落在这个时间段里,被用完后就采用创建新的线程直到达到最大线程数。3、超过最大线程数的新流量一律被抛弃(没有等待队列),需要重新申请。
2021-4-25
本周主要完成对接口层进行优化。主要是实现了拦截器,一切未能在Redis中找到对应登录信息的请求,都会被拦截器拦截并转发到登录注册页面。因此/login的请求都不会被拦截。同时拦截器会放行/test/**类型的接口,主要是为团队成员在开发过程中测试提供便利。
同时我加入了swagger库,主要是用于快速渲染出我们目前所有提供接口的接口文档,使我们和前端同学在开发中进行测试更加方便,同时也提供了一种十分直观易懂的web来展现我们的接口。swagger的使用十分简单,但是在具体操作则需要很多的注释来完成对接口信息的书写。
经过这周的整体性调整,我们的项目开发更加规范化和可测试,在团队中我们也通过JetBrains提供的Space功能进行团队协作,包括部署代码、发布任务、团队交流等等,基本上可以实现一种高效开发的方式。
2021-5-2
五一放假
2021-5-9
本周我的任务是完成文件系统(FileSpace)。文件系统的主要问题在于如何规范化文件的持久化和信息的收集。
首先是数据库中我们要收集的数据,比如创建者和创建时间,最近更新者和更新时间,等等。文件的传输在SpringBoot中提供了一个叫做MultipartFile的对象来接收和处理,为我的工作提供了便利的操作方式。但是在文件的下载和文件的存储却要把文件对象转换为javaIO的File对象来进行,以及在下载过程中对HTTPServletResponse的Header的设置来传输中文名文件,还有设置为流的传输模式等等,可以说文件系统的设计在知识面上需要相当的广度。
2021-5-16
本周主要完成博客系统(BlogService),具体的就是对博客的新增、更新、删除、下载和权限设置。
因为前几周的开发经验,我在对请求者的权限认证方面有一定的经验,以及由于博客系统和文件系统在逻辑上有异曲同工之处,所以开发是十分顺利。博客系统关注的是文件本身,而不需要文件的后缀,同时博客系统需要提供权限认证,以及对后续做出的团队工作情况报告做一个反馈。因此在开发上难度较小。但整体上仍具有挑战性。
2021-5-23
本周主要完成项目的团队工作情况报告word文档的设计以及信息搜集功能。
这一报告的生成基于Log。因此首先要设计一个面向用户端的具有可读性的Log信息收集系统。LogService就是这样一个服务,主要是用于在发生了insert、update、delete的数据库操作的地方进行log记录,并持久化在数据库中,最后呈现在团队工作情况报告中,以及为前端展示提供数据。
团队情况报告word的生成采用了Poi-tl Documentation,一个便捷生成word文档的库,可以根据模板docx生成相应地word文件。因此一个重要的问题在于如何收集信息。我们的表在对象上具有较大的解耦,表和表之间有很多联系,因此在呈现出具有可读性的关键数据的过程中需要多个表进行结合查询,需要撰写较为复杂的SQL语句。
因此这一部分的关键部分在于信息的收集和呈现。
2021-5-30
我的整体开发流程基本上开发完毕了。单元测试在每个功能的开发中都贯穿着,目前剩下系统测试。前端同学已经拿到我的接口,我们对接口的返回信息和异常进行了规范化,使如果在请求资源过程中出现问题都能以最简单易懂的方式进行呈现。
在测试过程中也出现了问题,一些在单元测试的过程中不曾发现的bug。因为我们的代码规范和异常规范比较清晰,所以在解决bug的过程中基本上没有走弯路。同时存在少数语法问题也被发现,这些问题往往是由于误操作或误触导致的,也得到了解决。基本上开发流程接近尾声,系统的可用性得到了保证。