SSM架构的Java在线考试系统源码(含管理员、教师、学生三端完整功能与部署环境)
本文还有配套的精品资源,点击获取
简介:一套基于Spring+SpringMVC+MyBatis(SSM)框架开发的Java在线考试系统,开箱即用,适合作为本科毕业设计项目。包含JDK、Apache Tomcat 8.0.48、Eclipse项目配置及详细部署文档,数据库脚本齐全,目录结构规范(含WebRoot、src、database、conf等标准模块)。管理员可管理三类用户(增删改查)、维护考试科目、编排试卷、查看和导出成绩,并按0-60分、60-90分等分数段生成成绩统计柱状图;教师能自主维护题库、按题型和分值组卷、在线阅卷(客观题自动判分,主观题支持文字输入或图片上传并手动评分),导出班级总分表、各题型得分分析及合格人数分布;学生可修改个人信息、在线答题(选择题自动提交,主观题支持文本+图片作答)、考后即时查看总分、各题型得分、是否合格,以及错题回顾与对应解析。所有功能均经本地调试验证,无报错,可直接导入Eclipse运行。
1. 项目概述:为什么这个SSM考试系统值得你花时间细读?
如果你正在为本科毕业设计发愁,或者刚接手一个校内教学辅助系统的开发任务,又或者想系统性地梳理一次SSM框架在真实业务场景中的落地逻辑——那这套“三端在线考试系统”不是“又一个Demo”,而是一份经过真实调试、结构清晰、边界明确、功能闭环的工业级教学级参考实现。它不追求炫技的前端动画或微服务拆分,而是把重心牢牢钉在“业务可跑通、代码可读懂、部署可复现、答辩可讲清”这四件事上。我带过十几届毕业设计,见过太多学生卡在“功能堆砌但逻辑断裂”“环境配不起来”“数据库字段和Java实体对不上”这些看似琐碎却致命的问题上。而这套源码,从目录命名到SQL脚本注释,从Controller层参数校验到MyBatis动态SQL的嵌套写法,处处透着一种“被反复推演过”的克制感。
关键词里提到的“SSM考试系统”“Java在线考试”“毕业设计源码”“三端考试系统”,其实指向三个不同层次的需求:技术栈选型合理性(SSM是否还适用?)、业务建模完整性(考试流程是否覆盖教-学-管全链路?)、工程交付可行性(能不能三天内搭起来跑通?)。答案是肯定的。它用Spring做事务控制与IoC解耦,用SpringMVC处理角色路由与视图跳转,用MyBatis做轻量持久化——没有引入Spring Boot自动配置带来的黑盒感,也没有绕开Servlet原生机制去搞过度封装。这意味着你能看清每一层数据怎么流转:学生点击“提交试卷”,请求如何被DispatcherServlet捕获,怎样经HandlerMapping匹配到ExamController.submitExam(),参数如何绑定到ExamAnswer对象,Service层如何调用Dao执行insertBatch()批量插入答题记录,事务如何在@Transaction注解下生效,最终响应如何渲染成JSON返回给前端。这种“透明度”,对理解Web开发本质比任何Spring Boot Starter都管用。
更关键的是,它把“三端”真正做成了角色隔离而非页面复制。管理员看的是用户树形结构+科目分类树+成绩统计图表;教师看到的是题库管理表格+组卷拖拽式界面(虽然后端是静态表单,但字段设计已预留扩展性)+阅卷打分弹窗;学生面对的是倒计时器+单题锁定机制+图片上传预览区。三者共用同一套用户认证体系(基于session的简单RBAC),但菜单栏、权限拦截、数据查询范围完全由角色字段驱动。这不是靠if(role.equals(“teacher”))硬编码出来的,而是通过Spring Security的表达式语言(如@PreAuthorize(“hasRole(‘TEACHER’)”))或自定义拦截器统一管控。所以你看源码时,不会陷入“这个按钮为什么在这里出现”的困惑,而是能顺着权限链一路追到数据库的role_code字段定义。这种设计思维,远比学会某个新框架更重要。
2. 整体架构与设计思路:为什么是SSM?为什么是这个分层?
2.1 技术选型背后的现实权衡
很多人看到“SSM”第一反应是“过时了”,尤其当周围都在聊Spring Boot、Vue3、微服务。但回到毕业设计这个具体场景,技术选型从来不是越新越好,而是“够用、可控、可解释”。我们来拆解这套系统为何坚定选择SSM组合:
Spring:承担核心容器与事务管理。系统中所有跨模块操作——比如教师发布试卷后需同时更新exam_info表、exam_question_relation表、并初始化student_exam_record表——都包裹在@Service类的@Transactional方法中。实测发现,当手动抛出RuntimeException时,MyBatis的insert操作会回滚,而Tomcat日志里清晰打印出“Rolling back transaction”,这种“看得见摸得着”的事务行为,比Spring Boot里一堆autoconfigure类更容易向答辩老师讲清楚原理。
SpringMVC:解决HTTP协议适配问题。它不像Spring Boot Web那样默认启用RESTful风格,而是显式声明@RequestMapping(“/admin/user/list”),配合@ResponseBody返回JSON。这种“显式优于隐式”的设计,让初学者能直观理解URL路径、HTTP方法、Java方法之间的映射关系。比如学生端的/exam/start接口,对应ExamController.startExam(),方法参数里有@RequestParam(“examId”) Long examId,这种写法比用@PathVariable或RequestBody更直白,也避免了新手因JSON格式错误导致400错误的调试困境。
MyBatis:作为ORM层,它没有Hibernate那种全自动映射的“魔法感”,而是要求你手写SQL。这恰恰是优势:系统中所有复杂查询——如“统计某科目下各分数段人数”——都通过 标签动态拼接条件, 遍历集合生成IN语句, 实现多条件分支。当你在database目录下打开score_statistics.sql,再对照src/main/resources/mapper/ScoreMapper.xml里的
- ,就能立刻明白“0-60分”这个区间是如何被翻译成SQL里的BETWEEN 0 AND 60的。这种SQL与Java代码的强对应关系,是理解数据库优化的第一课。 提示:不要被“SSM过时”的说法带偏。Spring Boot本质是SSM的封装,底层仍是DispatcherServlet和SqlSessionFactory。先吃透SSM的原始形态,再学Spring Boot才能知其所以然。就像学开车先练手动挡,再碰自动挡才不会变成“只会踩油门”。 2.2 分层结构解析:为什么src目录下要分controller/service/dao/mapper? 打开src目录,你会看到标准的四层结构:controller负责接收请求,service封装业务逻辑,dao定义数据访问接口,mapper存放SQL映射文件。这不是为了凑架构图好看,而是为了解决三个实际问题: 职责分离防污染:学生提交主观题答案时,Controller只做参数校验(如检查图片大小是否超2MB),把clean后的ExamAnswer对象交给Service。Service层则负责调用多个Dao:先查题目是否存在(QuestionDao.getQuestionById()),再存答题记录(AnswerDao.insertAnswer()),最后更新考试状态(ExamDao.updateStatus())。如果把这些逻辑全塞进Controller,一旦阅卷规则变更(比如新增“图片需带水印”校验),你就得改三个地方;而按分层设计,只需在Service里加一行校验代码,Controller和Dao完全不用动。 测试友好性:Service层可以脱离Web容器单独测试。比如写一个JUnit测试类,用Mockito模拟QuestionDao返回假数据,验证ExamService.calculateScore()方法是否正确计算了选择题得分。这种单元测试在毕业答辩时展示,比说“我测试过了”有力得多。 SQL集中管理:所有SQL都在mapper目录下的XML文件里,而不是散落在Java代码中。比如统计教师所教班级平均分的SQL,就放在TeacherMapper.xml里。当需要优化查询性能时,DBA可以直接拿到这个文件分析执行计划,而不用翻遍几十个Java类去找拼接SQL的StringBuilder。 注意:这套源码的mapper文件命名非常规范,如AdminUserMapper.xml对应管理员用户管理,ExamPaperMapper.xml对应试卷管理。建议你在导入Eclipse后,右键每个XML文件选择“Open With > MyBatis Mapper Editor”,能直接看到SQL高亮和参数提示,这是MyBatis官方插件提供的实用功能。 2.3 数据库设计逻辑:一张user表如何支撑三端角色? 系统只用一张user表实现三端权限,字段设计极具教学价值: CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(100) NOT NULL COMMENT 'BCRYPT加密密码', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `role_code` varchar(20) NOT NULL COMMENT '角色编码:ADMIN/TEACHER/STUDENT', `status` tinyint(1) DEFAULT '1' COMMENT '状态:1启用0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 关键在role_code字段。它不是用三个布尔字段(is_admin, is_teacher, is_student)表示角色,而是用单值枚举。这样做的好处是: - 查询高效:查所有教师只需SELECT * FROM user WHERE role_code = 'TEACHER',避免了多字段OR查询的索引失效风险; - 扩展性强:未来增加“助教”角色,只需新增role_code=’ASSISTANT’,无需修改表结构; - 权限收敛:所有角色判断逻辑集中在ShiroFilter或自定义拦截器里,比如if ("ADMIN".equals(user.getRoleCode())) { allowAll(); } else if ("TEACHER".equals(user.getRoleCode())) { allow("/teacher/**"); },比分散在各个Controller里写if判断更安全。 实操心得:我在帮学生调试时发现,90%的权限问题源于role_code字段值写错。比如数据库里存的是”teacher”(小写),而Java代码里写的是”TEACHER”(大写)。建议在User实体类的setRoleCode()方法里强制toUpperCase(),并在数据库插入脚本中统一用大写,从源头杜绝这类低级错误。 3. 核心功能实现细节:从登录到成绩分析的完整链路 3.1 认证授权流程:Session如何承载三端身份? 系统采用最朴素的Session机制,而非JWT或OAuth2。这并非技术落后,而是精准匹配毕业设计场景——无需引入Redis存储Token,也不用处理Token刷新等复杂逻辑。整个流程如下: 用户在login.jsp输入账号密码,表单POST到/LoginController.login(); Controller调用UserService.login(username, password),该方法先查user表,再用BCryptPasswordEncoder.matches()校验密码; 校验成功后,将User对象存入HttpSession:session.setAttribute("currentUser", user); 后续所有请求,通过自定义拦截器CheckLoginInterceptor检查session中是否存在”currentUser”,不存在则重定向到登录页。 这里有个易被忽略的细节:拦截器的排除路径配置。在spring-mvc.xml中,<mvc:interceptors>节点下必须明确排除静态资源和登录接口: <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login/**"/> <mvc:exclude-mapping path="/static/**"/> <mvc:exclude-mapping path="/error"/> <bean class="com.exam.interceptor.CheckLoginInterceptor"/> </mvc:interceptor> 否则会出现“未登录却无法访问login.jsp”的死循环。我见过太多学生因为漏写<mvc:exclude-mapping>,在Tomcat日志里看到无限重定向的302记录,折腾半天才发现是拦截器配置问题。 提示:学生端答题时,系统会校验当前考试是否处于“可作答时间”。这个逻辑不在前端JS里做,而是在ExamController.startExam()方法开头,通过Date now = new Date(); if (now.before(exam.getStartTime()) || now.after(exam.getEndTime())) { throw new RuntimeException("考试未开始或已结束"); }实现。这种服务端强校验,比前端倒计时更可靠。 3.2 题库与组卷逻辑:如何用一张question表支持多种题型? 题库设计是系统最精巧的部分。question表结构如下: CREATE TABLE `question` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `content` text NOT NULL COMMENT '题目内容,支持HTML', `type` tinyint(2) NOT NULL COMMENT '题型:1单选2多选3判断4填空5简答', `answer` text COMMENT '标准答案,JSON格式存储', `score` int(5) NOT NULL DEFAULT '1' COMMENT '分值', `subject_id` bigint(20) NOT NULL COMMENT '所属科目ID', `difficulty` tinyint(2) DEFAULT '2' COMMENT '难度:1简单2中等3困难', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 关键点在于answer字段存储JSON。例如一道单选题的答案存为{"correct":"A","options":["A. Java","B. Python","C. C++"]},而简答题的答案则是{"correct":"面向对象是一种编程范式,强调封装、继承、多态"}。这样设计的好处是: - 查询统一:无论什么题型,查题都用同一个SQL:SELECT * FROM question WHERE subject_id = ? AND type = ?; - 判分灵活:客观题判分时,解析answer JSON取出correct字段,与学生答案比对;主观题则交由教师在后台手动评分,系统只记录教师输入的分数; - 扩展友好:未来增加“编程题”,只需在answer字段存入{"testCases":[{"input":"1 2","output":"3"}]},判分逻辑单独写Service方法即可,不影响现有结构。 组卷功能在TeacherController.createPaper()中实现。它接收前端传来的JSON数组,如[{"questionId":101,"score":2},{"questionId":102,"score":5}],然后循环调用PaperService.addQuestionToPaper(paperId, questionId, score)。这个方法内部执行两条SQL:先插入paper_question_relation关联表,再更新question表的used_count字段(统计题目被使用次数)。这种“先关连后计数”的顺序,保证了即使组卷中途失败,也不会出现题目被计入但未关联到试卷的脏数据。 3.3 主观题图片上传:如何安全处理学生上传的图片? 学生答题时可上传图片,这是系统区别于纯文本考试的关键。实现方案采用传统表单上传(非Ajax),核心在ExamController.submitAnswer(): @RequestMapping(value = "/submitAnswer", method = RequestMethod.POST) public String submitAnswer( @RequestParam("examId") Long examId, @RequestParam("answers") String answersJson, @RequestParam(value = "images", required = false) MultipartFile[] images, HttpServletRequest request) { // 1. 解析JSON答题数据 List<AnswerDto> answerList = JSON.parseArray(answersJson, AnswerDto.class); // 2. 处理图片上传 List<String> imagePaths = new ArrayList<>(); if (images != null && images.length > 0) { for (MultipartFile image : images) { if (!image.isEmpty()) { String fileName = System.currentTimeMillis() + "_" + image.getOriginalFilename(); String uploadPath = request.getSession().getServletContext() .getRealPath("/upload/images/"); File uploadDir = new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); image.transferTo(new File(uploadPath + fileName)); imagePaths.add("/upload/images/" + fileName); } } } // 3. 保存答题记录(含图片路径) answerService.saveAnswers(examId, answerList, imagePaths); return "redirect:/exam/result?examId=" + examId; } 这里有几个必须注意的安全点: - 文件类型校验:代码中缺失了对MultipartFile.getContentType()的检查。实际部署时,应在上传前添加if (!image.getContentType().startsWith("image/")) { throw new RuntimeException("仅支持图片格式"); }; - 路径穿越防护:image.getOriginalFilename()可能包含../,需用FilenameUtils.getName()过滤; - 存储位置隔离:上传目录/upload/images/不在WebRoot下,而是映射到Tomcat的webapps目录外,避免用户通过URL直接访问恶意文件。 实操心得:本地调试时,图片上传路径容易出错。建议在web.xml中配置虚拟路径映射: xml <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/upload/*</url-pattern> </servlet-mapping> 这样/upload/images/abc.jpg就能被Tomcat静态资源处理器正确响应,无需额外写Servlet。 3.4 成绩统计图表:柱状图数据如何从SQL生成? 管理员端的成绩统计功能,表面看是ECharts画图,底层却是SQL聚合的胜利。核心查询语句在ScoreMapper.xml中: <select id="getScoreDistribution" resultType="map"> SELECT CASE WHEN score BETWEEN 0 AND 59 THEN '0-59' WHEN score BETWEEN 60 AND 79 THEN '60-79' WHEN score BETWEEN 80 AND 89 THEN '80-89' ELSE '90-100' END as scoreRange, COUNT(*) as count FROM student_exam_record WHERE exam_id = #{examId} GROUP BY CASE WHEN score BETWEEN 0 AND 59 THEN '0-59' WHEN score BETWEEN 60 AND 79 THEN '60-79' WHEN score BETWEEN 80 AND 89 THEN '80-89' ELSE '90-100' END ORDER BY scoreRange </select> 这个SQL的精妙之处在于: - 使用CASE WHEN做分数段分组,避免了在Java层循环判断的性能损耗; - GROUP BY子句必须与SELECT中的CASE表达式完全一致,否则MySQL会报错(严格模式下); - 返回resultType=”map”,让MyBatis自动映射为HashMap,Controller中可直接JSONArray.fromObject(list)转成JSON供前端ECharts使用。 前端chart.js的初始化代码在admin-score-statistics.jsp中,关键配置: option = { tooltip: {trigger: 'item'}, legend: {data: ['人数']}, xAxis: {type: 'category', data: ['0-59','60-79','80-89','90-100']}, yAxis: {type: 'value'}, series: [{ name: '人数', type: 'bar', data: [${scoreData[0].count}, ${scoreData[1].count}, ${scoreData[2].count}, ${scoreData[3].count}] }] }; 注意${scoreData[i].count}是JSP EL表达式,它依赖Controller在Model中放入的scoreData列表。这种“后端计算+前端渲染”的分工,比纯Ajax异步加载更符合SSM的同步渲染哲学。 4. 部署与运行全流程:从零开始搭建可运行环境 4.1 环境准备清单与版本锁定 资源包中明确列出所需软件:JDK、Apache Tomcat 8.0.48、Eclipse。这个版本组合不是随意指定,而是经过兼容性验证的黄金搭配: 组件 版本 选择理由 JDK 1.8.0_202 Tomcat 8.0.x最低要求JDK 1.7,但MyBatis 3.4+推荐JDK 1.8;1.8.0_202是长期支持版,避免新版本GC策略变动影响稳定性 Tomcat 8.0.48 这是Tomcat 8.0系列最后一个稳定版,修复了8.0.30-8.0.47间所有已知漏洞;且与Spring 4.3.x完美兼容,不会出现java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.isAsyncStarted()这类经典报错 Eclipse Mars.2 (4.5.2) 资源包中.project文件指定了<nature>org.eclipse.jdt.core.javanature</nature>,此版本Eclipse对Java 8语法支持成熟,且内置Maven插件无需额外安装 提示:切勿自行升级Tomcat到9.x!虽然Tomcat 9支持Servlet 4.0,但本系统web.xml中声明的是<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">,强行升级会导致org.apache.catalina.core.StandardContext.startInternal One or more Filters failed to start错误。 4.2 Eclipse项目导入五步法 很多学生卡在“项目导入后满屏红叉”,根本原因是没理解Eclipse的构建路径机制。以下是经过验证的导入步骤: 解压资源包:将网络在线考试系统所需软件以及程序包.zip解压到无中文路径的目录,如D:\exam-system\; 启动Eclipse:使用与资源包匹配的Mars.2版本,首次启动时Workspace设为D:\exam-system\workspace; 导入项目:File → Import → Existing Projects into Workspace → Browse选择D:\exam-system\vVOHXi2nIdj6uJWewNFI-master-cd590f2f6b9b33692a9e6226a9b8744bdeb80fa5目录; 配置JDK:右键项目 → Properties → Java Build Path → Libraries → Remove掉所有JRE System Library,点击Add Library → JRE System Library → Workspace default JRE(确保是1.8); 配置Tomcat:Window → Preferences → Server → Runtime Environments → Add → Apache → Tomcat v8.0 → Browse选择D:\exam-system\apache-tomcat-8.0.48目录。 此时项目应不再报错。若仍有红叉,检查src/main/resources/jdbc.properties中的数据库连接配置是否已按你本地MySQL修改。 4.3 数据库初始化实操指南 资源包中database目录包含exam_system.sql脚本。执行步骤: 启动MySQL 5.7(推荐使用免安装版,避免服务冲突); 创建数据库:CREATE DATABASE exam_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;; 执行脚本:用MySQL Workbench或命令行mysql -u root -p exam_system < D:\exam-system\database\exam_system.sql; 验证数据:执行SELECT COUNT(*) FROM user; 应返回至少3条记录(admin/teacher/student各一)。 注意:脚本中创建用户表时指定了ENGINE=InnoDB,这是必须的!因为系统中有多张表存在外键约束(如student_exam_record.exam_id REFERENCES exam_info(id))。若误用MyISAM引擎,外键将失效,导致删除试卷时关联答题记录未被级联删除,产生脏数据。 4.4 Tomcat部署与启动排错 部署到Tomcat的正确姿势: 在Eclipse中,右键项目 → Run As → Run on Server → 选择已配置的Tomcat 8.0; 启动后观察Console输出,关键成功标志是: INFO: Starting ProtocolHandler ["http-nio-8080"] INFO: Server startup in 5678 ms 浏览器访问http://localhost:8080/exam-system/login.jsp,出现登录页面即成功。 常见启动失败原因及解决方案: 现象 原因 解决方案 Console报java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet Spring MVC jar包未部署到WEB-INF/lib 检查项目Properties → Deployment Assembly,确保Maven Dependencies已勾选并映射到/WEB-INF/lib 登录时报HTTP Status 404 – /exam-system/login Context Path配置错误 右键项目 → Properties → Web Project Settings → Context root改为exam-system(与资源包中web.xml的一致) 控制台刷屏WARN: SQL Error: 1054, SQLState: 42S22 数据库字段名与实体类属性名不匹配 对照User.java的private String realName;和user表的real_name字段,在MyBatis映射文件中确认<result property="realName" column="real_name"/>已正确配置 5. 常见问题与实战排查技巧:那些文档里不会写的坑 5.1 “登录成功但跳转到空白页”的真凶 现象:输入admin/admin123后,Console显示Login success,但浏览器停留在/login路径,页面空白无报错。 排查过程: 1. 打开浏览器开发者工具(F12),切换到Network标签,发现/login请求返回302,但重定向目标/admin/index.jsp返回404; 2. 检查src/main/webapp/WEB-INF/web.xml,发现<welcome-file-list>中配置的是<welcome-file>index.jsp</welcome-file>,但实际首页在/admin/index.jsp; 3. 根本原因:SpringMVC的ViewResolver配置为InternalResourceViewResolver,其prefix设为/WEB-INF/jsp/,suffix为.jsp。而Controller中return "admin/index";会被解析为/WEB-INF/jsp/admin/index.jsp,但资源包中该文件实际路径是/src/main/webapp/admin/index.jsp。 解决方案:修改spring-mvc.xml中ViewResolver配置: <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean> 这样return "admin/index"就映射到/admin/index.jsp,与物理路径一致。 5.2 “图片上传后显示404”的路径迷局 现象:学生上传图片后,答题页面显示<img src="/upload/images/1678901234_test.png">,但浏览器控制台报404。 根因分析: - Tomcat默认不提供/upload路径的静态资源服务; - request.getSession().getServletContext().getRealPath("/upload/images/")返回的是绝对路径,如D:\exam-system\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\exam-system\upload\images\; - 但浏览器请求的是相对URL /upload/images/xxx.png,Tomcat找不到对应Servlet处理。 终极解法(二选一): - 方案A(推荐):在web.xml中配置DefaultServlet处理/upload路径: xml <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/upload/*</url-pattern> </servlet-mapping> 并确保上传目录位于src/main/webapp/upload/下(而非动态创建的临时目录); - 方案B:改用Spring ResourceHandler,在spring-mvc.xml中添加: xml <mvc:resources mapping="/upload/**" location="file:D:/exam-system/upload/" /> 同时修改上传代码,将图片存到D:/exam-system/upload/images/固定路径。 5.3 “成绩统计图表不显示数据”的JSON陷阱 现象:管理员进入成绩统计页,ECharts显示空白,Console报Uncaught ReferenceError: scoreData is not defined。 调试发现:Controller中model.addAttribute("scoreData", list);已执行,但JSP中${scoreData}为空。 真相:scoreData是一个List>,而JSP EL表达式${scoreData[0].count}在JSP 2.0规范中不支持Map的key访问。必须改用JSTL标签: <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:forEach items="${scoreData}" var="item" varStatus="status"> ${item.scoreRange} : ${item.count} </c:forEach> 或者在Controller中将List转为JSON字符串: model.addAttribute("scoreDataJson", JSONArray.fromObject(scoreData).toString()); JSP中用var data = ${scoreDataJson};直接赋值给JavaScript变量。 5.4 “教师阅卷时主观题分数不保存”的事务盲区 现象:教师在/teacher/grade页面给简答题打了8分,点击“保存”,页面刷新后分数变回0。 日志追踪发现:GradeService.saveGrade()方法执行了update语句,但数据库未更新。 深入排查: - 查看GradeService类,发现方法上缺少@Transactional注解; - 检查Spring配置,发现事务管理器<tx:annotation-driven>已启用,但Service类未被Spring容器管理(缺少@Service注解); - 原来GradeService是new出来的普通Java对象,而非Spring Bean,因此@Transactional无效。 修复方案: 1. 在GradeService类上添加@Service注解; 2. 确保spring-context.xml中已扫描该包:<context:component-scan base-package="com.exam.service" />; 3. 在Controller中用@Autowired GradeService gradeService;注入,而非new GradeService()。 最后分享一个小技巧:在Tomcat启动后,访问http://localhost:8080/exam-system/actuator(如果集成了Spring Boot Actuator)或http://localhost:8080/exam-system/spring-mvc-servlet.xml(查看实际加载的配置),能快速定位配置是否生效。不过本系统未集成Actuator,最朴实的办法是——在DispatcherServlet初始化时加一行日志:System.out.println("SpringMVC initialized with " + handlerMappings.size() + " handlers");,看到这行输出,就证明MVC容器已启动成功。 这套系统真正的价值,不在于它实现了多少酷炫功能,而在于它用最朴素的技术组合,把一个完整业务闭环的所有毛细血管都暴露给你看。从数据库建表时一个字段的命名斟酌,到Controller里一行异常处理的取舍,再到部署时Tomcat一个配置项的含义,每一个细节都在回答一个问题:“如果让我从零开始做一个考试系统,我该怎么思考?” 这种思考方式,远比记住某个API更重要。 本文还有配套的精品资源,点击获取 简介:一套基于Spring+SpringMVC+MyBatis(SSM)框架开发的Java在线考试系统,开箱即用,适合作为本科毕业设计项目。包含JDK、Apache Tomcat 8.0.48、Eclipse项目配置及详细部署文档,数据库脚本齐全,目录结构规范(含WebRoot、src、database、conf等标准模块)。管理员可管理三类用户(增删改查)、维护考试科目、编排试卷、查看和导出成绩,并按0-60分、60-90分等分数段生成成绩统计柱状图;教师能自主维护题库、按题型和分值组卷、在线阅卷(客观题自动判分,主观题支持文字输入或图片上传并手动评分),导出班级总分表、各题型得分分析及合格人数分布;学生可修改个人信息、在线答题(选择题自动提交,主观题支持文本+图片作答)、考后即时查看总分、各题型得分、是否合格,以及错题回顾与对应解析。所有功能均经本地调试验证,无报错,可直接导入Eclipse运行。 本文还有配套的精品资源,点击获取
