news 2026/10/4 1:31:01

JavaWeb考试系统源码解析:MD5加密、分页与权限实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb考试系统源码解析:MD5加密、分页与权限实战

简介:这是一套面向高校计算机相关专业学生的JavaWeb课程设计大作业完整项目,主题为Web电脑考试系统,适合初学者作为课程设计参考,也便于有基础的开发者进行二次开发。项目采用JavaWeb技术栈实现,涵盖登录认证、权限过滤、考试管理、成绩统计等核心业务模块,代码结构规范、易于维护与拓展。压缩包共345个文件,约46.27MB,包含45个java源文件、29个jsp页面、27个jar依赖包、27个js脚本以及大量docx项目文档,另附sql数据库脚本、war部署包与xls导入模板,覆盖从源码到文档的完整交付内容。系统功能较为丰富,支持双层MD5加密、过滤器权限保护、管理员与老师身份一键切换、老师评分评语、成绩报表与及格率统计、分页展示与模糊查询、学生名单表格导入、考试倒计时及Ajax通知提醒等,并配有详细使用说明。目前已有2955人学习下载,可作为课程设计选题落地的实用参考。

1. 从一份 JavaWeb 考试系统源码说起:它到底能跑出什么效果

如果你正在找一份能直接跑起来、结构清晰、还带数据库的 JavaWeb 课程设计,那这份「web 电脑考试系统」源码包值得先看一眼。它不是那种只有登录页和空壳后台的演示项目,而是把管理员、老师、学生三种角色的完整业务链路都串了起来:老师出题、学生在线答题、系统倒计时收卷、老师评分写评语、成绩报表按区间统计及格率,甚至连学生文件打包下载和 xls 名单导入都做了。技术栈是经典的 Servlet + JSP + JDBC + MySQL,配合 Filter 做权限拦截,DaoFactory 做数据访问层解耦,PageInfo 封装分页。对初学者来说,这是一份能照着理解 MVC 分层和请求流转的完整案例;对熟手来说,它的权限模型、加密方案和分页封装都有可二次开发的余地。源码包里带了数据库脚本和项目文档,解压后按说明导入就能跑,适合课程设计答辩、JavaWeb 期末大作业,或者单纯想拆一个真实项目练手的人。

2. 环境搭建与数据库导入:把项目从 zip 跑到登录页

2.1 运行环境选型与依赖确认

这份源码是典型的「老派但稳」的 JavaWeb 项目,没有用 Spring Boot 那套自动装配,所以环境配置反而更透明。我一般会先确认三件事:JDK 版本、Tomcat 版本、MySQL 版本。源码里的 class 文件是编译过的,但项目文档里通常附了源码目录,建议直接用源码重新编译,避免 class 版本和本地 JDK 不匹配导致UnsupportedClassVersionError。

推荐组合是 JDK 8 或 JDK 11、Tomcat 8.5 或 9.0、MySQL 5.7 或 8.0。为什么不用 JDK 17 以上?因为老项目里如果用了javax.servlet而不是jakarta.servlet,Tomcat 10 会直接报类找不到,这是最常见的翻车点。MySQL 8.0 要注意驱动包版本,com.mysql.cj.jdbc.Driver和老的com.mysql.jdbc.Driver写法不同,连接串还要加时区和 SSL 参数。

在 IDEA 里导入时,选「Existing Sources」或直接 Open 项目根目录,然后手动配置 Artifacts 和 Tomcat Server。具体步骤是:File → Project Structure → Modules 确认源码目录标记为 Sources,Web 目录标记为 Web Resource Directory;然后在 Run/Debug Configurations 里新增 Tomcat Local,Deployment 里加上war exploded。这一步做完,项目基本就能识别了。

2.2 数据库导入与连接配置

数据库是这份项目的核心,所有题目、用户、成绩、通知都存在 MySQL 里。源码包里的.sql文件一般包含建库、建表和初始数据。导入时我习惯先用命令行确认字符集,避免中文乱码:

# 登录 MySQL,注意 -p 后面不要跟密码,回车后再输入 mysql -u root -p # 创建数据库并指定 utf8mb4,兼容性比 utf8 好 CREATE DATABASE exam_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后用 source 导入 sql 文件,路径换成你解压后的实际位置 exit mysql -u root -p exam_system < /path/to/exam_system.sql

导入完成后,用SHOW TABLES;确认表数量,通常会有student、teacher、admin、exam、question、score、notice、file这几张核心表。接着改项目里的数据库配置文件,一般在src/db.properties或src/c3p0-config.xml里。如果是 properties 文件,重点改这四项:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/exam_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=你的密码

