SSM学生在线考试系统实战:从架构设计到高并发避坑指南
简介这是一套基于SSM框架开发的学生在线考试系统完整源码面向计算机专业学生与Java初学者适用于期末大作业、课程设计或毕业设计场景。系统划分学生、老师、管理员三种角色学生可加入课程、参加考试并查询成绩老师能添加试题、发布考卷、创建课程管理员负责用户、考试与权限管理并整合Shiro权限控制、Redis缓存、EasyUI界面与EasyPoi导出等功能。压缩包共7008个文件以png图片、html页面、css样式、js脚本为主另含java源码、jsp页面、xml配置、jar依赖及sql脚本等整体约763.9MB并附带用例图、ER图、数据库表设计图与详细代码注释。资源还包含运行指导视频及各类开发工具安装包方便快速搭建环境。目前已有1073人学习适合需要完整项目参考与实战练手的读者。1. SSM学生在线考试系统从“能跑”到“敢给学生用”之间隔着什么每年期末季总有教务老师抱着一摞纸质卷子找我吐槽印卷、发卷、收卷、批改、登分一套流程走下来两周起步还容易在登分环节出错。SSM学生在线考试系统就是冲着这个场景来的——用 Spring SpringMVC MyBatis 这套经典组合把出题、组卷、答题、判分、成绩查询串成一条线上链路。它适合课程设计、毕业设计也适合中小型培训机构做内部测验。但我要先泼一盆冷水网上能搜到的 SSM 考试系统 demo 一大把真正敢让学生同时在线答题的没几个。差别不在功能列表而在并发提交、断线重连、防作弊、判分一致性这些“看不见”的地方。这篇笔记就按我实际搭过的一套方案把选型理由、表结构、核心代码、参数配置和踩过的坑一次讲清楚让你少走弯路。2. 技术选型与数据库设计为什么这套组合现在还能打2.1 SSM 三层架构在考试场景下的分工很多人觉得 SSM 过时了但放到考试系统这个场景里它的分层反而特别清晰。Spring 管 Bean 和事务SpringMVC 管请求路由和参数绑定MyBatis 管 SQL 映射。考试系统最怕的是“提交答案时事务没兜住”比如学生点了交卷答案写了一半数据库连接断了成绩就算废了。Spring 的声明式事务在这里就是后悔药——只要把交卷逻辑包在一个Transactional方法里答案写入和状态更新要么全成要么全败。具体分工我一般这样划Controller 层只做参数校验和视图返回不写业务Service 层负责组卷算法、判分逻辑、事务边界DAO 层用 MyBatis 的 Mapper 接口SQL 写在 XML 里方便调优。这样分层的好处是判分逻辑可以单独写单元测试不用启动整个 Web 容器。2.2 考试系统核心表结构设计表设计是这类系统的地基地基歪了后面全是坑。我见过最离谱的设计是把所有题目塞进一个question表选项用逗号拼接存字符串结果判分时得用split解析性能差还容易出错。下面是我实际用的核心表结构字段经过脱敏但结构完整。表名关键字段说明exam_paperid, title, total_score, duration, start_time, end_time, status试卷主表status 控制是否可作答exam_questionid, paper_id, question_type, content, options, answer, score, sort_order题目表options 用 JSON 存选项exam_recordid, paper_id, student_id, start_time, submit_time, score, status考试记录status 区分进行中/已交卷/超时exam_answerid, record_id, question_id, student_answer, is_correct, got_score逐题答案判分后回写studentid, username, password, real_name, class_id学生表密码必须加盐哈希options字段用 JSON 而不是逗号拼接是因为 MySQL 5.7 以后原生支持 JSON 类型查询和校验都方便。exam_answer单独建表而不是塞进exam_record是为了支持逐题判分和错题回顾。2.3 组卷策略随机抽题与难度系数的平衡组卷不是简单ORDER BY RAND()就完事。我一般按题型和难度两个维度抽题先按question_type分组再在每组内按difficulty字段分层随机。下面这段 SQL 是抽 10 道单选题、其中简单 6 道中等 3 道困难 1 道的写法。-- 按难度分层抽题避免全抽到难题或全抽到简单题 SELECT * FROM ( SELECT * FROM exam_question WHERE paper_id #{paperId} AND question_type 1 AND difficulty 1 ORDER BY RAND() LIMIT 6 ) AS easy UNION ALL SELECT * FROM ( SELECT * FROM exam_question WHERE paper_id #{paperId} AND question_type 1 AND difficulty 2 ORDER BY RAND() LIMIT 3 ) AS medium UNION ALL SELECT * FROM ( SELECT * FROM exam_question WHERE paper_id #{paperId} AND question_type 1 AND difficulty 3 ORDER BY RAND() LIMIT 1 ) AS hard;逻辑说明三个子查询分别按难度抽题UNION ALL合并结果。参数#{paperId}是试卷 IDquestion_type 1代表单选。注意ORDER BY RAND()在数据量大时性能差如果题库超过一万条建议改用WHERE id (SELECT FLOOR(RAND() * MAX(id)) FROM exam_question)的方式。难度系数difficulty我设成 1/2/3 三档实际使用时可以在 Service 层根据试卷总分动态调整各档数量。3. 核心功能实现从登录到交卷的完整链路3.1 学生登录与考试权限校验登录不是查一下用户名密码就完事。考试系统必须校验“这个学生有没有这场考试的权限”否则学生猜到 paperId 就能进别人的考场。我的做法是在登录成功后把学生 ID 和班级 ID 写入 Session进入考试前再查一次exam_paper的status和班级白名单。// ExamController.java 进入考试前的权限校验 RequestMapping(/exam/enter) public String enterExam(RequestParam Integer paperId, HttpSession session) { Student student (Student) session.getAttribute(currentStudent); if (student null) { return redirect:/login; // 未登录直接踢回登录页 } ExamPaper paper examPaperService.getById(paperId); // 校验试卷状态0-未发布 1-进行中 2-已结束 if (paper null || paper.getStatus() ! 1) { throw new BusinessException(考试未开始或已结束); } // 校验班级白名单paper.getClassIds() 存的是逗号分隔的班级ID if (!paper.getClassIds().contains(String.valueOf(student.getClassId()))) { throw new BusinessException(你不在本次考试名单中); } // 检查是否已有进行中的记录防止重复进入 ExamRecord exist examRecordService.findOngoing(paperId, student.getId()); if (exist ! null) { session.setAttribute(recordId, exist.getId()); return redirect:/exam/paper; // 断线重连回到原记录 } // 新建考试记录 ExamRecord record examRecordService.createRecord(paperId, student.getId()); session.setAttribute(recordId, record.getId()); return redirect:/exam/paper; }参数说明paperId从 URL 传入student从 Session 取。BusinessException是自定义异常配合全局异常处理器返回友好提示。这里的关键是findOngoing方法——它查exam_record里status 0且student_id和paper_id匹配的记录实现断线重连。没有这一步学生刷新页面就会生成新记录最后交卷时数据全乱。3.2 答题页面的题目渲染与答案暂存答题页面我一般用 Thymeleaf 或 JSP 渲染题目一次性加载到前端答案用 AJAX 定时暂存。为什么不做成每翻一页请求一次因为考试场景网络可能不稳定频繁请求反而增加失败概率。一次性加载后前端用localStorage做本地暂存每 30 秒同步一次到后端。// 前端答案暂存逻辑每30秒同步一次 let answers {}; // 本地答案缓存 // 选项点击时更新本地缓存 function selectOption(questionId, option) { answers[questionId] option; localStorage.setItem(exam_answers, JSON.stringify(answers)); } // 定时同步到后端 setInterval(function() { const recordId document.getElementById(recordId).value; fetch(/exam/saveAnswer, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({ recordId: recordId, answers: answers }) }).then(res res.json()) .then(data { if (data.code ! 200) { console.warn(暂存失败将在下次重试); } }); }, 30000); // 30000毫秒 30秒逻辑说明answers对象以questionId为键、选项为值。localStorage保证刷新页面后答案不丢。同步间隔设 30 秒是权衡——太短增加服务器压力太长丢数据风险大。后端/exam/saveAnswer接口收到后先删exam_answer里该record_id的旧记录再批量插入保证幂等。注意这里不要用INSERT ... ON DUPLICATE KEY UPDATE因为题目可能被删除直接删旧插新更干净。3.3 自动判分与主观题处理交卷后的判分是考试系统的核心。客观题自动判主观题留给老师。我一般把判分逻辑放在 Service 层用策略模式区分题型。// ScoreService.java 判分核心逻辑 Transactional(rollbackFor Exception.class) public void autoScore(Integer recordId) { ExamRecord record examRecordMapper.selectById(recordId); ListExamAnswer answerList examAnswerMapper.selectByRecordId(recordId); int totalScore 0; for (ExamAnswer answer : answerList) { ExamQuestion question examQuestionMapper.selectById(answer.getQuestionId()); // 题型1-单选 2-多选 3-判断 4-简答 if (question.getQuestionType() 4) { answer.setIsCorrect(null); // 主观题标记待批改 answer.setGotScore(0); } else { boolean correct question.getAnswer().trim() .equalsIgnoreCase(answer.getStudentAnswer().trim()); answer.setIsCorrect(correct ? 1 : 0); answer.setGotScore(correct ? question.getScore() : 0); totalScore answer.getGotScore(); } examAnswerMapper.updateById(answer); } record.setScore(totalScore); record.setStatus(2); // 2-已交卷 record.setSubmitTime(new Date()); examRecordMapper.updateById(record); }参数说明recordId是考试记录 ID。Transactional保证判分和状态更新原子性。多选题的判分更复杂——我一般用“少选得一半分错选不得分”的策略实现时把答案拆成字符数组做集合比较。主观题isCorrect设为null前端根据这个字段显示“待批改”。注意equalsIgnoreCase只适用于单选和判断多选必须用集合比较否则“AB”和“BA”会被判错。4. 避坑与排查那些让我熬夜的翻车现场4.1 交卷时数据库连接超时导致答案丢失现象学生点交卷后页面卡住刷新后发现答案没保存考试记录还是“进行中”。原因交卷逻辑里做了太多事——判分、更新记录、写日志一个事务里跑了十几条 SQL超过了数据库连接超时时间。默认wait_timeout是 28800 秒但连接池的maxWait可能只有几秒。解决把判分拆成异步任务。交卷接口只做两件事——保存答案、把记录状态改成“待判分”然后立刻返回。判分用Async异步执行或者丢到消息队列。这样交卷响应时间从 3 秒降到 200 毫秒。连接池参数也要调Druid 的maxWait设 5000 毫秒validationQuery设SELECT 1。4.2 考试时间到期后学生仍能提交现象考试设了 60 分钟学生 65 分钟时还能点交卷成绩有效。原因前端倒计时只是展示后端没有校验start_time duration。学生改系统时间或直接调接口就能绕过。解决后端在交卷接口里强制校验。exam_record的start_time加上duration分钟和当前时间比较超时直接拒绝并标记为“超时未交”。前端倒计时用服务器时间校准不要用new Date()。// 交卷前的时间校验 Date deadline new Date(record.getStartTime().getTime() paper.getDuration() * 60 * 1000L); if (new Date().after(deadline)) { throw new BusinessException(考试已超时无法提交); }4.3 多选题判分把“AB”和“BA”判成不同答案现象学生选了 A 和 B标准答案也是 A 和 B但判分显示错误。原因字符串直接比较AB.equals(BA)返回 false。解决把答案字符串拆成字符数组排序后再比较。或者用Set做集合相等判断。// 多选题判分集合比较 SetString studentSet new HashSet(Arrays.asList(answer.getStudentAnswer().split())); SetString correctSet new HashSet(Arrays.asList(question.getAnswer().split())); boolean correct studentSet.equals(correctSet);4.4 并发交卷导致成绩被覆盖现象两个学生同时交卷A 的成绩变成了 B 的。原因判分方法里用了成员变量存totalScoreSpring 默认单例多线程共享导致数据串了。解决所有变量都定义在方法内部不要用成员变量。Service 类加Scope(prototype)也可以但更推荐无状态设计。这个坑我踩过两次血泪经验就是——Service 层永远不要存请求相关的状态。4.5 断线重连后答案重复插入现象学生网络闪断重连后答案表里同一道题出现两条记录。原因暂存接口没有做幂等每次同步都INSERT。解决exam_answer表加唯一索引UNIQUE KEY uk_record_question (record_id, question_id)插入用INSERT ... ON DUPLICATE KEY UPDATE。或者先DELETE再INSERT我一般用后者逻辑更清晰。5. 性能压测与防作弊的进阶技巧5.1 用 JMeter 模拟 200 人同时交卷系统能不能扛住压测说了算。我一般用 JMeter 建一个线程组200 个线程每个线程模拟登录、进入考试、答题、交卷的完整流程。重点看交卷接口的 TPS 和错误率。如果错误率超过 1%就得查连接池和慢 SQL。压测时把exam_record和exam_answer表清空用 CSV 文件提供 200 个学生账号。交卷接口的响应时间控制在 500 毫秒以内算合格。如果超过优先看判分逻辑是不是同步执行的改成异步后通常能降到 200 毫秒以下。5.2 防切屏与防复制的前端策略考试系统不做防作弊等于裸奔。我一般用visibilitychange事件监听切屏切一次记一次超过 3 次自动交卷。复制粘贴用oncopy和onpaste拦截但要注意——这些只能防君子防不了开发者工具。真要严格得用全屏模式加beforeunload拦截。// 切屏检测 let switchCount 0; document.addEventListener(visibilitychange, function() { if (document.hidden) { switchCount; if (switchCount 3) { alert(切屏超过3次系统将自动交卷); document.getElementById(submitBtn).click(); } } });5.3 判分结果的二次校验自动判分再准也可能有 bug。我习惯在判分完成后跑一次校验把所有exam_answer的got_score加起来和exam_record的score比对不一致就告警。这个校验用定时任务每天跑一次能发现大部分数据不一致问题。另外主观题批改后要重新计算总分。我一般把总分计算抽成一个独立方法recalcTotalScore(recordId)自动判分和人工批改都调它避免两处逻辑不一致。这套 SSM 学生在线考试系统我前后搭过三版第一版功能全但并发一塌糊涂第二版加了异步判分和断线重连第三版才把防作弊和压测补上。我的习惯是——每次上线前用 JMeter 跑一遍 200 并发跑通了才敢让学生用。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Spring Boot小区蔬菜水果商城系统:设计部署与答辩全攻略

Spring Boot小区蔬菜水果商城系统:设计部署与答辩全攻略

又到了毕业设计季,每年这个时候都能收到一堆“求一个能跑通的商城系统”的私信。这次说的“基于Spring Boot的小区蔬菜水果商城系统”,是一个很典型的Java Web毕设/课设选题:业务场景清晰、技术栈主流、扩展空间大。针对这类项目,…

2026/9/30 4:12:49 阅读更多 →
AI图片检测器实战:从扩散模型原理到免费工具识别伪造图像

AI图片检测器实战:从扩散模型原理到免费工具识别伪造图像

1. AI生成的图片已经多到让人防不胜防1.1 从"一眼假"到"几乎分不清"的进化大概两三年前,AI生成的图片还有个通病:细节经不起放大。手指出奇地多、耳环左右不对称、背景文字糊成一团,稍微盯两秒就能抓出破绽。那会儿大家都…

2026/9/30 4:12:49 阅读更多 →
商用电子秤选型指南:量程、精度、防水与校准要点

商用电子秤选型指南:量程、精度、防水与校准要点

1. 先搞清楚你现在到底在选什么秤很多朋友一上来就问“商用电子秤怎么选”,第一反应是看精度、看量程、看价格,这本质上没有错,但问题在于——你如果不先想清楚这台秤每天要面对什么使用场景,只看参数买东西,大概率是要…

2026/9/30 4:12:49 阅读更多 →

最新新闻

从数学定义到工程实现:指数函数exp的原理、精度与应用全解析

从数学定义到工程实现:指数函数exp的原理、精度与应用全解析

你是不是也被"EXP"这三个字母搞得头晕过?游戏里它是经验值,安全报告里它是漏洞利用代码,到了数学库文档里它又变成了指数函数。我这次要聊的是最后一种,也是日常编码里存在感最高、却很少有人认真拆解过的那个exp。它全…

2026/9/30 4:53:11 阅读更多 →
Python与人工智能:从零开始的实操路径与避坑指南

Python与人工智能:从零开始的实操路径与避坑指南

1. 从两个热搜词说起:Python和人工智能到底什么关系先把结论摆在前面:Python 和人工智能不是“绑定关系”,而是“恰好合拍”的关系。Python 是一门通用编程语言,人工智能是一个技术方向,两者之间没有必然的从属关系。但…

2026/9/30 4:53:11 阅读更多 →
DeepSeek证券研报自动化:从数据到文档的工程化生成链路

DeepSeek证券研报自动化:从数据到文档的工程化生成链路

简介:这份257页的PDF文档面向金融科技从业者、量化研究员与AI工程师,系统讲解如何用DeepSeek-R1构建证券研报自动化生成方案,解决人工研报撰写效率低、数据来源分散、专业术语难以统一等痛点。内容从多源异构金融数据预处理、财经文本清洗与向…

2026/9/30 4:53:11 阅读更多 →
Java+MySQL学生信息管理系统:JDBC增删改查与Swing界面完整实现

Java+MySQL学生信息管理系统:JDBC增删改查与Swing界面完整实现

简介:这份资源面向Java初学者与需要完成课程设计的学生,提供一套基于Java Swing与MySQL的学生信息管理系统实现方案,重点解决JDBC对学生数据的增删改查操作,适合作为课设参考或入门练手项目。压缩包内共1个PDF文件,约1…

2026/9/30 4:53:11 阅读更多 →
网上图书商城系统项目管理文档模板:增量模型与JSP技术栈全流程

网上图书商城系统项目管理文档模板:增量模型与JSP技术栈全流程

简介:这份《网上图书商城系统 软件项目管理》大作业文档,面向计算机相关专业学生及软件项目管理初学者,以网上图书商城为案例,完整呈现从合同签订到项目收尾的管理流程。资源包共1个doc文件,约297KB,内容按…

2026/9/30 4:53:11 阅读更多 →
Trae接入自定义大模型:从Base URL到API Key的完整配置指南

Trae接入自定义大模型:从Base URL到API Key的完整配置指南

Trae 接入自定义大模型这事,我琢磨了一晚上才算彻底搞明白。最近群里好几个朋友都在问:那个内置模型用着还行,但我想把 Trae 切到自己申请的 API Key 上,或者干脆跑本地模型,到底该怎么配?说实话刚打开设置…

2026/9/30 4:52:11 阅读更多 →

日新闻

Base64 图片头部特征识别:从文件头到格式判断的完整指南

Base64 图片头部特征识别:从文件头到格式判断的完整指南

1. 项目概述:为什么说看懂 base64 图片头部是基本功这几年跟 base64 打交道的机会越来越多,后端接口返回图片、前端渲染验证码、小程序里存小图、还有一些老系统导出报表,动不动就给你一段长到怀疑人生的 base64 字符串。很多人拿到字符串就直…

2026/9/30 0:00:35 阅读更多 →
Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

简介:本资源是一份面向Java初学者与课程设计学生的公交站牌广告灯箱管理系统毕业设计文档,聚焦城市公共广告资源信息化管理痛点,提供从需求分析到技术实现的完整方案。文档采用标准学术论文结构,含摘要、英文摘要、目录及五章正文…

2026/9/30 0:00:35 阅读更多 →
用 Redis Lua 构建大模型 API 多租户原子配额治理体系

用 Redis Lua 构建大模型 API 多租户原子配额治理体系

我去年年底接了一个内部 AI 平台的治理需求,背景很直接:公司把 DeepSeek、MiniMax 这类大模型 API 统一封装成内部网关,开放给几个业务团队用。结果第一个月账单出来,额度直接超了 4 倍。仔细查日志,发现原因并不复杂—…

2026/9/30 0:00:35 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 8:16:59 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 16:41:41 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/29 8:24:48 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/29 19:29:29 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/29 5:58:00 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/29 3:55:56 阅读更多 →