Skip to content

学完了微服务和 monorepo 后,我们做了这个考试系统项目。

整体是模仿问卷星的流程,从创建考试、考试编辑器、答卷、自动判分、到排行榜等,流程比较完整。

我们通过 monorepo 的方式组织项目:

4 个微服务都是 apps 下的单独目录。

然后公共模块放在 libs 下,通过 @app/xxx 在项目里引入。

每个微服务都是独立跑的:

npm run start:dev user
npm run start:dev exam
npm run start:dev answer
npm run start:dev analyse

之间通过 tcp 来实现通信:

比如 answer 微服务调用 exam 微服务:

在 exam 微服务暴露 tcp 端口:

answer 微服务连接上它:

在 exam 写一个消息处理函数:

在 answer 里调用它:

但这个项目的多个模块比较独立,最终也没用到 tcp 通信。

同样,rabbitmq 的异步通信也没用到。

实际上大多数 node 项目我觉得都没必要用微服务架构,拆分会带来通信的复杂度,不如单体架构简单。

回顾下我们做这个项目的整个过程:

首先我们做了需求分析,分析了下有哪些功能:

直接用的问卷星的页面作为原型图:

这一步主要是明确做什么。

然后我们设计了下技术方案,做了技术选型:

当然,因为微服务之间比较独立,最终没用到微服务之间的同步(基于 tcp)和异步(基于消息队列)通信。

数据库设计:

分析了下接口:

接下来进入开发:

首先实现了 monorepo 架构:

创建了 user、exam、answer、analyse 这 4 个 app,还有 redis 这个公共 lib。

4 个微服务都单独暴露 http 接口在不同端口,之间还可以通过 TCP 来做通信。

libs 下的模块可以在每个 app 里引入,可以放一些公共代码。

然后实现了用户微服务的登录、注册、修改密码的功能。

过程中又创建了 prisma、email 的 lib。

通过 prisma 的 migrate 功能,生成迁移 sql 并同步到数据库。

之前用 TypeORM 也要做数据库迁移,不过需要自己准备这些 sql:

从数据库迁移方面来说,prisma 确实方便很多。

然后实现了考试微服务的接口,包括考试列表、考试创建、考试删除、发布考试、保存试卷内容的接口。

具体的试卷内容是用 JSON 存储的。

我们做了一个简单的低代码编辑器:

2024-08-27 20.33.03.gif

可以拖拽题型到画布区,然后在右侧编辑:

2024-08-26 23.08.21.gif

每个题目都设置分值、答案、答案解析

最终生成一个 json,把这个 json 保存到数据库。

其实一般的低代码编辑器都是编辑器拖拽,服务端存 JSON。

之后实现了这答卷微服务,包括创建答卷、答卷列表、答卷详情接口,导出答卷列表 excel 等接口。

然后实现了答题页面:

编辑完考试可以生成链接,打开链接答题后就会保存提交的答案。

渲染试卷 json 的逻辑和预览时一样。

表单 onChange 的时候修改 answers 状态,当点击提交的时候调用接口保存答卷。

这样从新建考试,编辑试卷,到答题提交答案的流程就完成了。

2024-08-27 20.37.29.gif

答题结果也是用 json 保存的。

格式是这样:

json
[
    {
        id: 1,
        answer: 'xxx'
    },
    {
        id: 2,
        answer: 'yyy'
    }
]

我们通过和试卷 json 的答案对比,实现了分数的计算:

之后又实现了排行榜。

排行榜的功能基于 redis 的 zset 实现,用 zadd 往其中添加元素,用 zrang 取排好序的前多少个元素,加上 REV 就是按照分数从大到小排序。

然后加了一个弹窗来展示排行榜。

2024-08-27 20.02.19.gif

这样,整个流程的功能就开发完了。

这个项目主要是熟悉了 monorepo 的架构,并且知道了低代码编辑器的存储方案。

如果你要开发微服务项目,也是基于这种 monorepo 的项目结构来开发。

整个项目流程都是对标问卷星来的,虽然有些简化,但功能是一样的。

不过这个项目比较简单,没用到微服务之间的通信。

实际上,单体架构的 node 项目占绝大多数,一般没必要用微服务的方式写,只会增加项目的复杂度。