本科毕业设计回顾

Review of undergraduate graduation project

Posted by Echo on November 17, 2019

Keep thinking

写在前面

大学毕业已经五个多月了,读研之后感觉到周围同学的学习都极其努力,每天的学习时间远远不止 996,好的学习环境促进进步。近期任务不多,对以往的项目都做了一些更新总结,修复了以往没解决的一些 bug,同时更新了项目文档。

本科毕业设计的课题是“基于 Web 的 IT 教育课程考评系统的设计与实现”。名字很土,普遍来讲“XX系统”就给人一种似乎很简单的感觉。的确,这个系统做起来确实不算难,主要是大二参加服务外包创新创业大赛做的类似的课题没得奖,心里一直觉得是一个遗憾,当时那个系统是用 AngularSSM 写的,后端是 MySQL。这次毕业设计进行了重写,前端 Angular 7 (大二暑假的时候用的还是 Angular 2,做毕设的时候都更新到 7 了,今天又看了下,已经到第8个版本了…不得不说前端的迭代速度真的很快),后端 NodeJS + Express ,数据库 MongoDB,整体采用了 NgAlain 脚手架,加快项目构建。整个项目采用了全栈轻量级技术,考评系统本身是一个IO密集型的系统,对高并发有着较高的要求,基于此进行了技术选型。

项目地址:

开发环境

前端开发环境:

项目 内容
操作系统 Windows 10 Education 1803
开发集成环境 VSCode 1.33、Sublime Text 3.2
使用语言 TypeScript、HTML、Less
版本控制 Git 2.20.1.windows.1
服务器类型 Nodejs 10.15.3
服务器URL http://localhost:4200

后端开发环境:

项目 内容
操作系统 Windows 10 Education 1803
开发集成环境 VSCode 1.33
使用语言 JavaScript
接口测试 Postman 7.07
数据库 MongoDB 4.0.8
依赖管理 NPM 6.4.1
版本控制 Git 2.20.1.windows.1
服务器类型 Nodejs 10.15.3
服务器URL http://localhost:3000

经验总结

适量转移后端的数据处理和逻辑操作至前端

在传统的软件开发过程中,人们习惯于将几乎所有数据处理和逻辑操作放在后台,前端仅仅对数据做一个简单的显示,除了对数组进行迭代这种循环操作之外,几乎所有数据处理都放在了后台。

对于以往个人计算机并不普及、性能并不高的情况采用这种方法固然没有错,但是现如今个人计算机的性能已经十分优异了,并且当今浏览器的JS渲染引擎的(例如谷歌 V8 引擎)在经过了这么多年的优化和发展,已经成为一个性能十分优越的高速引擎,在执行页面上行为代码(即 JavaScript 代码)时,已经可以做到极速响应,完全可以用来实现一些本应该在后台实现的数据处理和逻辑操作。并且在如今这个海量数据时代,数据库中所存储的数据量是极其庞大的,在进行 CRUD 时同样给服务器造成巨大的压力,对于一般要求的系统还好,但对于考试系统这种对并发要求特别高的系统来说,一旦瞬时访问量上来,无疑将带给服务器毁灭性的打击,轻者服务异常,重者宕机。

我们完全可以将适量的数据处理以及逻辑操作转移至前台,让高速的JS渲染引擎去处理,这样不但可以降低服务器的压力,还可以变相提高响应速度,提高用户体验,因为这种前端的资源开销对于如今的用户硬件条件以及高速引擎来说是十分小的。而升级服务器硬件的代价是十分高昂的,因此在设计这个系统的时候才会采用全栈轻量的思路,尽量通过优化软件的方式提高性能。

NoSQL数据库对聚合操作并不友好

在设计本系统之初,本想通过 MongoDB 来优化数据库查询操作,利用其轻量快速的特点支持本系统的性能。

但在实际使用时发现 MongoDB 在设计之初对于聚合操作并不友好。例如:对于关系型数据库,对表进行连接操作时,只需要通过SQL语句的JOIN关键字即可实现,而在 MongoDB 中必须通过 $lookup 来实现,而 $lookup 其实是在 MongoDB 3.2 版本中才出现,这意味着 NoSQL 的设计初衷是对于单文档记录进行原子操作,而不是对多个表进行形形色色的连接。

其实对于表连接来说还有个替代操作,即通过一次内嵌的查询实现(例如:先查表 A,查完后用满足条件的表 A 中的记录去查表 B),但是 Nodejs 中的数据库查询是异步操作,那么对于仅仅两个表的简单查询还尚能接受,但一旦涉及多个表,性能直接跌破谷底,因为这么做每次查表都会新建一次数据库连接,接着再关闭数据库连接,不断开启关闭。并且每一次异步都涉及到回调函数,一次次控制转移、参数传递再回调,综合以上几点会对软件性能造成很大伤害,十分不提倡此种行为。

那么回过头来看这个系统,如果聚合操作十分多的话,是否应该重新考虑数据库的选用,而不是仅仅把眼光局限在性能优化上,也要考虑数据库本身的特点,是否适合该系统。

部分页面请求在涉及到CORD跨域时,发送预检请求的问题

跨域问题是老生常谈的问题。在实现上传头像功能时,一直上传失败。经检查,前调用的API与后台接口地址相吻合。参数格式也相一致,但就是无法成功上传。

