在线考试这类系统写的人太多了,但大多数做出来就是个"题库的皮、考试的壳":考生点进去,一题一题往下选,交卷,出分,结束。整个过程跟被审讯一样,做完就关页面,毫无记忆点。我这次做的这个基于Spring Boot的在线考试答题游戏,本质上是把一场考试重新设计成一套"闯关 + 计分 + 排行"的游戏规则,让答题不再是单向输出,而是有即时反馈、有竞争刺激、有复盘价值的一轮挑战。这篇博客就完整拆解一下这个项目的设计思路、Spring Boot侧的核心实现、以及我在实际开发中踩过的一些值得说的坑。如果你正准备做类似的毕设,或者想把已有的考试系统改得有可玩性一点,这篇内容应该能给你省不少事。
1. 这个项目要做的事:一场考试加一套游戏规则的化学反应
1.1 传统在线考试为什么需要改造
我先说结论:传统在线考试系统的功能完备性,其实早就不缺了。无论是Spring Boot还是其他框架,做一套"用户管理 + 题库管理 + 在线作答 + 自动判分 + 成绩导出"的组合,技术上没有太多门槛。问题出在用户体验上——大多数考生面对一个在线考试页面,心态是紧张和被动的,尤其是那种一屏显示几十道题、滚动条拉半天、一次交卷定生死的设计,它只完成了"考核"这个动作,完全没有考虑"答题状态"对成绩的影响。
如果把这个过程换个思路,变成有限时间内逐题闯关、答对即时得反馈、连对还有额外奖励、结束后能看自己在所有玩家中的排名,那考生的状态就会从"被迫验证"变成"主动挑战"。这不是玄学,是游戏化设计中最基本的即时反馈和成就驱动理论。我做的这个系统,核心目标就是把这两件事缝合起来:后端仍然是严肃的考试判分逻辑,前端交互却采用游戏的节奏和激励机制。
1.2 答题游戏的游戏化规则模型
既然要做"答题游戏",那游戏化到底落在哪些机制上?我整理了一张规则映射表,这是我做需求分析时最先定下来的东西:
| 传统考试概念 | 答题游戏化映射 | 后端实现要点 |
|---|---|---|
| 试卷 | 关卡/场次 | 独立配置题目集、时长、难度系数 |
| 单选题干 | 关卡目标值 | 每题绑定基础分 |
| 作答正确 | 命中得分 | 实时判分,记录答案明细 |
| 连续答对 | 连击加成 | 维护连击次数,计算加权分数 |
| 答错扣分或失去机会 | 生命值消耗 | 可配置的答错上限 |
| 交卷 | 本场结算 | 汇总分数、用时、排名位次 |
| 成绩单 | 战报/结果页 | 展示统计图表、错题回顾、排名变化 |
这套规则下来,一个普通的50道题考试,就成了"50个连续的小关卡"。考生不会觉得每一步都在被审判,而是在尝试维持自己的连击节奏。后端在存储层做的改动其实不大——无非是多记录一些状态字段和事件流水,但体验层的差异是质的。
1.3 系统用户角色与核心流程梳理
这个系统我按三种角色设计:管理员负责系统配置和全局数据查看,出题人(教师)负责题库维护和试卷/场次创建,考生负责参与答题挑战。核心业务流大概是:
- 出题人创建题库 → 批量导入题目 → 配置一场答题游戏的场次(题目范围、限时、生命值、难度分布);
- 考生登录后选择场次 → 进入答题房间 → 按题作答,系统实时反馈对错、连击、剩余时间;
- 本场结束 → 后端结算分数和排名 → 考生查看成绩报告和排行。
这个流程里最关键的,不是那些常规的增删改查,而是"实时反馈"和"结算排名"这两条链路。后面的文章核心部分我会专门展开。
2. Spring Boot骨架搭建与技术选型思考
2.1 为什么这个项目非Spring Boot不可
先回答一个最常被问到的问题:这类系统用Spring Boot合适吗?我的答案是,如果你要求的是"生态成熟、上手快、部署简单、招人好聊",那Spring Boot几乎是目前最优解,没有之一。
Spring Boot框架在我这里最大的价值不是"自动配置"这种宣传话术,而是它把Java Web开发从"打地基"阶段直接带到了"盖房子"阶段。传统Spring项目要配一堆XML、要装独立Tomcat、要处理各种依赖版本冲突;Spring Boot把这些全压平了,一个spring-boot-starter-web起步依赖下来,内嵌容器、默认配置、健康检查全都有了,我可以把百分之九十的精力放在业务逻辑本身上。
对这类在线考试答题游戏项目来说,还有几个特别现实的考量:
- 稳定性和事务能力:答题过程涉及试卷快照、答案记录、分数更新、排名写入等多个数据操作,稍微有点规模就不能依赖脚本语言那份随意,Spring的声明式事务
@Transactional在这些场景下非常好用; - 生态覆盖:题库导入需要Excel解析(EasyExcel/Poi)、登录鉴权需要JWT或Session方案、排行榜需要Redis、文件上传需要OSS或本地存储,这些需求Spring Boot都有成熟的集成方案,而不是让我自己造轮子;
- 部署友好:最终交付物就是一个可执行的Jar包,服务器上装个JDK就能跑,对毕设演示或者小范围公网上线都是最省事的路径。我后面部署时更是深有体会。
2.2 数据库表设计与核心实体关系
游戏化机制带来的表结构变化,主要集中在"答题过程记录"和"场次状态"这两块。核心表我设计了以下几张:
用户表(sys_user):常规的id、用户名、密码、昵称、角色、头像、创建时间。普通做法,不多说。
题库表(question_bank):存储题目本身。这里要注意的是题目类型有多种(单选、多选、判断),选项数量和正确答案的存储结构需要统一。推荐用"选项JSON + 答案JSON"的方式,后面会专门讲。
场次表(quiz_session):这是答题游戏和普通考试最大的区别点。除了常规的标题、开始时间、结束时间、题目数量,它还要额外存这些游戏化配置字段:限时总时长、每题时限、初始生命值、答错扣多少生命值、基础得分、连击加成系数、是否公开排行。这些字段直接决定了一场答题游戏好不好玩。
答题记录表(answer_record):记录考生每道题的作答情况。字段包括:场次id、用户id、题目id、选中答案JSON、是否正确、得分、连击数、作答耗时、作答序号。这表是后续排行榜和错题回顾的核心数据来源。
场次参与表(user_session):记录谁参加了哪一场、什么时间进入、最后的总分、总用时、排名、状态(进行中/已结束)。这张表是排行数据的直接来源。
核心关系不复杂:一个场次对应多道题(通过场次题目关联表),一个用户参加多个场次,每个场次产生一份参与记录,每道题产生一条答题明细。
2.3 工程目录结构与接口设计规范
我用的是标准的四层结构:controller → service → mapper → entity。没有引入复杂的DDD分层,因为这个项目规模远没有到那个程度,过度设计反而是累赘。
src/main/java/com/example/quizgame ├── controller // 接口层 ├── service // 业务层 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 请求响应对象 ├── common // 统一返回结构、异常、工具类 └── config // 全局配置接口设计上,我遵循两点原则。第一,接口按资源而非功能命名,比如/api/session/{id}/start表示开始一场答题,/api/session/{id}/submit表示提交答案,而不是/api/doQuiz这种含糊写法。第二,所有接口返回统一的ResponseResult<T>结构,包含code、message、data三个字段。这样做前后端联调时非常愉快,前端只需要统一处理一次响应结构即可。
3. 题库与组卷:把一道题变成一条可用的数据
3.1 题型建模与选项存储方案
这是很多从零开始做考试系统的人第一个纠结的地方:单选、多选、判断题,到底是每科建一张表,还是合成一张表?我强烈建议合成一张题表,用"类型字段 + JSON字段"来存储差异化内容。
原因很简单:合表之后,出题人维护题库只管往一张表里插数据,组卷时的查询逻辑也只需要对一个表做筛选,不会因为多张题型表互相UNION把简单的事情搞复杂。具体表结构我这样设计:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| type | tinyint | 题型:1单选 2多选 3判断 |
| content | text | 题干 |
| options | json | 选项列表,如["A.xxx","B.xxx"],判断题也统一存两个选项 |
| answer | json | 正确答案,单选存["A"],多选存["A","C"] |
| difficulty | tinyint | 难度:1简单 2中等 3困难 |
| analysis | text | 解析,结果展示和错题回顾用 |
JSON字段在MySQL 5.7以上版本用起来很顺畅,查询时用JSON_CONTAINS也可以做部分筛选,但绝大多数场景其实只需要整体存取,所以性能完全不用担心。判断题我统一存成两个选项"正确/错误",答案就是["A"]或["B"],处理逻辑和单选完全一致,省掉分支。
3.2 随机组卷算法的实现与代价控制
组卷这一步是"考试系统"和"答题游戏"在数据准备上的第一个分水岭。普通考试系统往往是出题人手动挑题,或者简单随机抽题;答题游戏则要求在有限条件下生成一场"有梯度、有节奏"的题目序列。
我实现的组卷策略分两层:先按难度配比抽取,再进行随机洗牌。难度配比可以配置,比如一场50题的场次,默认是简单20题、中等20题、困难10题。抽取时先按难度分组,各部分内部用随机打乱的方式取指定数量,最后合并时再整体洗牌一次,避免所有简单题都堆在前面,导致开局节奏垮掉。
核心逻辑的伪代码大致是这样:
public List<Long> generatePaper(QuizSession session) { // 1. 从配置中解析难度配比,例如 simple:20, medium:20, hard:10 Map<Integer, Integer> ratio = parseRatio(session.getDifficultyRatio()); List<Long> result = new ArrayList<>(); for (Map.Entry<Integer, Integer> entry : ratio.entrySet()) { List<Long> ids = questionMapper.selectIdByDifficulty(entry.getKey()); Collections.shuffle(ids); result.addAll(ids.subList(0, Math.min(entry.getValue(), ids.size()))); } // 2. 整体洗牌 Collections.shuffle(result); return result; }这里有一个必须提醒的坑:如果题库里某个难度的题数量不够配比数量,直接取subList会报越界异常,一定要用Math.min兜底。更合理的做法是组卷前先做一次数量校验,如果不够就及时提示出题人补充题库,而不是硬抽。这个坑我在测试阶段踩到过,给测试同学提了个很"惊喜"的bug单。
3.3 批量导入导出与题库维护
答题游戏的内容核心是题库,所以题库维护的效率直接决定这个系统的实用程度。我引入了EasyExcel来实现Excel批量导入,在服务端解析上传文件,逐行校验后插入数据库中。
导入时比较容易忽视的是数据校验。我建议不要上来就全量插入,而是先做一次全量校验,收集所有行错误之后统一返回给前端。比如题干为空、选项缺失、答案格式不对、重复题等。这样出题人不需要一遍一遍地改文件。校验逻辑放在一个独立的方法里,校验和入库分开,保证事务性——只要有一行数据有问题,整批不入库,避免题库出现一半成功一半失败的状态。
导出逻辑就简单很多:查询符合条件的题目,映射成Excel模板行,用EasyExcel写出到响应流。导出Excel的时候注意服务端要设置好响应头:
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-Disposition", "attachment;filename=question_bank.xlsx");如果漏掉Content-Disposition,前端拿到的就是一坨乱码流,下载下来的文件打不开,这类问题排查起来也很常见。
4. 答题游戏节奏的实现:倒计时、连击与结算
4.1 倒计时究竟放在前端还是后端
这是我在设计阶段纠结最久的一个问题。倒计时的实现方案直接决定游戏节奏的体验和安全边界。
方案一:纯前端倒计时。JS的setInterval每秒减一,倒计时结束触发交卷。优点是交互流畅、服务器压力小;缺点也很致命——稍微懂点前端的玩家开个控制台就能把计时器停掉或改时间,就算不做恶意修改,页面切后台后定时器被浏览器节流,倒计时就会变慢,造成严重不公平。
方案二:纯后端倒计时。每次请求都带时间戳,由服务端判断本场是否超时。优点是绝对安全;缺点是这样一来前端每秒都要发一个请求,服务端压力大,而且网络波动时体验会很差。
我的实际做法是折中:前端倒计时只做展示和本地过期提醒,真正的时限判定全部以服务端保存的"场次开始时间 + 配置时长"为准。考生每次提交答案时,后端先做超时校验,超时则强制结束本场。这样既保证了前端交互流畅,又杜绝了前端改时间的作弊问题,同时不算计每次网络请求。
4.2 连击加分与生命值扣除的判定逻辑
连击加分是这个系统"游戏感"的核心来源。我定义的规则是:基础分按题目难度分成三档,简单10分、中等15分、困难20分;连续答对时,从第2题开始,每道题的得分加一个连击加成,加成为当前连击数 * 难度系数。
举个例子:假设全是中等难度题,基础分15分,连续答对第5题时,连击数是5,那本题得分为15 + 5 * 1 = 20分。也就是说,保持连击的意义不只是虚荣,而是直观地反映在总分上。由于连击数上限我设了20,所以分数不会无边界膨胀。判定流程大概是这样:
public SubmitResult submitAnswer(SubmitRequest request) { // 1. 校验场次存在且进行中 // 2. 校验用户属于该场次且未结束 // 3. 取出本场记录,得到当前连击数 // 4. 判断答案正确性 if (isCorrect) { currentCombo++; int baseScore = difficultyMap.get(question.getDifficulty()); int extraScore = Math.min(currentCombo, MAX_COMBO) * difficultyBonus; score = baseScore + extraScore; } else { currentCombo = 0; lifeLeft--; score = 0; if (lifeLeft <= 0) forceFinishSession(user, session); } // 5. 记录answer_record,更新user_session状态和连击数 }这套逻辑最关键的一点是:连击数的读取和更新必须是原子的。实际开发时建议通过乐观锁或数据库行锁来保证,否则高并发下两个人同时提交,很容易把连击数读成同一个旧值,导致加分重复。我用的办法是在user_session表加一个版本号字段,更新时带where version = ?,更新成功才继续,失败则重试。
4.3 实时反馈机制:答题卡、进度条与结果页
游戏节奏的另一半在反馈层的设计。我做了三个层面的反馈:
- 当前题反馈:提交一题后,立即高亮显示对错,把正确答案简要展示出来,同时更新顶部连击数。这个"对了,下一个"的循环是驱动玩家继续答下去的核心动力;如果反馈延迟超过一秒,游戏感就没了。
- 答题卡总览:页面上有一块区域的缩略答题卡,标注已完成、待答、答错三种状态。答错的题可以点击回看,回看只是展示题干和正确答案,不允许修改。这点很关键——游戏可以给重试机会,但考试判分必须保持严肃性。
- 结果页战报:结算后展示总分、正确率、最高连击、用时、击败玩家百分比这几项数据。这里我额外加了一个"连击折线图"组件,展示每道题的得分趋势,让玩家一眼看到自己的爆发区间。
前端这块我用的Vue,实时反馈主要靠接口返回后更新响应式数据,用不到WebSocket。因为答题是一个"提交-响应"的短循环,轮询和长连接都是过度设计,这个经验在后面WebSocket相关热搜上很多人都在纠结,其实选不选WebSocket要看场景,答题这种短交互完全不需要。
5. 排行榜与成绩存档:竞争氛围的数据支撑
5.1 排行需求的细分:全局榜、好友榜、单场榜
排行榜不能只做一个"所有成绩倒序取前100"了事,那太粗糙。我实际拆分成了三种榜单:
- 单场榜:同一场次内,按分数从高到低、用时从少到多排序。这是答题游戏每场结算后的核心榜单,也是玩家最有感知的榜单;
- 全局积分榜:把玩家在所有场次中获得的总分和场次胜场数汇总,按积分排名。这个榜单适合放在系统首页,作为长期留存激励;
- 好友榜:基于关注关系,展示好友列表内的排名,竞争感更强,但因为好友关系会引入复杂的社交设计,我在当前版本里用"同场次玩家榜"代替了,数据直接从单场榜里过滤出来。
这里有一个容易忽略的细节:单场榜要注意并列处理。分数相同、用时相同的情况用什么规则排?我的做法是"先比分数,再比用时,再比提交时间早的优先",一个稳定的排序规则比啥都重要,否则前端拿到的榜单每次刷新顺序都不一样,会被玩家认为是bug。
5.2 高并发排名写入的取舍策略
排名写入是答题游戏最经典的并发场景。设想一场公开赛,500个人同时在最后三秒交卷,如果每个人交卷都实时更新排行榜SQL,数据库压力瞬间就会上来。
我给这个系统设计了一个简单的两级策略:普通场次直接更新MySQL排行榜表,热门场次走Redis临时榜单,结束后落库。判断一个场次是否热门,可以在创建场次时由出题人手动标记,或者系统根据参与人数阈值自动切换。这样做的好处是冷场次不用引入额外依赖,保持部署简单;热场次则用Redis的ZSet结构,ZAdd命令天然支持按分数排序,延迟极低,非常适合榜单场景。
Redis方案的代码非常简洁:
stringRedisTemplate.opsForZSet().add("session:rank:" + sessionId, userId.toString(), totalScore);注意ZSet只能按一个分数排序,所以"分数相同按用时排序"这种需求在Redis里实现起来比较绕。实际做法是把"分数"映射成一个带权重的复合分数,比如score * 100000 - costSeconds,ZSet按这个复合值排序,这样一次ZRevRange就能拿到正确的名次列表。这种空间换排序的用法,在排行榜场景里非常典型。
5.3 错题回顾与成绩报告
答题游戏结束不等于闭环结束,错题回顾是学习价值的核心。我从answer_record表里把用户某场次所有错误题目查出来,关联题目表拿到完整题干、选项、正确答案和解析,组装成一个错题集合接口返回给前端。
这里我额外做了一件事:把错题集合里的题目难度做了统计,输出"薄弱模块"提示。虽然这个版本没做太复杂的知识点标签,但仅靠难度分布就能给玩家一个很直观的复盘视角——"你是简单题全对,困难题全错,那你的问题在于硬骨头没啃下来"。这个功能在毕设答辩时非常加分,因为它证明你不仅做了增删改查,还思考了数据的二次价值。
6. 安全与稳定性:在线答题系统不能漏掉的三件事
6.1 防刷接口与作答校验
在线答题系统和游戏服务器一样,天然是"恶意玩家"的目标。最常见的攻击方式是:绕过前端直接调用交卷接口,伪造一个高分提交。防这个问题的第一道防线,不是加密,而是服务端校验——校验当前场次确实包含这道题、校验考生确实在参与该场次、校验题目答案的格式和值域、校验提交时间没超时。这些校验缺一不可,别以为前端做了拦截就够了。
第二道防线是幂等控制。同一道题同一场次同一个用户,后端只接受第一次提交,后续提交直接拒绝或返回重复提示。我通过answer_record表建唯一索引(session_id, user_id, question_id)来实现,数据库层面的唯一约束比任何业务代码都可靠。
第三道防线是对场次状态的校验。答题游戏的核心状态是user_session里的"进行中/已结束"字段。交卷接口、提交答案接口都要先校验状态,已结束的场次任何提交都要拒绝。这个逻辑看起来是废话,但真的有很多系统把状态校验只写在页面上,后端接口裸奔。
6.2 XSS、文件上传与接口兜底
安全这块必须有专门一节。很多人搜过"springboot项目全局过滤器处理上传pdf文件时xss攻击"这类问题,说明大家在实际开发中对XSS和文件上传安全是真有困惑。我的建议是:不要试图用一个全站过滤器解决所有安全问题,而是分层处理。
题干、答案解析这类富文本内容,在入库前做XSS过滤,把<script>、onerror等危险标签转义或移除。我实现了一个基于Jsoup的HTML清理工具,白名单保留基础的格式标签,其余全部过滤,比黑名单过滤可靠得多。全局过滤器只处理请求参数层面,比如URL参数和表单字段的非法字符检查,但不要用它替代业务层的富文本安全处理。
文件上传这块,"上传PDF时XSS"的经典场景是PDF里嵌入脚本,在浏览器预览时执行。Spring Boot默认的MultipartFile接收上传没问题,关键是存储和访问策略:上传目录绝对不要放在静态资源目录下,访问时不走Web容器直接映射。更稳的做法是:上传到服务端专用目录,文件名用UUID重命名,不保留用户原始文件名;访问时通过专门的下载接口鉴权后走流输出,或者走OSS签名URL。最优解是不要自己托管文件,直接用对象存储服务,把文件安全交给专业的人。
另外还有一类容易被忽略的兜底问题:接口参数校验。比如用户id传负数、题目id传不存在的、分页参数超大,这些问题用@Validated注解加@NotNull/@Min/@Max约束,再配合全局异常处理器统一返回友好提示。Spring Boot的@RestControllerAdvice处理异常非常方便,这是我在项目早期就引入的,后续所有接口的异常处理都走了统一通道,没有在业务代码里到处try-catch。
6.3 高并发场景下的性能调优经验
答题游戏做到后期,性能优化是绕不开的话题。我实际给测试环境压过一轮,总结下来优先做这三件事:
连接池调优:默认HikariCP配置非常保守,
maximum-pool-size只有10。我在压测时发现40并发就出现获取连接等待,把最大连接数调到50、最小空闲调到10之后,并发能力提升明显。这个参数按实际机器配置调,不是越大越好。SQL加索引:
answer_record表在(session_id, user_id)上加联合索引,question_bank表在(difficulty, type)上加普通索引,user_session表在(session_id, score)上加索引。这几条索引一加,排行榜和错题回顾的查询从全表扫描直接变成索引扫描,响应时间降了一个量级。这是投入产出比最高的一步优化。缓存高频读接口:场次基本信息、排行榜前100名这类读多写少的接口,加一层
@Cacheable本地缓存或Redis缓存,过期时间设置30秒到一分钟即可。不要过度缓存,否则又带来缓存一致性问题。
另外还有一个不算性能但算稳定性的点:题库导入和导出这类耗时操作,不要放在请求线程里同步执行。如果题库数据量大,接口可能会超时。我用了Spring的@Async配合一个简单的任务表,前端提交导入任务后轮询任务状态,后台线程执行解析。这个设计在毕设演示时也更有话可说。
7. 从本地到服务器:部署实录与踩坑复盘
7.1 本地打包与外部配置分离
项目做完之后,最终要部署到服务器上给别人用。Spring Boot项目的交付物就是一个Jar包,打包用Maven的package命令即可。但我在实际部署时遇到了第一个坑:配置文件。
本地开发时application.yml里写的是本地的数据库地址和Redis地址,如果直接打包部署到服务器,要么改配置重新打包,要么想办法让配置外部化。我的做法是:在Jar包同目录下放一份application.yml,启动时显式指定外部配置:
java -jar quiz-game.jar --spring.config.location=./application.yml这样数据库、Redis连接、端口、文件存储路径等信息都留在服务器上,代码仓库里只保留一份application-dev.yml作为默认配置。部署时修改外部配置即可,不用重新打包。这个习惯基本成了我所有Spring Boot项目的标配。
7.2 服务器部署中的典型问题
实际部署中还踩了几个不大不小的问题,值得记一笔:
- 端口被占用:服务器上跑着其他服务,8080端口被占了。排查用
lsof -i:8080看进程,然后决定换端口。Spring Boot改端口只需要在外部application.yml里改server.port,很方便,但前提是你的防火墙和安全组也要放行对应端口。 - 数据库时区问题:本地开发时没注意,服务器上部署后答题记录的时间比实际时间慢了8小时。原因是MySQL连接串里的
serverTimezone没配对。在配置里显式加上serverTimezone=Asia/Shanghai并确认服务器系统时间本身正确,这个问题就解决了。 - 上传目录权限:文件上传路径如果配置成绝对路径比如
/home/quiz/uploads,要确保运行Jar包的进程对这个目录有读写权限。我因为用了systemd服务方式启动,服务运行用户是root,但目录属主配错了,导致上传文件一直报权限不足。后来统一用chown -R把目录属主改成运行用户解决。 - Jar包内存占用:默认JVM堆内存会根据机器配置自适应,如果服务器内存不大,加上其他服务很容易吃紧。部署时显式指定
-Xms256m -Xmx512m,让JVM在合理范围内运行。
7.3 如果再让我做一遍,我会改什么
这个项目做完后,我复盘了几处如果重做一定会调整的设计:
第一,题库的知识点标签功能。当前版本只有难度维度,导致"薄弱模块分析"只能按难度统计,维度太浅。如果加上知识点分类,错题回顾的"薄弱模块"就能精确到具体知识点,无论是学习价值还是答辩展示效果都会上一个台阶。
第二,前端工程化程度。我的前端是用Vue + Element UI搭的,如果重做,一定会引入状态管理库Pinia来维护答题过程中的全局状态。现在答题卡、倒计时、连击数这些数据在多个组件间共享,通信基本靠Props和事件,代码写得比较累,状态多了之后维护成本直线上升。
第三,答题过程的"复盘回放"功能。现在结果页只能看错题,不能看完整个过程。想象一下,如果能把整个答题时间线做成回放,什么时间答了哪道题、每道题耗时多少、连击在哪里断的,都展示出来,这个产品的完整度和可讲的故事性都会有很大提升。这算是我对下一版的一个明确规划。
最后再分享一个我在实际使用中总结的小技巧:答题游戏这类系统,上线前一定要有人真的去完整跑一遍流程。不是测试用例那种跑,而是像真实用户一样,打开页面、登录、开始答题、中途切一下页面、甚至开两个浏览器同时操作。很多并发问题和状态错乱,只有在这种模拟真实的使用节奏中才会暴露出来。这个项目从开发到部署,我自己完整跑下来的次数不下30次,最开始几乎每跑一次都能发现一个批次的状态问题——但现在稳定跑了几百场都没出过差错。系统的可靠性,从来都是这么一遍遍磨出来的。