关于在线答题系统设计的一些想法

语言: CN / TW / HK

业务场景

  • 100道不定项选择题,不同考生的题目顺序不一样
  • 200位考生在规定时间同时开始和结束答题
  • 考试开始后可以查看题目列表,不一定要按照顺序作答,已作答的题目可以修改,最终分数以交卷时的分数为准
  • 在考场后台的办公室中可以实时刷新分数的排行榜
  • 考试环境为学校的机房,网络环境为局域网
  • 考试题目为文学类知识竞赛,考生准考证号和身份证号分别作为用户名和密码

需求分析

1、为了避免相互抄袭,不同考生的题目顺序需要不一样,这里需要打乱题目的顺序。
2、考试开始作答的时间可能不同,但是都要在统一的时间截止答题,这就要求考生的机器需要统一时间,能够在考试结束后自动提交。
3、在考试过程中要能够查看分数排行版,这里可以在考生每回答或者更新题目的时候都去刷新排行榜,或者在管理段定时刷新的时候再去计算所有考试的分数和排行榜情况。
4、考试环境为本地局域网,考试过程中有监考老师进行巡逻,并且考试群体大多不具备计算机知识,这里可以基本排除考生进行抓包后主动调用接口的场景。


设计思路

打乱题目顺序

1、使用mysql的随机排序方法,ORDER BY RAND(准考证号)
2、在用户首次登录时,可以使用php的shuffle($arr)函数将题目顺序打乱后返回,并将打乱后的数组顺序再次存储到数据库中。
##统一时间交卷
交卷时间是确定的,我们要以服务器时间为准,用户机器上的时间不可靠。用户的每次请求的返回中,我们都给返回给用户一个交卷的截止时间,前端的js就基于该截止时间显示考试的剩余时间。在后端我们需要记录每个考生的交卷时间,在截止时间到达后不能再提交。
由于考试开始和结束时间都是一样的,所以这时候的并发了会比较大,我们可以让用户随机延迟5s以内,通过阅读考场规则或者增加loading图的方式可以优化交互体验。



数据都存储在本地

考虑到用户都是不具备计算机知识的,网络是局域网络,且现场有监考老师的存在,可以基本排除有用户会抓包或模拟请求的可能。
在用户登录后,查询题目列表时也把题目的答案返回给用户,这里可以将答案做一个简单的加密。在用户答题后可以根据答案计算用户当前的得分,只更新用户的得分,用户后台的显示。在用户交卷时再将本地数据全部上传到接口,这样最多在用户交卷时会有卡顿,在用户答题过程中还是非常流畅的。

这个场景比较特殊,存在很大的安全隐患,除非接口性能实在不能满足,最好还是不要使用。

基于Mysql的系统设计

生成随机题目

SELECT title,option1,option2,option3,option4 FROM questions ORDER BY RNAD('准考证号')
在用户登录后,将题目一次性取出,如果担心用户不小心关闭了浏览器或者电脑断电的话,cookie只能存放4k的数据,并且会随着请求发送,增加请求耗时,可以将数据存储到localStorage中。
用户作答或修改答案时都会调用接口,所以即使用户关闭了浏览器,再次打开重新登录的话,也是能够保存之前的答题记录的,只需要重新拉取一份题目列表就可以了。

记录用户答题结果

key questionId answer time
按照这种格式记录用户每道题目最后一次更新的答案,并记录用户的最后一次更新时间。
为了后期复盘,我们需要记录用户的登录、退出、交卷、答题和修改的操作日志,为了不影响接口的响应时间,我们可以将日志序列还之后添加到List里面,也可以使用redis或者RambbitMQ的消息队列来异步记录日志。

排行榜的刷新

1、在用户作答或者更新题目的时候计算用户的分数的话会影响答题接口的响应时间。
2、当管理端刷新排行榜时计算所有用户的分数,需要读取200*200条答题记录,计算分数并更新到数据库,然后进行排序后输出,短时间进行大量的数据操作会对数据库产品一定的压力,进而影响考试插入答题记录的速度。
3、我们可以采用swoole、redis或者RambbitMQ的消息队列,将第一种方案的接口请求立即返回,然后通过异步任务计算该考生的分数,并更新到数据库中。管理端的排行榜直接读取数据库中的成绩表,排序后显示。这里的消息队列和日志一样,即使消息丢失了也不会有什么大的影响。

交卷操作

在用户交卷时,我们可以直接调用php方法进行考生成绩的同步计算,为了避免同一时间很多考生同时调用接口,可以随机等待3s,然后再调用交卷接口。

基于Redis的系统设计

生成随机题目

记录用户答题结果

排行版的刷新

交卷操作

日志记录

压力测试