Debug时发现,前台请求已经发送到后台,但是 Request Method 显示为 OPTION 方式,这种请求方式很少出现,一般只用于在涉及到CORD跨域问题时,前台发送预检请求需要用到这种请求方式。解决方法并不难,只需要在后台的允许跨域代码中,放行OPTION请求,且在收到OPTION格式请求时,直接响应 Status Code 200 OK 即可。

JavaScript循环代码中含有异步操作的问题

众所周知,JavaScript 是不支持多线程的,在涉及到类似操作时只能用异步去替代,因此JS循环中出现异步问题也很常见。

在使用 JavaScript 连接数据库进行操作时,依然是异步的。本系统的考试记录详情页面,在进入时需要初始化这个组件,需要判断每个试题是否已被收藏,如果未收藏,试题左边的五角星需要显示为无色空心,否则需要显示为有色实心。而在后台检验每个试题时,使用 for 循环,在每次循环中去查询数据库,最终输出到控制台上发现每次的结果都可能不一样。

因为查询数据库在此处是以异步方式实现的,所以实际执行的数据库查询操作,与当前循环次数i并不是一一对应的关系,甚至有可能循环体中的 i 已经累加到最大值了,而数据库查询操作还没有开始。最终采用了 JavaScript 中的闭包去完成操作,因为闭包中的变量是局部作用域且不会收到外部变量的影响,在 for 循环中的变量i传值进入闭包中时,循环体中的 i 与闭包中的i就不再有关系了,二者互不影响,以此来保证每次数据库查询的 i 值不变且正确。

NG-ALAIN脚手架问题

监听动态表单改变的函数中,若对表单本身进行改变并刷新表单组件,则会陷入无限回调,造成浏览器崩溃。经官方群的开发者们讨论,一致认为这是框架本身的问题,现已发布该 ISSUE至Github。

当前考试组件无缘由地初始化两次的问题

当前考试组件在初始化时会判断 localStorage 中是否含有正在进行的考试,如果有会继续判断考试类型并开始准备生成考卷;如果没有则会弹出“当前无考试”的提示。

在测试时发现,若无当前考试,每次进入当前考试组件时,该组件都会初始化两次,即弹出两次提示。当时尝试过以原生JS中的 window.location 函数在第一次初始化后强行跳转回来,但这么做会破坏本地数据和相关逻辑,遂弃用。查阅 Angular 英文原文档,文档中写道如果子组件出现错误,那么父组件可能会强制初始化两次,经检验当前考试组件并无错误。因此该问题最终没有得到解决,显示给用户的是弹窗框可能跳出两次。

时间安排问题严重

在考研复试结束之后已经到了四月份,在短短的一个月内做出一个较为完备的系统,压力是极大的。最主要的是本系统所选用的技术都不是我以前做项目所涉及的,基本都是现学现卖。以前学过 Angular 2.0 ,但现在已经更新到了第七个版本,与当初相比已经面目全非。同样,NodejsExpress 也都是在边学边用,甚至连 MongoDB 数据库的增删改查都是刚刚学会的,因为非关系型数据库和关系型数据库有着本质的不同,查询方式以及查询语句也完全不一样,都需要花时间学习。于是我在一个月内,学习本系统相关的全栈技术,并完成系统,时间极度紧张,造成系统本身也有许多小的瑕疵来不及纠正。反思设计之初,在技术选型时选用以前用过的,比如 SSMPHP 等,便可以短时间内完成项目了。

致谢

感谢紫金学院14级翁沈顺学长。可以说没有顺哥就没有今天的我。自大一加入工作室便一直在顺哥的严格要求之下学习技术(大学四年老师都没怎么说过我,被顺哥训倒是家常便饭 0.0 )。对于我不懂的问题他也不厌其烦地教我。自加入服务外包组起,便经常被顺哥恨铁不成钢地训,一直到从顺哥手中接手服务外包组。翁沈顺让我明白对待技术学习应该持有一个怎样的态度,无论是过去、现在,还是将来的学习中,我都会将这种态度一直贯彻下去。

感谢紫金学院计算机学院朱俊老师。可以说我计算机技术的入门就是在俊姐的指导下完成的。当初在大一时学个C语言怎么都学不会,一度心灰意冷。一直到后来学习C++时,俊姐通过形象的例子去描述编程语言的各种特性,才好不容易把我拉回正道,也拉进了计算机行业的大门。

感谢紫金学院IT工作室。是工作室的氛围和学习环境造就了今天的我,在工作室中,我认识了许多优秀的同学,也在这些优秀同学的耳濡目染之下,或多或少地提高对自己的要求。大一的天梯赛,不断的训练让我编程能力不断凝实,大二的各种比赛让我体会到了赢得比赛的喜悦,大三的各种项目又让我感受到通过自己能力完成一些系统所带来的喜悦感。加入IT工作室是我大学以来所做的最正确的决定之一。也希望IT工作室能够越来越好,培养出更多优秀的同学出来。

感谢计算机行业的前人们。为我们提供了一个如此好的学习环境。可以说互联网上最多的资料,就是计算机行业的资料;最多的课程,就是计算机领域的课程;并且许多能人异士都将自己写出的优秀代码开源在了Github、码云等平台上供大家学习,正是有了前人的帮助,站在前人的肩膀上,我才能完成本系统。我也希望能够为计算机行业献出我的力量,共同推动计算机行业走向更加繁荣。

结束语

我是一个运气很好的人,遇到的人也多是好人。

同时也祝愿你将来的日子无比幸运 o(^▽^)o

明日之我,胸中有丘壑,立马镇山河。