-
-
Notifications
You must be signed in to change notification settings - Fork 2
详细设计文档
2021年5月
实现一个在线编程笔试平台项目
前端需要完成的静态页面有:
(1) 首页欢迎界面;
(2) 登录/注册页面;
(3) 候选人/面试官面试界面(包括实时聊天界面、代码编辑器界面以及题目发送/接收界面);
(4) 发起/接收面试界面;
(5) 数据库题库界面;
(6) 题库修改/添加界面;
(7) 个人信息(如密码)修改界面;
(8) 其他功能界面(如设置界面,用于清除localstorage)
前端需要完成的主要功能模块有:
(1) 登录注册与注销功能:可以实现注册、登录与注销;同时实现持久化登录。
(2) 实时聊天界面以及消息发送与接收功能:实现消息的发送与接收,发出以及接收的消息页及时存储在远程数据库以及本地。
(3) 个人信息的配置功能:可以配置个人信息,由于不需要太多的信息,这里只设计了修改密码。
(4) 面试发起/接收功能:可以在面试发起/接收界面,面试官/候选人可以发起/(接收或拒绝)面试邀请。
(5) 题库功能:可以通过该界面增删改查数据库保存的题库中的题目;
(6) 实时聊天功能:实现实时聊天页面,用户能够通过访问数据库并同步到本地存储获取该面试聊天信息,候选人和面试官可实时文字聊天,其他获得链接的人都可参与文字聊天;当有人员进入/离开面试页面时,会自动发出提醒;同时记录聊天人数;
(7) 在线编程功能:提供代码编辑器monaco-editor,实现了切换语言和切换编辑器风格;编辑器支持代码补全、高亮等基本功能;同时支持候选人/面试官协同编辑。
(8) 题目发送接收功能: 提供富文本编辑器Tinymce,支持富文本格式编写和显示;
(9) 代码提交及代码运行功能:在线编程模块支持运行Javascript代码,候选人和面试官可在线运行代码并查看输出。
实时聊天、代码同步以及题目发送采用websocket实现,后台需要提供的通讯板块需要实现的功能包括但不限于:
(1) 能够生成并辨别不同的面试id,根据面试id将发送方的信息发送到获得面试链接并进入面试界面的用户;
(2) 为了能够使得代码同步及时,需要将发送方发送给接收方的消息进行及时转发。
(3) 统计每个面试链接实时聊天的人数并发送到对应的接收方;
(4) 后台根据每个面试的hashid存储进入该面试的用户websocket连接,每当一个用户发送消息时,根据面试的hashid对应的连接分发给其他也在该面试中的用户。
面试id生成(用于拼接面试url)
若面试id直接使用数据库递增的id,与“获得面试链接的人即可参与聊天”需求结合会产生安全风险(意味着可以随机进入其它面试来聊天)。因此需要一种新的面试id方案,需要满足以下特点:
- 唯一。不同面试要有不同的id。
- 简短。面试id要用在url中用于分享,不能太长。
- 不可枚举。无法通过简单的规律遍历所有id。
- 生成算法简单通用,易于实现。
使用hashids,对面试id进行重构,重构后的hashid满足上述要求,通过相关接口返回到前端。
后台websocket服务器根据面试id分发前端面试题目,编辑器和实时聊天的信息
后台根据每个面试的hashid存储进入该面试的用户websocket连接,每当一个用户发送消息时,根据面试的hashid对应的连接分发给其他也在该面试中的用户。
具体逻辑如下:
面试id字典 interviewId => array of {connect, uid}
连接结构体
let conObj = {
uid: obj.uid,
con: connect,
};
用户进入面试链接,发送type为open的含有面试hashid和用户id的jsonstr,查找是否已有这个hashid的websocket连接,
- 有则判断该数组中是否存在某个连接结构体,其用户id和该信息的用户id相同,有则重复连接,打印错误信息,无则
push
一个包含用户id和当前连接的一个结构体到该数组; - 无则,赋值一个数组,数组的0号元素是
conObj
如果是type为close的消息,则删除hashid对应的数组中该用户的conObj
。
如果是普通消息,则遍历hashid对应的conObj数组,给每一个连接发送消息,即广播给其他也在该面试中的用户。
(1) 后端接口采用基于JWT的Token认证机制实现
(2) 使用 express-jwt 和 json web token
(3) 对于前端发起的请求(除用户登录等少量接口不鉴权外),如果请求头headers里的authorization没有token,则不予返回数据(token可由登录接口获取)
(4) token 使用 id 和 email 生成
参考github文档中后端关于分页的实现;
前端发起请求:
$ curl 'https://api.github.com/user/repos?page=2&per_page=100'
后端在返回相关数据的同时,在响应头中包含1个参数Total-Count
,表示总记录数,方便前端进行上下页、第一和最后一页的跳转
数据库使用mysql :
(1) user用户表,存储用户id,邮箱email,密码password和昵称name
(2) problem题目表,存储标题,内容,题目创建者id,创建时间和修改时间
(3) interview面试表,存储面试官id,面试者id,开始时间,结束时间和面试状态
(4) answer答案表,存储面试id,问题id,语言,内容,创建时间
(5) comment留言表,存储留言内容,面试id,留言者id,留言时间
关系图(由MYSQL Workbench生成)如下:
接口文档 :https://app.swaggerhub.com/apis-docs/tootal/codeview/1.0.0#/
根据接口文档,实现用户、题目、面试、答案、留言模块的接口(包含所有模块的增删查改的接口) 后端使用express,使用router实现路由
(1) 后台接口单元测试使用:
(2) mocha作为测试主体框架,其中supertest负责http请求,should负责断言,判断各种请求的返回结果是否符合实际情况,从而测试接口。
(3) 接口已全部通过测试。
(1) 使用pm2,用于启动、监控服务器;
(2) 后台代码修改后,原先需要手动关闭后台服务器,然后再重启;
(3) 使用pm2监听后,修改后会自动重启,方便快捷。
前端框架:Vue脚手架+element-plus
组件库;vue-cli初始化工程,基于vue3
代码编辑器:使用Monaco Editor
本地存储:localstorage
后端框架:express框架
通讯服务:websocket
数据库存储:mysql
后端接口单元测试:http测试SuperTest;单元测试框架Mocha;断言库Should;
代码管理:github协作开发
下图是整个项目运行的一个流程,用于展示其各个页面之间的联系以及页面本身所具有的功能:
(由MYSQL Workbench生成)
属性 | 类型 | 属性描述 | 备注 |
---|---|---|---|
id | INT(11) | 存储用户id | 主键 |
VARCHAR(100) | 邮箱email | ||
password | VARCHAR(20) | 密码password6. |
属性 | 类型 | 属性描述 | 备注 |
---|---|---|---|
id | INT(11) | 题目id | 主键 |
title | VARCHAR(100) | 存储标题 | |
content | TEXT | 内容 | |
owner_id | INT(11) | 题目创建者id | 外键 |
created_at | DATATIME | 创建时间 | |
updated_at | DATATIME | 修改时间 |
属性 | 类型 | 属性描述 | 备注 |
---|---|---|---|
id | INT(11) | 答案id | 主键 |
interview_id | INT(11) | 存储面试id | 外键 |
problem_id | INT(11) | 问题id | 外键 |
language | ENUM(...) | 语言 | |
content | TEXT | 内容 | |
created_at | DATATIME | 创建时间 |
属性 | 属性类型 | 属性描述 | 备注 |
---|---|---|---|
id | INT(11) | 面试记录编号ID | 主键 |
viewer_id | INT(11) | 面试官账号ID | 外键 |
viewee_id | INT(11) | 候选人账号ID | 外键 |
start_time | DATETIME | 面试开始时间 | |
finish_time | DATETIME | 面试结束时间 | |
status | ENUM | 面试状态 |
属性 | 属性类型 | 属性描述 | 备注 |
---|---|---|---|
id | INT(11) | 留言记录ID | 主键 |
content | TEXT | 留言内容 | |
interview_id | INT(11) | 面试ID | 外键 |
owner_id | INT(11) | 留言者id | 外键 |
created_at | DATETIME | 留言时间 |
属性 | 属性类型 | 属性描述 | 备注 |
---|---|---|---|
id | INT(11) | 面试题目ID | 主键 |
problem_id | INT(11) | 题目ID | 外键 |
interview_id | INT(11) | 面试ID | 外键 |
接口设计详见接口设计文档
想要参与此项目?欢迎Fork并提交PR