独立开发实战:学生管理+考试防作弊机制设计
独立开发实战:学生管理 + 考试防作弊机制设计
一、问题起源
在上一篇我分享了在线考试系统的整体搭建过程。但在实际使用中,老师和学生都反馈了一些问题:
老师反馈:
- 「学生关了浏览器,后台还显示在作答,这个记录怎么消失?」
- 「有的学生考完了还能再进去,这不是挂我们的人数吗?」
- 「能不能管理学生名单?现在我们谁考了谁没考都靠人工记」
学生反馈:
- 「考完了想回去看成绩怎么看?」
- 「别人是不是也能看我的成绩?」
这些问题其实很有代表性——任何用户系统都会遇到会话管理和权限控制的问题。这篇就详细讲讲我是怎么解决的。
二、心跳机制:解决「挂机不交卷」
为什么需要心跳
学生开始考试后,系统在t_exam_session表里创建一条会话记录。正常情况下交卷后会自动删除这条记录。但如果学生直接关了浏览器:
- ❌
beforeunload事件可以发请求,但不是100%可靠 - ❌ 等考试时长到了再清理,要等45分钟
- ✅ 心跳机制:定期检查,断开后快速释放
实现方案
前端每 30 秒发一个请求到后端:
// 前端:每30秒发一次心跳heartbeatRef.current=setInterval(()=>{api.sendHeartbeat(examId,studentNo).catch(()=>{})},30000)后端收到心跳更新last_heartbeat_at:
// 后端:更新心跳时间session.setLastHeartbeatAt(LocalDateTime.now());sessionMapper.updateById(session);然后定时清理超过 1 分钟没心跳的会话:
// 每次查询前先清理LocalDateTimecutoff=LocalDateTime.now().minusMinutes(1);sessionMapper.delete(newLambdaQueryWrapper<ExamSession>().eq(ExamSession::getPaperId,paperId).and(w->w.isNull(ExamSession::getLastHeartbeatAt).or().lt(ExamSession::getLastHeartbeatAt,cutoff)));三层防御
为了确保不遗漏,我做了三层防御:
| 防御层 | 机制 | 效果 |
|---|---|---|
| beforeunload | 关标签页弹确认框 + fetch keepalive | 给用户一次反悔机会 |
| visibilitychange | 切标签页时发一次心跳 | 切走也能保持会话 |
| 后端定时清理 | 1分钟无心跳自动清除 | 兜底方案,无论什么情况都不超过1分钟 |
这样设计后,学生正常考试不受影响,意外断开后最多 1 分钟就会自动清理。
三、防重复进入:已考过的不能再考
原来的流程是:输入姓名 → 选试卷 → 开始考试 → 创建会话 → 答题 → 交卷
问题在于「开始考试」时没有检查是否已交卷。已考过的学生点开始考试,系统又给他创建了会话,但交卷时会被拦住「你已经提交过试卷了」。会话就挂在那了。
修复很简单:创建会话之前先查t_exam_result表有没有记录。
// 开始考试前检查是否已交卷longexistingCount=resultMapper.selectCount(newLambdaQueryWrapper<ExamResult>().eq(ExamResult::getPaperId,examId).eq(ExamResult::getStudentId,studentId));if(existingCount>0){returnApiResult.error(400,"你已经参加过本场考试");}这样已考过的学生根本进不到考试页面,也不会产生无效的会话记录。
四、学生管理:学号 + 密码登录
之前任何人都能输入姓名就考试,显然不行。我加了个完整的学生管理模块。
数据库设计
CREATETABLEt_student(idBIGINTAUTO_INCREMENTPRIMARYKEY,student_noVARCHAR(50)NOTNULLUNIQUECOMMENT'学号',nameVARCHAR(50)NOTNULLCOMMENT'姓名',genderVARCHAR(10)COMMENT'男/女',gradeVARCHAR(20)COMMENT'年级(下拉选择)',class_nameVARCHAR(50)COMMENT'班级(自由输入)',passwordVARCHAR(200)COMMENT'BCrypt加密密码',statusTINYINTDEFAULT0COMMENT'0=正常 1=禁用',created_byVARCHAR(50),updated_byVARCHAR(50),create_timeDATETIMEDEFAULTCURRENT_TIMESTAMP,update_timeDATETIMEDEFAULTNULLONUPDATECURRENT_TIMESTAMP,UNIQUEKEYuk_student_no(student_no));导入策略
老师通过 Excel 模板导入学生,模板格式:学号、姓名、性别、班级、年级。
第一次导入时自动设密码为student@123。如果学号已存在则跳过,不覆盖已有数据,也不改密码。
登录流程
学生首页输入学号 + 密码,后端的逻辑链:
输入学号密码 → 查学生表 → 学号不存在?→ 提示「请联系老师」 → 被禁用? → 提示「已被禁用」 → 密码错? → 提示「密码错误」 → 全部通过 → 显示姓名班级,允许选试卷考试权限控制
- 学生只能看「我的成绩」,接口按 studentId 过滤
- 老师可以在后台查看所有学生的成绩和统计
- 老师可以禁用某个学生(该学号无法再登录考试,但历史成绩保留)
五、前端通用组件沉淀
这次开发中我写了一些通用组件,以后新项目可以直接复用:
Toast 组件:右上角滑入,3秒自动消失,支持 success/error/warning 三种类型。不用原生 alert,体验好很多。
ConfirmDialog 组件:自定义确认弹窗,毛玻璃背景 + 缩放动画,支持 danger/warning/info 三种样式。
API 封装:统一request<T>()函数,自动注入 Bearer token,解析后端{code, data, message}响应格式。
这些组件已经提取成模板,新项目直接用。
六、小结
做这个考试系统最大的体会是:用户系统再小,权限和会话管理都不能马虎。
- 心跳机制解决挂机问题
- 开始考试前检查已交卷,防止重复进入
- 学号+密码登录,防止无关人员乱入
- 学生只能看自己的成绩,老师看全部
如果你也在做类似的小项目,欢迎关注我,后续分享更多实战经验。
💡 如果你对某个功能的具体实现感兴趣,欢迎留言交流。完整代码在 Gitee 私库,设计文档可以分享参考。
关于作者:无羡,独立开发者,专注AI应用开发。
📌 分类:全栈开发
👉 关注我获取更多技术分享
👉 个人博客:云深不知处
👉 独立开发省钱攻略:查看详情
👉 体验我的AI产品:一纸云深
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
点击「阅读原文」查看我的独立开发笔记
👉 点击查看我的个人介绍
👉 点击查看我的小红书主页
