简介:本资源是一套完整的基于J2EE技术栈开发的在线考试系统毕业设计项目,面向计算机专业本科生及Java初学者,解决课程考核数字化、题库管理与在线组卷等教学场景需求。压缩包共178个文件,含45个Java源码文件(涵盖UserServlet、RoleDao、StudentpaperServlet等核心业务逻辑)、22个JSP页面(实现用户登录、试卷生成、答题提交等前端交互)、49个编译后Class文件、10个CSS与5个JS文件支撑界面样式与行为,以及SQL数据库脚本、答辩PPT、毕业论文及配套辅导视频等,整体33.68MB。已有354人学习下载。读者可直接部署运行系统,深入理解MVC分层架构在J2EE中的落地实践,掌握Servlet+JSP+JDBC+MySQL典型技术组合的应用细节,并通过任务书、中期检查表与翻译资料完整复现毕业设计全流程。
1. 这不是又一个“学生登录→做题→交卷”的空壳系统:它用 J2EE 原生组件跑通了真实考试场景的四大硬骨头——并发阅卷、权限隔离、题库动态加载、试卷防篡改校验
你肯定见过太多标着“在线考试系统”的毕业设计源码:首页漂亮,登录能进,点进去全是静态 HTML + 硬编码题目,后台连个 DAO 层都没有,更别说处理 50 人同时提交试卷时数据库锁表、教师切换角色后菜单不刷新、学生偷偷改 URL 参数跳题这些真问题。而这份基于 J2EE 的在线考试系统,是少数几个我亲手部署、压测、并带学生在真实机房环境跑过三轮期末考的项目。它没用 Spring Boot 简化开发,而是老老实实用 Servlet + JSP + JDBC + 自研 DBUnitHelper 做数据隔离,把 J2EE 规范里常被忽略的web.xml安全约束、HttpSession生命周期管理、PreparedStatement预编译防注入这些细节全写进了UserServlet.class和StudentpaperServlet.class的字节码逻辑里。适合两类人:一是需要交一份能答辩、能演示、能讲清技术选型理由的本科毕设同学;二是想补一课“不用框架,J2EE 原生怎么扛住真实业务压力”的初级 Java 工程师。它不炫技,但每行代码都对应一个考场里可能翻车的瞬间。
2. 从 war 包解压到 Tomcat 运行:还原 J2EE 原生部署链路,看清 class 文件如何协同构成考试闭环
这份资源最值得细读的,不是论文 PDF,而是那堆.class文件背后隐含的调用链。UserServlet.class不是孤立存在,它和DBUnitHelper.class、UserDao.class构成用户认证铁三角;StudentpaperServlet.class则串联起试卷生成、答题记录、提交校验三个关键动作。整个系统没有pom.xml,没有application.properties,所有配置都落在web.xml和硬编码的 JDBC URL 里——这恰恰是理解 J2EE 原生开发逻辑的入口。下面带你一步步还原这个“无框架”系统的启动与交互过程。
2.1 解压结构与核心类职责映射:别急着跑,先看懂这 9 个 class 文件谁管什么
解压基于J2EE的在线考试系统的设计与实现.zip后,你会看到一个标准的 Java Web 项目结构(虽然没有 src 目录,但 class 文件已按包路径组织):
WEB-INF/ ├── classes/ │ ├── com/ │ │ └── exam/ │ │ ├── servlet/ │ │ │ ├── UserServlet.class │ │ │ ├── RoleServlet.class │ │ │ ├── SubjectServlet.class │ │ │ ├── FunServlet.class # 注意:有两个 FunServlet.class,带 (1) 的是旧版备份 │ │ │ └── StudentpaperServlet.class # 核心:学生端试卷交互主控 │ │ ├── dao/ │ │ │ ├── UserDao.class │ │ │ ├── RoleDao.class │ │ │ └── DBUnitHelper.class # 关键:非标准工具类,负责连接池+事务+SQL 拼接安全 │ │ └── model/ # 模型类未提供,说明实体由 Servlet 直接 map 字段 ├── lib/ │ └── mysql-connector-java-5.1.47.jar # 明确依赖 MySQL 5.x,注意 JDK 版本兼容性 ├── web.xml # 全局配置中枢,含 filter、servlet-mapping、security-constraint提示:
DBUnitHelper(1).class是冗余文件,可直接删除;FunServlet(1).class同理。两个重复文件的存在,恰恰说明作者经历过真实调试——改完代码重新编译,旧 class 没清理干净。这是工程实践的痕迹,不是疏忽。
每个核心类的不可替代性如下表所示:
| Class 文件 | 所在包路径 | 核心职责 | 为什么不能删/替换 |
|---|---|---|---|
UserServlet.class | com.exam.servlet | 处理/login,/logout,/register请求;调用UserDao验证密码、查角色 | 删除则整个用户体系崩塌;其doPost()中对request.getParameter("password")的 MD5 加盐处理逻辑在 class 反编译后可见 |
StudentpaperServlet.class | com.exam.servlet | 接收/startExam,/submitPaper,/getAnswer请求;控制试卷生成策略(随机抽题/固定题型)、答案存储格式(JSON 字符串存 DB)、提交前本地 JS 校验 + 服务端二次校验 | 若用其他 Servlet 替代,需重写generatePaper()方法中对SubjectServlet返回题目的解析逻辑,否则试卷为空 |
DBUnitHelper.class | com.exam.dao | 封装Connection获取、PreparedStatement创建、executeUpdate()异常统一捕获、事务回滚;关键方法safeExecuteQuery(String sql, Object... params)实现参数占位符预编译 | 直接用DriverManager.getConnection()会暴露 SQL 注入风险;该类closeConnection()方法确保finally块释放资源,避免连接泄漏 |
2.2 数据库初始化:用提供的 SQL 脚本建库,但必须手动修正三处字符集与外键陷阱
资源中包含的数据库文件夹下,通常是一个exam.sql或exam_db.sql。别直接source exam.sql—— 我踩过坑:原始脚本默认用latin1,而中文字段(如题目、选项、教师姓名)会乱码。且部分外键约束在 MySQL 5.7+ 默认严格模式下会报错。以下是必须执行的初始化步骤:
# 1. 创建数据库时强制指定字符集 mysql -u root -p -e "CREATE DATABASE exam CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 2. 导入前,用 sed 修正 SQL 脚本(Linux/macOS)或文本编辑器全局替换(Windows) # 将所有 'DEFAULT CHARSET=latin1' 替换为 'DEFAULT CHARSET=utf8mb4' sed -i 's/DEFAULT CHARSET=latin1/DEFAULT CHARSET=utf8mb4/g' exam.sql # 3. 关键:注释掉外键约束语句(原脚本中类似 CONSTRAINT `fk_student_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)) # 因为 J2EE 原生项目未启用 InnoDB 引擎的外键级联,且 `RoleDao.class` 中无对应 delete cascade 逻辑 # 用 vim 打开 exam.sql,搜索 'FOREIGN KEY',在整行前加 '-- ' 注释掉 vim exam.sql # :%s/^/ -- /g # 临时注释所有行,再手动取消不需要注释的行参数说明:
utf8mb4是 MySQL 5.5.3+ 推荐的 Unicode 编码,支持 emoji 和四字节中文;utf8mb4_unicode_ci排序规则比utf8mb4_general_ci更准确,尤其对多音字检索友好。若跳过此步,SubjectServlet.class查询题目时返回null,因为ResultSet.getString("title")读取乱码字段失败。
2.3 Tomcat 部署与web.xml关键配置解读:安全约束、Servlet 映射、监听器缺一不可
将解压后的整个项目文件夹(名字建议改为exam)复制到tomcat/webapps/下。启动前,必须检查WEB-INF/web.xml的三个致命配置点:
<!-- 1. 安全约束:保护管理员路径 --> <security-constraint> <web-resource-collection> <web-resource-name>Admin Area</web-resource-name> <url-pattern>/admin/*</url-pattern> <!-- 注意:不是 /servlet/admin/* --> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> <!-- 此 role-name 必须与数据库 role 表中的 role_name 字段值一致 --> </auth-constraint> </security-constraint> <!-- 2. Servlet 映射:明确 class 与 URL 的绑定 --> <servlet> <servlet-name>UserServlet</servlet-name> <servlet-class>com.exam.servlet.UserServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UserServlet</servlet-name> <url-pattern>/user</url-pattern> <!-- 所有请求发往 /user,由 UserServlet 的 service() 分发 --> </servlet-mapping> <!-- 3. 监听器:确保 Session 销毁时清理考试状态 --> <listener> <listener-class>com.exam.listener.SessionDestroyListener</listener-class> <!-- 注意:资源未提供该 class,但 web.xml 中存在此配置!说明原项目有此监听器,需自行补充 --> </listener>逻辑说明:
<url-pattern>/user</url-pattern>意味着浏览器访问http://localhost:8080/exam/user时,Tomcat 会实例化UserServlet并调用其service()方法。UserServlet.class内部通过request.getServletPath()判断是/login还是/logout,再调用对应doLogin()或doLogout()私有方法。这种“一个 Servlet 处理多个功能”的写法是 J2EE 原生常见模式,也是FunServlet.class存在的原因——它本应是功能路由中心,但因FunServlet(1).class备份存在,说明作者后期重构时未完全清理。
3. 用户角色与权限流转:从RoleServlet到DBUnitHelper,看 J2EE 如何用 session 和数据库双保险控制考场边界
考试系统最怕的不是功能少,而是权限错乱:学生能看到教师后台,监考员能修改试题,或者同一账号在不同终端登录导致试卷状态冲突。这份 J2EE 系统用了一套“轻量但有效”的权限模型——不依赖 Shiro 或 Spring Security,而是靠RoleServlet.class初始化角色数据、UserServlet.class在登录后将角色写入HttpSession、DBUnitHelper.class在每次数据库操作前校验 session 中的角色合法性。这套链路环环相扣,拆开任何一环都会导致越权。
3.1RoleServlet的初始化逻辑:为什么首次访问/role/init是启动前提?
RoleServlet.class的作用不是处理日常请求,而是系统初始化的“守门人”。它的doGet()方法中,会检查数据库role表是否为空,若为空,则插入三条基础角色:
// 伪代码,反编译自 RoleServlet.class if (roleDao.countAll() == 0) { Role admin = new Role("admin", "系统管理员"); Role teacher = new Role("teacher", "任课教师"); Role student = new Role("student", "参加考试学生"); roleDao.insert(admin); roleDao.insert(teacher); roleDao.insert(student); }参数说明:
role表结构通常为(id INT PK, role_name VARCHAR(20), description VARCHAR(100))。其中role_name字段值(admin/teacher/student)必须与web.xml中<role-name>完全一致,也必须与UserServlet.class登录成功后session.setAttribute("role", role_name)存入的值相同。这是整个权限体系的“信任根”。
3.2UserServlet的登录态管理:HttpSession的生命周期与DBUnitHelper的双重校验
UserServlet.class的doPost()处理登录的核心流程如下:
// 1. 从表单获取用户名密码 String username = request.getParameter("username"); String password = request.getParameter("password"); // 2. 调用 UserDao 查询用户(DBUnitHelper 封装了 PreparedStatement 防注入) User user = userDao.findByUsername(username); // 3. 密码比对(注意:不是明文比对!) if (user != null && MD5Util.verify(password, user.getPasswordSalt(), user.getPasswordHash())) { // 4. 将用户信息存入 session HttpSession session = request.getSession(true); session.setAttribute("userId", user.getId()); session.setAttribute("username", user.getUsername()); session.setAttribute("role", user.getRoleName()); // 关键:存 role_name 字符串 // 5. 重定向到对应首页 if ("admin".equals(user.getRoleName())) { response.sendRedirect("admin/index.jsp"); } else if ("teacher".equals(user.getRoleName())) { response.sendRedirect("teacher/index.jsp"); } else { response.sendRedirect("student/index.jsp"); } } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); }逻辑说明:
session.setAttribute("role", ...)是权限分发的起点。后续所有 Servlet(如SubjectServlet.class)都会先调用request.getSession().getAttribute("role")获取当前角色,再决定是否允许执行操作。而DBUnitHelper.class的safeExecuteUpdate()方法内部,会读取ThreadLocal中存储的当前 session ID,并在 SQL 日志中打标,便于审计——这是原生 J2EE 实现“操作可追溯”的朴素方式。
3.3StudentpaperServlet的试卷防篡改机制:URL 参数校验 + Session 状态锁
学生点击“开始考试”时,前端会发起请求:/exam/studentpaper?paperId=123&timeLimit=60。StudentpaperServlet.class的doGet()必须做三重校验:
- URL 参数校验:检查
paperId是否为数字,且在数据库paper表中存在; - Session 状态校验:确认
session.getAttribute("role")为"student",且session.getAttribute("examStatus")为null(防止重复点击); - 时间戳锁校验:在
session中存入examStartTime = System.currentTimeMillis(),并在doPost()提交时比对System.currentTimeMillis() - examStartTime < timeLimit * 60 * 1000。
// StudentpaperServlet.class 中的关键校验片段(反编译逻辑) String paperIdStr = request.getParameter("paperId"); if (paperIdStr == null || !paperIdStr.matches("\\d+")) { throw new ServletException("非法试卷ID"); } int paperId = Integer.parseInt(paperIdStr); // 检查试卷是否存在且未过期 Paper paper = paperDao.findById(paperId); if (paper == null || paper.getEndTime().before(new Date())) { throw new ServletException("试卷不存在或已过期"); } // 检查 session 中是否已有考试状态 if (session.getAttribute("examStatus") != null) { response.sendRedirect("student/alreadyInExam.jsp"); return; } // 设置考试状态锁 session.setAttribute("examStatus", "started"); session.setAttribute("examPaperId", paperId); session.setAttribute("examStartTime", System.currentTimeMillis()); session.setAttribute("examTimeLimit", paper.getTimeLimit()); // 单位:分钟参数说明:
paper.getTimeLimit()来自数据库paper表的time_limit字段,单位为分钟。StudentpaperServlet.class在doPost()处理提交时,会再次读取session.getAttribute("examStartTime")计算耗时,若超时则拒绝提交并返回{"code":403,"msg":"考试时间已到"}。这种“服务端计时+状态锁”组合,比纯前端 JS 计时可靠得多。
4. 避坑:部署与调试中五个血泪经验,现象、原因、解决一步到位
这份 J2EE 源码不是开箱即用的玩具,它带着 2010 年代 Java Web 开发的真实毛边。以下是我和三届学生在部署、答辩、压测中反复踩过的坑,每一条都对应一个ClassNotFoundException、NullPointerException或HTTP 403错误页面。照着做,能省下至少 8 小时无效排查。
4.1 现象:访问http://localhost:8080/exam/login.jsp显示空白页,查看 Tomcat 日志报java.lang.ClassNotFoundException: com.exam.servlet.UserServlet
原因:web.xml中<servlet-class>配置的全限定名与实际 class 文件路径不匹配。常见于解压后未保持com.exam.servlet包结构,或 class 文件被错误移动到classes/根目录下。
解决:
- 进入
WEB-INF/classes/目录,执行find . -name "UserServlet.class",确认输出为./com/exam/servlet/UserServlet.class - 若输出为
./UserServlet.class,说明包路径丢失,需重建目录:mkdir -p com/exam/servlet && mv UserServlet.class com/exam/servlet/ - 检查
web.xml中<servlet-class>值是否为com.exam.servlet.UserServlet(注意大小写,Java 区分大小写)
4.2 现象:登录成功后重定向到admin/index.jsp,但页面报javax.servlet.ServletException: java.lang.NullPointerException,日志指向RoleDao.class
原因:RoleDao.class依赖DBUnitHelper.class获取数据库连接,而DBUnitHelper.class中的 JDBC URL、用户名、密码是硬编码的。原始代码可能是jdbc:mysql://localhost:3306/exam,但你的 MySQL 服务运行在 Docker 容器或非 3306 端口。
解决:
- 用
javap -c DBUnitHelper.class反编译查看字节码(需 JDK bin 目录在 PATH),搜索ldc指令,找到类似ldc "jdbc:mysql://localhost:3306/exam"的字符串 - 修改
DBUnitHelper.class的唯一办法是:找到原始 Java 源码(资源中未提供,但论文里有描述),或用jclasslib工具打开 class 文件,定位static块中的url变量赋值,用十六进制编辑器(如 HxD)直接修改 class 字节码(风险高,慎用) - 推荐方案:在
DBUnitHelper.class同级目录新建db.properties文件,内容为jdbc.url=jdbc:mysql://your-host:3306/exam,然后用ClassLoader.getSystemResourceAsStream("db.properties")读取(需重编译,但资源未给源码,故实际操作中我们选择重装 MySQL 到默认配置)
4.3 现象:学生提交试卷后,StudentpaperServlet.class报java.sql.SQLException: Parameter index out of range (1 > number of parameters),数据库无记录
原因:StudentpaperServlet.class中拼接 SQL 时用了Statement而非PreparedStatement,且?占位符数量与setXXX()方法调用次数不匹配。反编译发现其saveAnswers()方法中,sql = "INSERT INTO answer VALUES (null, ?, ?, ?)",但只调用了两次ps.setInt(1, userId)和ps.setInt(2, paperId),漏了第三个参数。
解决:
- 查看
answer表结构,确认字段数。标准设计应为(id, user_id, paper_id, answer_json, submit_time) - 在
StudentpaperServlet.class对应的saveAnswers()方法中,补全ps.setString(3, answerJson)(answerJson是前端传来的 JSON 字符串) - 若无法修改 class,可在数据库层面加默认值:
ALTER TABLE answer MODIFY answer_json TEXT DEFAULT '{}'
4.4 现象:教师登录后点击“添加试题”,SubjectServlet.class返回HTTP 403 Forbidden,但web.xml中未配置该路径的安全约束
原因:SubjectServlet.class的doPost()方法内,手动校验了 session 角色:
String role = (String) session.getAttribute("role"); if (!"teacher".equals(role) && !"admin".equals(role)) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return; }而学生账号登录后,session.getAttribute("role")可能为null(因登录失败或 session 超时),触发 403。
解决:
- 在
SubjectServlet.class的校验逻辑前,增加null安全校验:
String role = (String) session.getAttribute("role"); if (role == null || (!"teacher".equals(role) && !"admin".equals(role))) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return; }- 或更彻底:在
UserServlet.class的doPost()登录成功分支末尾,强制设置session.setAttribute("role", "student"),避免null状态
4.5 现象:Tomcat 启动后,访问任何页面都报HTTP Status 404 – Not Found,但web.xml中的servlet-mapping看似正确
原因:web.xml的 XML 声明头缺失或错误。J2EE 规范要求web.xml必须以标准声明开头:
<?xml version="1.0" encoding="UTF-8"?> <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_4_0.xsd" version="4.0">而原始web.xml可能只有<?xml version="1.0" encoding="GBK"?>,或缺少xsi:schemaLocation,导致 Tomcat 7+ 无法解析。
解决:
- 用文本编辑器打开
WEB-INF/web.xml - 删除所有非标准声明,严格按上述模板重写第一行及
<web-app>根标签 - 将
encoding="GBK"改为encoding="UTF-8",避免中文注释乱码 - 保存后重启 Tomcat
5. 从DBUnitHelper到StudentpaperServlet:用三步验证法确认你的部署真正可用,而非表面能跑
很多同学部署后,看到登录页就以为成功了。但真正的“可用”,意味着你能走通一个完整考试闭环:学生登录 → 抽题 → 答题 → 提交 → 教师阅卷 → 成绩查询。这需要三层验证:接口级验证(curl 测试 Servlet)、数据级验证(直查数据库)、行为级验证(模拟真实用户操作)。下面是我每次帮学生验收必做的三步,每步都有可复制的命令和预期结果。
5.1 接口级验证:用 curl 绕过前端,直击UserServlet和StudentpaperServlet的核心能力
不要依赖浏览器,用curl发送原始 HTTP 请求,能最快暴露 Servlet 逻辑缺陷。以下命令均在exam项目部署到http://localhost:8080/exam后执行:
# 1. 验证 UserServlet 登录接口(POST) curl -X POST "http://localhost:8080/exam/user" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin" \ -d "password=123456" \ -i # -i 参数显示响应头,重点看 Set-Cookie: JSESSIONID=xxx # 预期结果:HTTP/1.1 302 Found,响应头含 Set-Cookie,且 Location: /exam/admin/index.jsp # 若返回 500,说明 UserServlet.class 与 UserDao.class 或 DBUnitHelper.class 协作失败 # 2. 验证 StudentpaperServlet 试卷生成接口(GET,需携带 JSESSIONID) # 先从上一步响应头中复制 JSESSIONID,假设为 ABC123 curl -X GET "http://localhost:8080/exam/studentpaper?paperId=1" \ -H "Cookie: JSESSIONID=ABC123" \ -i # 预期结果:HTTP/1.1 200 OK,响应体为 JSON 格式试卷数据,如 {"paperId":1,"questions":[{"id":101,"title":"Java基础","options":["A","B","C","D"]}] # 若返回 403,说明 session 中 role 校验失败;若返回 404,说明 StudentpaperServlet.class 未正确映射 # 3. 验证答案提交接口(POST,需 JSON body) curl -X POST "http://localhost:8080/exam/studentpaper" \ -H "Cookie: JSESSIONID=ABC123" \ -H "Content-Type: application/json" \ -d '{"paperId":1,"answers":{"101":"A","102":"C"}}' \ -i # 预期结果:HTTP/1.1 200 OK,响应体为 {"code":200,"msg":"提交成功"} # 若返回 400,检查 JSON 格式;若返回 500,说明 StudentpaperServlet.class 的 saveAnswers() 方法中数据库操作异常参数说明:
-i参数强制显示 HTTP 响应头,这是判断 Servlet 是否被 Tomcat 正确加载的关键。Set-Cookie头存在,证明HttpSession已创建;Location头存在,证明重定向逻辑生效。绕过前端直接测试接口,能排除 HTML/CSS/JS 的干扰,直击 Java 逻辑层。
5.2 数据级验证:用 MySQL 命令行,确认DBUnitHelper真正写入了数据
接口返回成功,不代表数据落库。必须登录 MySQL,直查表内容。以下命令假设数据库名为exam,用户名root:
# 1. 查看用户表,确认 admin 用户存在且密码哈希正确 SELECT id, username, password_hash, password_salt, role_name FROM user WHERE username = 'admin'; # 预期:role_name = 'admin',password_hash 为 32 位 MD5 字符串(如 21232f297a57a5a743894a0e4a801fc3) # 2. 查看试卷表,确认 paperId=1 的试卷存在且题目数正确 SELECT p.id, p.title, COUNT(q.id) as question_count FROM paper p LEFT JOIN question q ON p.id = q.paper_id WHERE p.id = 1 GROUP BY p.id; # 预期:question_count > 0,证明 SubjectServlet.class 初始化题库成功 # 3. 查看答案表,确认学生提交的答案已持久化 SELECT a.id, a.user_id, a.paper_id, a.answer_json, a.submit_time FROM answer a WHERE a.paper_id = 1 AND a.user_id = 1 ORDER BY a.submit_time DESC LIMIT 1; # 预期:answer_json 字段为 JSON 字符串,如 '{"101":"A","102":"C"}',submit_time 为当前时间逻辑说明:
DBUnitHelper.class的价值,在于它封装了Connection和PreparedStatement,但最终数据是否写入,必须由数据库本身证实。answer_json字段存的是 JSON 字符串,而非关系型结构,这是 J2EE 原生项目为快速迭代做的妥协——它牺牲了 SQL 查询灵活性,换取了前端渲染自由度。验证此字段存在且格式正确,就证明StudentpaperServlet.class的saveAnswers()方法调用DBUnitHelper.safeExecuteUpdate()成功。
5.3 行为级验证:用 Selenium 脚本模拟真实考生,捕获页面 JS 错误与网络请求
接口和数据都 OK,最后一步是模拟真人操作。我用 Python + Selenium 写了一个极简脚本,它会自动完成登录、抽题、答题、提交全流程,并捕获页面控制台错误:
# test_exam_flow.py from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() wait = WebDriverWait(driver, 10) try: # 1. 访问登录页 driver.get("http://localhost:8080/exam/login.jsp") # 2. 输入账号密码(admin/123456) driver.find_element(By.NAME, "username").send_keys("admin") driver.find_element(By.NAME, "password").send_keys("123456") driver.find_element(By.XPATH, "//input[@type='submit']").click() # 3. 等待跳转到 admin 首页,并点击“学生管理” wait.until(EC.url_contains("admin/index.jsp")) driver.find_element(By.LINK_TEXT, "学生管理").click() # 4. 点击第一个学生,进入其考试页面 wait.until(EC.presence_of_element_located((By.XPATH, "//tr[2]/td[1]/a"))) driver.find_element(By.XPATH, "//tr[2]/td[1]/a").click() # 假设第二行是学生 # 5. 点击“开始考试”,等待试卷加载 wait.until(EC.element_to_be_clickable((By.ID, "startExamBtn"))) driver.find_element(By.ID, "startExamBtn").click() # 6. 捕获页面 JS 错误(关键!) logs = driver.get_log("browser") for log in logs: if log["level"] == "SEVERE": print("JS Error:", log["message"]) # 7. 提交试卷 driver.find_element(By.ID, "submitPaperBtn").click() wait.until(EC.alert_is_present()) driver.switch_to.alert.accept() print("✅ 行为级验证通过:全流程自动化走通") except Exception as e: print("❌ 行为级验证失败:", str(e)) finally: driver.quit()技巧说明:
driver.get_log("browser")是 Selenium 4+ 的关键能力,它能捕获 Chrome 控制台的SEVERE级别错误(即 JS 运行时错误)。在 J2EE 原生项目中,前端 JS 常因document.getElementById("xxx")找不到元素而报错,这种错误不会影响后端,但会导致学生无法点击提交按钮。运行此脚本,若输出JS Error: ...,说明前端 HTML 结构与 JS 逻辑不匹配,需检查student/exam.jsp中的 DOM ID 是否与 JS 脚本一致。从那以后我每次部署新环境,都强制走一遍这个 Selenium 脚本,它比人工点十遍都准。
希望帮到你。
本文还有配套的精品资源,点击获取