简介:这份资源是面向软件工程、计算机相关专业学生的JavaEE课程大作业完整方案,聚焦网上考试系统的设计与实现,适合需要完成期末项目、课程设计或自学JavaEE开发的学习者参考。压缩包共74个文件,约6.75MB,以16个java源文件、16个jsp页面、16个class编译文件为核心,配合css样式、gif与jpg图片资源,以及doc使用说明和项目报告文档,完整覆盖从源码到文档的交付内容。目前已有3476人学习下载,说明该方案在同类作业中具有较高的参考价值。资源包含可直接导入MyEclipse的工程结构,代码注释详细、难度适中,读者只需修改姓名、学号等个人信息即可运行使用;配套报告与使用说明文档则有助于理解系统模块划分与设计思路,为撰写实验报告和答辩准备提供便利,适合作为JavaEE入门到进阶阶段的项目实践参考。
1. 从一份“能跑起来”的 JavaEE 大作业说起
期末周前一周,某高校实验室里,A 同学抱着一个压缩包来找我:里面是一套网上考试系统的源码、一份设计报告和一份使用说明。他说老师要求“能登录、能出题、能考试、能判分”,但自己连数据库脚本在哪都没找到。我打开一看,典型的 JavaEE 课程设计结构:Servlet 接请求、JSP 渲染页面、JDBC 直连数据库,功能闭环完整,代码量不大但五脏俱全。这类网上考试系统设计,核心价值不在于技术多新,而在于它把“用户登录—题库管理—在线答题—自动判分—成绩查询”这条链路完整跑通了,适合拿来做课程设计复现、二次开发练手,也适合刚接触 Java Web 的同学照着理解一个请求从浏览器到数据库再返回的全过程。下面我就按“它是什么、怎么跑、怎么改”的顺序,把这套代码拆开讲清楚。
2. 项目定位与技术栈:一套典型的 Servlet + JSP + JDBC 三层结构
2.1 这个系统到底解决什么问题
网上考试系统的业务目标很明确:教师端能维护题库、组卷、查看成绩;学生端能登录、参加考试、提交后看到分数。这套大作业把角色分成管理员、教师、学生三类,管理员管用户和科目,教师管题目和试卷,学生只管答题。功能不算多,但每个角色都有独立的登录入口和操作页面,权限隔离靠 Session 里存的角色字段来判断。对于课程设计来说,这种“三角色 + 题库 + 考试 + 判分”的组合已经能覆盖 JavaEE 教学里要求的大部分知识点:Servlet 生命周期、JSP 内置对象、JDBC 增删改查、Session 与 Cookie、过滤器做登录拦截。
我一般会把这类项目定位成“教学型完整闭环”,它不是生产级系统,但结构清晰、依赖少、部署简单,拿来改造成自己的课程设计或者作为 Java Web 入门后的第一个完整项目,性价比很高。报告和使用说明的存在也说明它面向的是“要交作业、要答辩”的场景,所以代码里会有不少注释和固定写法的痕迹,这反而对初学者友好。
2.2 技术栈清单与选型逻辑
从源码结构看,这套系统没有用 Spring 全家桶,而是原生 Servlet + JSP + JDBC,构建工具大概率是 Maven,Web 容器用 Tomcat,数据库常见做法是 MySQL 5.7 或 8.0。前端没有独立框架,JSP 里混写 HTML 和少量 JavaScript,样式可能是手写 CSS 或者引了一个轻量库。这种选型在课程设计里非常普遍,原因是依赖少、启动快、老师环境里不用额外装一堆东西。
| 层次 | 技术 | 作用 |
|---|---|---|
| 表现层 | JSP + HTML/CSS/JS | 页面渲染与表单提交 |
| 控制层 | Servlet | 接收请求、调用业务、转发页面 |
| 数据层 | JDBC + DAO | 直连数据库执行 SQL |
| 会话管理 | HttpSession | 保存登录用户与角色 |
| 构建部署 | Maven + Tomcat | 打包 war 并部署 |
需要说明的是,原文没有给出具体版本号,所以我在复现时会按“这个场景下最稳妥的组合”来选:JDK 8、Tomcat 8.5、MySQL 5.7、Maven 3.6。这套组合在课程设计环境里兼容性最好,JSP 和 Servlet 的 API 也不会因为容器版本过高出现包名迁移问题。如果你本地已经是 JDK 17,也不是不能跑,但要注意 Tomcat 版本和javax.servlet与jakarta.servlet的包名差异,这是后面避坑章节要重点说的。
2.3 目录结构长什么样
解压后一般能看到这样的结构,不同人整理习惯略有差异,但核心目录跑不掉:
exam-system/ ├── src/main/java/ │ ├── controller/ # Servlet 控制器 │ ├── dao/ # 数据库访问对象 │ ├── model/ # 实体类(User、Question、Paper 等) │ └── util/ # 数据库连接工具类 ├── src/main/webapp/ │ ├── WEB-INF/ │ │ ├── web.xml # Servlet 映射与过滤器配置 │ │ └── lib/ # 第三方 jar(如 mysql-connector) │ ├── admin/ # 管理员页面 │ ├── teacher/ # 教师页面 │ ├── student/ # 学生页面 │ └── login.jsp # 登录入口 ├── sql/ # 建库建表脚本 ├── pom.xml # Maven 依赖 └── 使用说明.md / 设计报告.docx这个结构的关键在于controller和dao的分层:Servlet 只负责取参数、调 DAO、设属性、转发;DAO 里才是真正的 SQL。看代码时先看web.xml里的映射,能快速知道每个 URL 对应哪个 Servlet,再顺着 Servlet 找 DAO,链路就清楚了。sql目录是跑起来的前提,很多人翻车就翻在没先执行建表脚本。
3. 跑起来:从建库到登录成功的完整步骤
3.1 环境准备与数据库初始化
先把 JDK、Maven、Tomcat、MySQL 装好,版本按上一节说的组合来。数据库这一步最容易出问题,因为脚本里的库名、用户名、密码要和代码里的连接配置对上。常见做法是打开sql目录下的脚本,用命令行或图形工具执行:
# 登录 MySQL 后执行建库建表脚本 mysql -u root -p < sql/exam_system.sql # 确认表已创建 mysql -u root -p -e "use exam_system; show tables;"执行完应该能看到用户表、题目表、试卷表、成绩表等。如果脚本里没有CREATE DATABASE语句,需要先手动建库再导入。这里的关键参数是库名,后面 JDBC 连接串里的jdbc:mysql://localhost:3306/库名必须和它一致,否则启动后第一个查询就会报“Unknown database”。
3.2 改连接配置与打包部署
数据库访问的配置一般集中在util包下的连接工具类里,或者是一个db.properties。找到它,把用户名密码改成你本地的:
// DBUtil.java 常见写法 public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/exam_system?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; public static Connection getConnection() throws SQLException { // 驱动在 JDBC 4 之后可省略显式注册,但老项目常保留 try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("驱动未找到", e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码的逻辑很直白:加载驱动、拿连接。参数上要注意serverTimezone,MySQL 8 的驱动不设时区可能报时区错误;characterEncoding=utf8保证中文题目不乱码。改完后用 Maven 打包:
mvn clean package # 产物在 target/ 下,把 war 丢进 Tomcat 的 webapps cp target/exam-system.war $TOMCAT_HOME/webapps/启动 Tomcat 后访问http://localhost:8080/exam-system/login.jsp,能看到登录页就说明部署成功了一半。如果 404,先检查 war 包名和访问路径是否一致;如果 500,看 Tomcat 日志里的异常栈,多半是数据库连不上或驱动缺失。
3.3 登录链路与 Session 校验
登录是验证整套环境是否打通的最好入口。典型实现是LoginServlet接收表单,查用户表比对密码,成功则把用户对象放进 Session 并跳转到对应角色首页:
// LoginServlet.doPost 核心逻辑 String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { HttpSession session = request.getSession(); session.setAttribute("currentUser", user); session.setAttribute("role", user.getRole()); // 按角色转发到不同首页 if ("admin".equals(user.getRole())) { response.sendRedirect("admin/index.jsp"); } else if ("teacher".equals(user.getRole())) { response.sendRedirect("teacher/index.jsp"); } else { response.sendRedirect("student/index.jsp"); } } else { request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); }参数说明:currentUser存整个用户对象,后续页面取用户名显示;role单独存一份方便过滤器判断。这里有个细节,重定向用sendRedirect而不是forward,是为了避免刷新页面重复提交登录表单。登录成功后随便点几个菜单,如果每个页面都能正常显示数据,说明 DAO 层的查询也没问题,环境就算彻底跑通了。
4. 核心模块拆解:题库、组卷、答题与判分怎么落地
4.1 题库管理的增删改查
题库是整套系统的地基,题目表一般包含题干、选项、正确答案、科目、题型等字段。教师端的新增题目走一个QuestionServlet,根据action参数区分新增、删除、修改、列表查询,这是课程设计里很常见的“一个 Servlet 管一个模块”的写法:
// QuestionServlet.doGet 按 action 分发 String action = request.getParameter("action"); if ("list".equals(action)) { List<Question> list = questionDao.findAll(); request.setAttribute("questions", list); request.getRequestDispatcher("teacher/question_list.jsp").forward(request, response); } else if ("delete".equals(action)) { int id = Integer.parseInt(request.getParameter("id")); questionDao.deleteById(id); response.sendRedirect("question?action=list"); } else if ("add".equals(action)) { Question q = new Question(); q.setContent(request.getParameter("content")); q.setOptionA(request.getParameter("optionA")); // ... 其余选项与答案 questionDao.insert(q); response.sendRedirect("question?action=list"); }逻辑上就是“取参数—调 DAO—转发或重定向”。参数里action是分发开关,id用于定位记录。这种写法不够优雅,但对大作业来说足够直观。改造时可以把分发逻辑抽到一个 BaseServlet 里,用反射调方法,能少写一堆 if-else,这是后面二次开发可以动刀的地方。
4.2 组卷与考试页面的数据组装
组卷的本质是把若干题目和一张试卷关联起来。常见做法是试卷表存试卷基本信息,再用一张关联表存“试卷 ID—题目 ID”的对应关系。学生进入考试时,Servlet 先查试卷,再查关联题目,组装成一个列表放进 request,JSP 遍历渲染成答题表单:
// ExamServlet 加载试卷与题目 int paperId = Integer.parseInt(request.getParameter("paperId")); Paper paper = paperDao.findById(paperId); List<Question> questions = questionDao.findByPaperId(paperId); request.setAttribute("paper", paper); request.setAttribute("questions", questions); request.getRequestDispatcher("student/exam.jsp").forward(request, response);JSP 里用<c:forEach>或脚本片段遍历questions,每道题渲染成单选或判断题。这里要注意题目顺序,如果关联表没有排序字段,查出来的顺序可能不稳定,考试体验会打折。我一般会在关联表加一个sort_no,查询时order by sort_no,这是个小改动但很实用。
4.3 提交判分与成绩落库
判分逻辑是整套系统里最值得看的部分。学生提交后,Servlet 拿到每道题的选择,和题库里的正确答案比对,累加得分,最后把成绩写进成绩表:
// SubmitExamServlet 判分核心 List<Question> questions = questionDao.findByPaperId(paperId); int score = 0; for (Question q : questions) { String answer = request.getParameter("q_" + q.getId()); if (answer != null && answer.equals(q.getCorrectAnswer())) { score += q.getScore(); // 每题分值来自题库 } } scoreDao.insert(userId, paperId, score); response.sendRedirect("student/result.jsp?score=" + score);参数说明:q_ + 题目 ID是表单里每道题的 name,保证提交时能按题取答案;q.getScore()是每题分值,判分时累加。这里有个隐患,如果题目被删除而试卷关联没清理,判分时可能取不到题,所以删除题目时要同步清理关联表。成绩落库后跳结果页,学生就能看到分数。整套链路走完,这个项目的核心价值也就体现出来了:它把“出题—组卷—答题—判分—查分”串成了一个可运行的闭环。
5. 避坑与排查:跑这套代码最容易翻车的几个地方
5.1 中文乱码:现象是题目变问号
现象:登录后题目列表里中文显示成???或乱码。原因通常是三处编码不一致:数据库表字符集、JDBC 连接串、Servlet 响应编码。解决方法是建库时用utf8mb4,连接串加characterEncoding=utf8,并在 Servlet 里统一设request.setCharacterEncoding("UTF-8")和response.setContentType("text/html;charset=UTF-8")。这三处缺一处都可能翻车,我一般会写一个编码过滤器统一处理,省得每个 Servlet 重复写。
5.2 驱动报错:ClassNotFound 或时区异常
现象:启动后第一次查库就抛ClassNotFoundException: com.mysql.cj.jdbc.Driver,或者报时区错误。原因是WEB-INF/lib下没有 MySQL 驱动 jar,或者驱动版本和连接串不匹配。解决方法是确认pom.xml里引入了mysql-connector-java,打包后 war 的lib目录里能看到对应 jar;连接串补上serverTimezone=UTC。如果是 MySQL 5.x 的驱动,类名是com.mysql.jdbc.Driver,别混用。
5.3 404 与路径问题:war 包名决定访问前缀
现象:Tomcat 启动正常,但访问登录页 404。原因多半是 war 包名和访问路径不一致,比如包叫exam-system.war,访问却写成/exam/login.jsp。解决方法是访问路径严格按http://localhost:8080/包名/页面来,包名就是 war 去掉.war的部分。另外 JSP 里的相对路径在转发后可能错位,建议统一用${pageContext.request.contextPath}拼绝对路径。
5.4 登录后跳回登录页:过滤器把静态资源也拦了
现象:登录成功但点任何菜单都跳回登录页。原因是登录过滤器判断 Session 为空就重定向,但没放行登录页、CSS、JS 等资源,导致页面样式加载失败或循环跳转。解决方法是在过滤器里对login.jsp、/css/、/js/等路径直接放行,只拦截业务页面。这个坑很典型,看日志里重定向次数就能定位。
5.5 判分对不上:答案字段带空格或大小写不一致
现象:明明选对了却判 0 分。原因是题库里正确答案存的是"A",表单提交的是"a",或者数据库字段带了尾部空格。解决方法是在判分前统一trim()并转大写比较,建表时答案字段用定长或加约束。这类问题不报错,只能靠对答案,排查时先把提交参数和题库答案打日志对比,一眼就能看出来。
6. 二次开发:把这套大作业改成能拿得出手的作品
如果你不满足于交个作业,想把它改成简历上能说的项目,我建议从三个方向动刀,改动量可控但效果明显。第一是抽 BaseServlet,用反射替代 if-else 分发,代码立刻清爽一个档次;第二是给 DAO 层加一个简单的连接池,比如用 Druid 或 HikariCP 替换每次DriverManager.getConnection,这是性能上最直观的提升;第三是把判分逻辑抽成独立的 Service 类,方便加题型扩展,比如多选题、判断题的判分规则不同,用策略模式比堆 if 更好维护。
改造后的验证方法也很简单:先跑通原有登录和答题链路,确认没回归;再用一个脚本批量插入 100 道题,看列表分页和判分是否还正常;最后模拟两个学生同时提交,观察成绩表有没有串数据。下面这个表格可以帮你判断改动优先级:
| 改造点 | 改动量 | 收益 | 风险 |
|---|---|---|---|
| 抽 BaseServlet | 小 | 代码结构清晰 | 低 |
| 引入连接池 | 小 | 并发性能提升 | 中(配置错会连不上) |
| 判分策略化 | 中 | 易扩展题型 | 中(要回归测试) |
| 加分页 | 中 | 题库大时不卡 | 低 |
有个血泪经验:改连接池的时候,别忘了把关闭连接的代码从conn.close()改成“归还连接”,否则连接池很快被耗尽,表现为用一会儿就卡死。我一般会先在一个分支上改,跑通再合并,避免把能跑的版本改废。从那以后我每次动这类课程设计项目,都强制先把原始版本完整跑一遍并备份,再开始改,这个习惯帮我省了不少后悔药。希望这套拆解能帮到你,拿到源码后先照着第 3 章跑通,再按第 6 章挑一个点动手,基本就能把它变成自己的东西了。
本文还有配套的精品资源,点击获取