关于考试系统的问题(一): 答题提交
在考试系统中,答题提交是最核心的操作。大部分的问题都出现在这个操作上。那么在设计考试系统的时候,该如何设计答题提交呢?
服务端设计
服务端在设计接口的时候无非有两种:
- 全量提交
- 增量提交
具体到答题提交表现为要么提交整张试卷,要么提交单道答题。
全量提交
在题量比较小的时候,使用全量提交是最省事的方案。
所谓全量提交,就是每次提交时,把整张试卷当前的所有答题状态一次性提交到服务端。无论用户只改了一道题,还是刚进入考试系统,服务端接收到的始终是完整数据。
优点
实现简单
- 服务端接口设计简单,一个接口即可
- 不需要维护复杂的答题状态
- 前端只关心“当前整卷数据”
服务端逻辑清晰
- 服务端可以直接做整体校验
- 数据结构天然和试卷结构一致
- 不容易出现“某道题状态缺失”的问题
容错能力强
- 网络抖动、请求失败后,重新提交即可恢复完整状态
- 不依赖之前的提交是否成功
缺点
数据量大
- 题量一多,请求体会迅速膨胀
- 特别是包含主观题、富文本、图片时,问题更加明显
提交频率受限
- 不适合高频自动保存
- 对网络和服务器压力较大
并发冲突难处理
- 多端同时作答(极少见,但并非不可能)时,容易相互覆盖
适用场景
- 题量少(如 10~20 道)
- 考试时长短
- 对实时保存要求不高
- 小型系统或内部系统
增量提交
增量提交是大多数考试系统最终都会走到的方案,也是我实际用过的方式。
增量提交的核心思想是: 只提交“发生变化的答题数据”,通常表现为按题提交。
常见实现方式
- 用户作答后立即提交当前题目
- 定时自动保存未提交的题目
- 提交内容一般包含:
- 试卷 ID
- 题目 ID
- 作答内容
- 作答时间 / 状态
优点
请求体小,性能友好
- 非常适合高频自动保存
- 对移动端和弱网环境更友好
用户体验更好
- 实时保存,降低丢失风险
- 页面刷新、异常退出后可快速恢复
支持大规模考试
- 题量大、考试人数多时依然稳定
缺点
服务端复杂度高
- 需要维护题目级别的答题状态
- 需要处理幂等、重复提交、乱序提交等问题
- 需要定义清晰的“时间语义”(以哪个时间为准)
对数据设计要求高
- 表结构、索引设计不合理,性能很容易出问题
客户端设计
客户端并不是“傻提交”,合理的客户端设计可以显著降低服务端压力,同时提升答题成功率。
提交设计的核心原则
少提交、晚提交、可补救
案例一:高频无效提交
用户在短时间内频繁修改答案,本质问题是: 中间状态没有任何业务价值。
设计策略:提交缓冲 + 防抖
每道题维护本地答题状态
用户操作后:
- 不立即提交
- 启动一个短延时(如 1~2 秒)
延时期间若再次修改:
- 覆盖本地状态
- 重置定时器
定时器触发后才真正提交
效果:
- 20 次操作 → 1 次提交
- 服务端只收到“稳定态”数据
- 不影响用户体验
本质是把“编辑态”和“提交态”分离。
案例二:截止时间前提交失败
考试结束前的最后几秒是事故高发区:
- 网络抖动
- 服务端排队
设计策略一:提前完成“有效提交”
- 不把希望压在最后一次提交上
- 客户端应保证:
- 最后一次有效答题提交 ≤ 截止时间 - 安全窗口
安全窗口建议:5~10 秒
超过安全窗口:
- 禁止继续编辑
- 只允许查看
- 明确提示「答题已保存」
设计策略二:尽最大努力接住最后一次提交
考试结束前的最后几秒,客户端是否能成功提交,不应该完全取决于网络和调度。系统应当允许“迟到到达,但逻辑上仍然有效”的请求。
核心思想是:
是否允许提交,由服务端“接收到请求的时间”决定,而不是处理完成的时间
设计要点
服务端在接收请求的第一时间记录时间戳
- 请求一进入服务端(如网关 / 接口入口)
- 立即记录
receive_time - 后续所有校验都基于该时间
提交合法性判断基于 receive_time
若
receive_time ≤ exam_end_time- 该提交视为合法
即使:
- 后续处理耗时较长
- 写库延迟
- 队列排队
都不影响提交有效性
交卷与答题提交同一判定规则
- 增量答题提交、交卷请求
- 都遵循同一时间判断逻辑
- 避免“能答不能交 / 能交不能答”的不一致体验
对客户端的配合要求
尽早发起请求
- 客户端仍应预留安全窗口
不因超时立即判定失败
- 请求发送成功即可视为“已提交中”
允许延迟反馈
- 服务端可异步返回最终状态
客户端负责“尽力送达”, 服务端负责“尽力接住”。
注意一个边界条件
- 必须防止客户端伪造时间
- 时间戳只能由服务端生成
小结
答题提交看起来只是一个“保存数据”的动作,但在考试系统中,它同时承担着数据正确性、公平性和用户体验三重责任。
- 服务端层面,需要在简单实现和系统规模之间做取舍,全量提交适合小规模场景,增量提交则是长期必然选择。
- 客户端层面,不应机械地“每次操作都提交”,而是通过缓冲、防抖和本地兜底,减少无效请求,提高提交成功率。
- 在截止时间这一极端场景下,系统应当尽可能接住已经到达的请求,以服务端接收时间作为判定依据,而不是让用户为网络和系统负载买单。