独立开发实战:学生管理+考试防作弊机制设计

📅 2026/7/4 20:09:33 👁️ 阅读次数 📝 编程学习
独立开发实战:学生管理+考试防作弊机制设计

独立开发实战:学生管理 + 考试防作弊机制设计


一、问题起源

在上一篇我分享了在线考试系统的整体搭建过程。但在实际使用中,老师和学生都反馈了一些问题:

老师反馈:

  • 「学生关了浏览器,后台还显示在作答,这个记录怎么消失?」
  • 「有的学生考完了还能再进去,这不是挂我们的人数吗?」
  • 「能不能管理学生名单?现在我们谁考了谁没考都靠人工记」

学生反馈:

  • 「考完了想回去看成绩怎么看?」
  • 「别人是不是也能看我的成绩?」

这些问题其实很有代表性——任何用户系统都会遇到会话管理权限控制的问题。这篇就详细讲讲我是怎么解决的。


二、心跳机制:解决「挂机不交卷」

为什么需要心跳

学生开始考试后,系统在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产品:一纸云深

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!

点击「阅读原文」查看我的独立开发笔记


👉 点击查看我的个人介绍

👉 点击查看我的小红书主页