参数说明:useUnicode和characterEncoding保证中文不乱码,serverTimezone解决 MySQL 8.0 时区报错,useSSL=false去掉本地开发时的 SSL 警告。改完配置后启动 Tomcat,访问http://localhost:8080/项目名/,看到登录页就说明数据库连通了。如果报Access denied,检查密码和远程权限;如果报Unknown database,说明库名写错或没导入成功。

2.3 登录页与权限过滤器的初始化验证

登录页是这份项目的门面,摘要里提到「精美炫酷」,实际看下来是用了 CSS 渐变和背景图,不算复杂但足够体面。真正要验证的是登录后的权限流转。项目里有个PermissionFilter.class,它拦截所有请求,根据 session 里的角色判断能不能访问目标资源。测试时我建议按这个顺序走一遍:

  1. 用管理员账号登录,确认能进后台、能切换老师身份;
  2. 用老师账号登录,确认能出题、评分、看报表;
  3. 用学生账号登录,确认能答题、看通知、下载文件;
  4. 直接访问未登录状态下的/admin/xxx路径,确认被踢到nopriviliage页面。

这一步能过,说明 Filter 配置和 web.xml 里的映射没问题。常见坑是 Filter 的url-pattern写成/*后把登录页也拦了,导致死循环,需要在 Filter 里放行登录和静态资源路径。

3. 核心模块拆解:MD5 加密、分页封装与 DaoFactory

3.1 32 位字段截取式双层 MD5 加密的实现逻辑

摘要里写的「32 位 字段截取式 双层 MD5 加密」听起来玄乎,拆开看其实是一个很实用的密码存储方案。普通 MD5 直接对密码做一次哈希,容易被彩虹表撞库;这份项目做了两层:第一层对原始密码加盐后 MD5,第二层对第一层结果再截取部分字段拼接后二次 MD5。这样即使数据库泄露,攻击者拿到的也不是标准 MD5 值,反查成本高很多。

核心代码逻辑大概是这样:

public class MD5Util { // 第一层:原始密码 + 固定盐值,做一次 MD5 public static String encodeOnce(String password, String salt) { String base = password + salt; return DigestUtils.md5Hex(base); // 返回 32 位小写十六进制 } // 第二层:截取第一层结果的前 16 位和后 16 位交叉拼接,再 MD5 public static String encodeTwice(String password, String salt) { String first = encodeOnce(password, salt); // 字段截取:前 16 位 + 后 16 位反转拼接 String mixed = first.substring(0, 16) + new StringBuilder(first.substring(16)).reverse(); return DigestUtils.md5Hex(mixed); } }

逻辑说明:encodeOnce负责加盐哈希,盐值可以存在配置文件里,每个项目不同;encodeTwice做字段截取和反转,目的是打乱哈希结构,让第二层结果和第一层没有直观对应关系。参数上,盐值建议用固定字符串加用户注册时间戳,但这份源码里为了简化,用的是全局固定盐。验证时可以在注册和登录两处都调用encodeTwice,对比结果是否一致。注意 MD5 本身不是加密而是摘要,这套方案提升的是撞库难度,不是绝对安全,课程设计够用,生产环境建议换 BCrypt。

3.2 PageInfo 分页封装与模糊查询的配合

分页是这份项目里复用率最高的模块。PageInfo.class一般封装了当前页、每页条数、总记录数、总页数、数据列表这几个字段。所有展示处支持分页,所有搜索处支持模糊查询,这两句话落到代码里就是:Dao 层先用COUNT(*)查总数,再用LIMIT查当前页数据,Service 层把结果塞进 PageInfo 返回给 JSP。

一个典型的分页查询方法:

public PageInfo<Student> getStudentPage(int pageNum, int pageSize, String keyword) { PageInfo<Student> page = new PageInfo<>(); page.setPageNum(pageNum); page.setPageSize(pageSize); // 模糊查询条件,keyword 为空时查全部 String like = (keyword == null || keyword.isEmpty()) ? "%" : "%" + keyword + "%"; // 第一步:查总记录数 String countSql = "SELECT COUNT(*) FROM student WHERE name LIKE ? OR sno LIKE ?"; int total = queryRunner.count(countSql, like, like); // 第二步:查当前页数据,LIMIT 第一个参数是偏移量 String dataSql = "SELECT * FROM student WHERE name LIKE ? OR sno LIKE ? LIMIT ?, ?"; List<Student> list = queryRunner.query(dataSql, new BeanListHandler<>(Student.class), like, like, (pageNum - 1) * pageSize, pageSize); page.setTotal(total); page.setList(list); page.setTotalPage((int) Math.ceil(total * 1.0 / pageSize)); return page; }

参数说明:pageNum从 1 开始,(pageNum - 1) * pageSize是 SQL 偏移量,写错会导致第一页重复或跳页;pageSize建议 10 到 20,太大影响渲染速度。模糊查询的LIKE两边加%是全模糊,如果数据量大建议改成右模糊keyword + "%"以命中索引。JSP 页面里用 JSTL 遍历page.list,分页条根据page.totalPage和page.pageNum渲染上一页、下一页和页码。

3.3 DaoFactory 与过滤器权限保护的分工

DaoFactory.class是这份项目结构清晰的关键。它用工厂模式统一创建 Dao 实例,避免在 Servlet 里到处new StudentDao()。常见实现是读配置文件或反射,简单点就是静态方法返回单例:

public class DaoFactory { private static final StudentDao studentDao = new StudentDao(); private static final TeacherDao teacherDao = new TeacherDao(); private static final ExamDao examDao = new ExamDao(); public static StudentDao getStudentDao() { return studentDao; } public static TeacherDao getTeacherDao() { return teacherDao; } public static ExamDao getExamDao() { return examDao; } }

这样做的价值是:以后换 ORM 框架或加缓存,只改工厂内部,Servlet 不用动。PermissionFilter则负责在请求进入 Servlet 之前做角色校验,它和 DaoFactory 一个管「谁能进来」,一个管「进来后怎么取数据」,职责分开后代码就好维护。测试权限时重点看 Filter 里对 session 为空的处理,以及nopriviliage页面的跳转路径是否正确。

4. 考试流程与成绩报表:倒计时、评分、区间统计怎么落地

4.1 考试倒计时与 Ajax 通知提醒

考试结束倒计时是学生端的核心体验。实现方式一般是后端在考试记录里存start_time和duration,前端页面加载时算出剩余秒数,用setInterval每秒刷新显示。关键点是时间以服务器为准,不能信客户端本地时间,否则改系统时间就能作弊。常见做法是后端返回剩余秒数,前端只做递减展示,到 0 自动提交试卷。

// 后端在页面渲染时注入 remainingSeconds let remaining = parseInt(document.getElementById('remaining').value); const timer = setInterval(function () { if (remaining <= 0) { clearInterval(timer); // 倒计时结束,自动提交试卷 document.getElementById('examForm').submit(); return; } remaining--; let min = Math.floor(remaining / 60); let sec = remaining % 60; document.getElementById('countdown').innerText = min + '分' + sec + '秒'; }, 1000);

Ajax 通知提醒通常用在老师发布新考试或管理员发通知时,前端定时轮询一个接口,有新消息就弹窗。轮询间隔建议 30 秒以上,太频繁会给服务器压力。这份项目里通知和考试是分开的模块,NoticeDao负责通知的增删查,学生登录后能看到未读通知数量。

4.2 老师评分、评语与成绩区间统计

老师评分模块的流程是:老师进入某场考试的学生列表,看到学生提交的答案,逐题给分或给总分,再写评语,提交后更新score表。这里要注意事务,评分和更新考试状态要么一起成功,要么一起回滚,否则会出现「分数存了但状态没变」的脏数据。

成绩报表是这份项目的亮点。区间人数统计和及格率统计本质上是 SQL 聚合查询:

-- 按分数区间统计人数,区间可以按需调整 SELECT CASE WHEN score >= 90 THEN '优秀' WHEN score >= 80 THEN '良好' WHEN score >= 60 THEN '及格' ELSE '不及格' END AS level, COUNT(*) AS num FROM score WHERE exam_id = ? GROUP BY level; -- 及格率统计 SELECT SUM(CASE WHEN score >= 60 THEN 1 ELSE 0 END) / COUNT(*) AS pass_rate FROM score WHERE exam_id = ?;

参数说明:exam_id是考试场次标识,CASE WHEN划分区间,GROUP BY按区间聚合。前端拿到数据后用 ECharts 或简单表格展示,摘要里说的「炫酷成绩报表」一般就是柱状图加饼图。注意SUM和COUNT的除法在 MySQL 里默认是浮点,如果结果是 0 要检查是否有成绩记录。

4.3 学生文件打包下载与 xls 名单导入

学生文件自动打包下载用的是 ZIP 流。后端把学生上传的文件从磁盘或数据库读出,用ZipOutputStream边读边写,设置响应头Content-Disposition触发浏览器下载。常见坑是文件名中文乱码,需要URLEncoder.encode(fileName, "UTF-8")处理。

xls 名单导入用的是 Apache POI 或 jxl。读取每一行,校验学号姓名,批量插入student表。导入前建议先查重,学号已存在就跳过或更新,避免主键冲突导致整个导入失败。这份项目里导入功能配合StudentDao的批量插入方法,效率比逐条插入高很多。

5. 避坑与常见问题排查:从乱码到权限死循环

5.1 中文乱码:请求、响应、数据库三处都要管

现象:登录后页面显示问号,或者导入的学生姓名变成乱码。原因通常是三处编码不统一:JSP 页面pageEncoding、请求体编码、数据库连接编码。解决:JSP 顶部统一写<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>;在 Filter 里加request.setCharacterEncoding("UTF-8")和response.setContentType("text/html;charset=UTF-8");数据库连接串加useUnicode=true&characterEncoding=utf8。三处都改完再重启 Tomcat,基本能解决。

5.2 权限过滤器死循环:登录页被拦住了

现象:访问任何页面都跳转到nopriviliage,包括登录页本身,导致永远登不进去。原因:PermissionFilter的url-pattern配了/*,但没有放行登录请求和静态资源。解决:在 Filter 里判断请求路径,如果是login.jsp、LoginServlet、css/、js/、images/开头,直接chain.doFilter放行,不校验 session。

5.3 数据库连接池耗尽:连接没关或配置太小

现象:用一会儿就报Cannot get a connection或页面卡死。原因:Dao 里手动拿的连接没有在 finally 里关闭,或者连接池最大连接数设得太小。解决:检查每个Connection、PreparedStatement、ResultSet是否在 finally 中关闭;如果是 c3p0 或 Druid,把maxPoolSize调到 20 以上,并开启连接泄漏检测。这份项目里如果用了 DBUtils,通常会自动关,但手写 JDBC 的地方要重点看。

5.4 class 文件与源码不一致:改了没生效

现象:改了 Java 代码,重启 Tomcat 后行为没变。原因:项目里同时存在.class和.java,IDEA 编译输出路径和 Tomcat 部署路径不一致,或者直接部署了旧的 war 包。解决:在 Project Structure 里确认 Output 目录,重新 Build → Rebuild Project,然后检查 Tomcat Deployment 里用的是war exploded而不是旧 war。最稳妥的办法是删掉out目录和 Tomcat 的work目录再重启。

5.5 考试倒计时归零后没自动交卷

现象:倒计时显示 0 但试卷没提交,学生还能继续答。原因:前端setInterval被浏览器节流,或者自动提交的 form action 路径写错。解决:倒计时以服务器时间为准,前端每秒同步一次剩余秒数;自动提交用form.submit()并确认 action 指向正确的 Servlet;后端在接收提交时再校验一次考试是否已结束,双重保险。

6. 二次开发与验证技巧:从能跑到能改

把项目跑起来只是第一步,真正有价值的是知道怎么改、怎么验证改对了。我一般会先做一次「最小改动验证」:改登录页的标题文字,重新部署,看页面有没有变。这一步能确认编译和部署链路是通的,后面改业务代码才不会白费功夫。

接着是数据库层验证。用SHOW CREATE TABLE score;看字段类型和索引,如果exam_id没索引,成绩报表在数据量大时会很慢,加一个普通索引就能明显提速。分页查询的LIMIT偏移量在深分页时性能差,如果数据超过几万条,建议改成基于游标的WHERE id > ? LIMIT ?。

权限模块的验证有个技巧:在PermissionFilter里临时加一行日志,打印请求路径和 session 里的角色,然后分别用三种角色访问同一批 URL,看日志输出是否符合预期。这比反复点页面快得多。评分模块要测边界:0 分、满分、评语为空、重复提交,看后端有没有做校验和事务回滚。

报表模块我习惯用一组构造数据验证:插入 10 条成绩,其中 6 条及格、4 条不及格,看及格率是不是 60%,区间人数是不是对得上。数字对不上就回去查 SQL 的CASE WHEN边界,比如 60 分到底算及格还是不及格,这种边界最容易写错。

最后说一个血泪经验:改任何和数据库相关的代码之前,先备份一份 sql 文件。我有次调分页把LIMIT参数写反,导致数据看起来「丢了」,其实是查询偏移量错了,但当时慌了直接改表,结果更乱。从那以后我每次动 Dao 层之前都强制走一遍「备份 → 改代码 → 构造数据验证 → 对比备份」的流程。这份考试系统源码结构清楚,适合拿来练这种完整闭环,改坏了也能从备份恢复。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 1:30:57

SAGA GIS地形分析实战:7类核心因子生产级计算流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 1:30:12

Java多人聊天室实战:Socket并发与线程池设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 1:30:10

Vivado中LDPC与TSN IP核的硬件级协同设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 1:30:05

C#宿舍管理系统开发实战:从数据库设计到WinForms界面实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 1:30:03

深度学习3D物体重建全流程:YOLO检测、体素重建与OBJ导出

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/4 1:29:52

PCIE DMA例子工程详解:从FPGA链路到驱动调试的完整避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华