ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

学生考勤管理系统期末作业全解析:JSP+Servlet+MySQL实战与避坑指南

2026/10/7 5:32:57 拓冰建站 浏览量
学生考勤管理系统期末作业全解析:JSP+Servlet+MySQL实战与避坑指南 简介一份面向软件项目管理课程期末设计的Java学生考勤管理系统完整项目包适合高校学生在软件项目管理、Java Web开发等课程中参考用于完成考勤业务设计、前后端交互与项目文档等任务。资源内含系统源码、数据库脚本、使用说明文档与测试账号学生/老师角色并附带开发工具导入运行起点可快速启动演示考勤管理核心流程。压缩包共319个文件、大小约36.23MB主要包含52个Java后端文件、170个JavaScript前端脚本、HTML/CSS静态页面、SQL建表脚本以及docx项目文档和Maven配置文件目录按代码、页面、数据库、文档拆分便于期末答辩前逐模块梳理。已有969人浏览学习覆盖需求分析、数据库设计、编码实现与项目交付等课程设计环节是完成软件项目管理期末作业、理解考勤业务与前后端联调的实用参考。1. 软件项目管理期末作业学生考勤管理系统这题到底在考什么期末答辩前夜最常翻车的不是代码写不出来而是老师打开你的项目随口一问为什么同一个学生一天能有三条打卡记录为什么请假算不算出勤代码和文档里的口径对不上软件项目管理期末作业课程设计真正要交的是源码、数据库、文档三件套能互相印证代码实现了需求文档写的功能数据库能支撑统计报表文档说清楚了进度、风险和测试。学生考勤管理系统正好是这类课程的经典选题业务边界清楚、增删改查齐全、出勤率报表可量化。这篇文章把选型、建表、核心代码到避坑完整过一遍照着做就能交出一份不会被问倒的期末作业。2. 技术选型与三层架构JSPServletMySQL为什么是期末作业的稳妥答案2.1 软件项目管理课程的评分天平过程文档比框架新更重要软件项目管理这门课和纯编码课不一样评分重点往往落在需求分析、进度计划、风险识别、测试报告这些过程文档上。框架再新如果文档里写不清「为什么这样设计」答辩时照样会被追问到墙角。反过来功能朴素但每一步都有据可查反而容易拿高分。常见的选题实现方案有三种JSPServletMySQL、Spring BootMyBatis、Java Swing 桌面版。桌面版有个天然劣势演示时要在答辩教室现场装 JDK 和数据库环境一出问题整个流程就卡住。Spring Boot 功能强但对期末项目来说结构偏重而且起步依赖多稍微踩一个版本兼容坑就耗掉半天。我一般建议用 JSPServletMySQL这套组合在评分老师眼里最熟悉Tomcat 拷过去能跑数据库脚本导入就能用。另一个实际考虑是数据库增删改查要手写 SQL这正好把数据库设计能力摆在明面上。如果用 MyBatis 自动生成老师一问「这条 SQL 为什么这么写」反而说不清楚。期末作业要的是「每行代码都能解释」不是「功能跑通就行」。2.2 包结构怎么切一眼能被看懂的源码布局源码布局直接影响第一印象。老师解压 zip 后第一件事就是看目录长什么样。如果所有 Java 文件堆在一个包里就算功能全印象分先掉一半。按职责分包是最稳妥的做法也能在文档里画一张三层架构图。attendance/ ├── pom.xml ├── sql/ │ ├── init.sql # 建库建表 初始化数据 │ └── demo_data.sql # 演示用当天数据 └── src/main/java/com/attendance/ ├── bean/ # 实体类Student, Course, Attendance 等 ├── dao/ # JDBC 数据访问层 ├── service/ # 业务逻辑层 ├── servlet/ # 控制器层 └── util/ # DBUtil 等工具类 └── src/main/webapp/ ├── WEB-INF/web.xml ├── jsp/ # login.jsp, checkin.jsp, report.jsp └── static/ # css, js请求流转是典型的 MVC浏览器发请求到 ServletServlet 只做参数接收、调用 service、判断跳转service 里写业务规则比如迟到判断、请假审批流dao 里只写 JDBC 和 SQL。JSP 负责展示不写 Java 业务代码。这样拆的好处是答辩时能顺着一条线讲用户点了签到按钮请求到 CheckinServletServlet 调 AttendanceServiceService 调 AttendanceDaoDao 执行 INSERT。每一步都能指到具体的类和代码行老师想追问也有迹可循。2.3 用 Maven 把最小工程跑起来pom.xml 与 db.properties 的关键参数如果用 IDEA 新建项目我习惯先把 Maven 的 pom.xml 写好避免后面手动往 WEB-INF/lib 里塞 jar。这份配置对期末项目来说已经够用版本都是经过验证的常见组合。project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.attendance/groupId artifactIdattendance/artifactId version1.0/version packagingwar/packaging properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties dependencies !-- Servlet 和 JSP 由 Tomcat 提供scope 必须用 provided -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL 标签库 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL 8.x 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies /projectservlet-api 和 jsp-api 的 scope 写成 provided是因为 Tomcat 自带这两个库。如果写成默认的 compile打包时会把它们塞进 war部署后反而可能和 Tomcat 自带的版本冲突启动报错。这个细节踩过的人不少属于典型的期末玄学。数据库连接信息我放在 src/main/resources/db.properties由 DBUtil 加载。连接串的每个参数都有明确作用答辩时能讲清楚。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/attendance_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456MySQL 8.x 的驱动类名是 com.mysql.cj.jdbc.Driver不是老版本用的 com.mysql.jdbc.Driver。useSSLfalse 关掉 SSL 警告serverTimezoneAsia/Shanghai 解决时区报错characterEncodingutf8 保证中文写入不乱码allowPublicKeyRetrievaltrue 是因为 MySQL 8 默认认证插件在非 SSL 连接下需要它。这些参数缺哪个运行时就报哪个错。3. 数据库设计六张表把考勤的边界与统计口径锁死3.1 哪六张表从管理员到请假单的实体关系学生考勤管理系统的数据库设计看起来简单但做不对的话后面每个功能都在跟数据打架。最常见的错误是把所有信息塞进一张大表或者考勤表里不区分课程导致统计「软件项目管理课出勤率」时算出一堆脏数据。我一般拆成六张表各管一段admin 表存管理员和老师student 表存学生course 表存课程student_course 表存学生选课关系attendance 表存每日考勤记录leave_info 表存请假单。班级信息直接冗余在 student.class_name 里不单独建表期末项目这样够用。attendance 表是核心字段设计决定了统计报表能不能写出来。status 用 tinyint 存枚举值0 正常、1 迟到、2 早退、3 缺勤、4 请假。check_date 和 check_time 分开存而不是用一个 datetime因为后面统计出勤率要按天 GROUP BY按日期过滤比按时间过滤快得多。另一个关键设计是唯一约束。attendance 表对 (student_id, course_id, check_date) 加唯一键这是防止重复打卡最硬的一道闸。业务层能拦一次数据库约束还能再兜底一次。答辩时老师问「怎么避免学生签两次到」这道约束就是标准答案。表名职责关键字段admin管理员/教师账号username, password, real_namestudent学生基础信息student_no, student_name, class_namecourse课程与上课时间course_name, teacher_id, begin_time, end_timestudent_course学生选课关系student_id, course_idattendance每日考勤明细student_id, course_id, check_date, check_time, statusleave_info请假申请与审批student_id, course_id, leave_date, reason, status3.2 建表 SQL主键、唯一约束和统计索引怎么写下面是核心建表脚本放数据库初始化脚本里。字符集统一用 utf8mb4不要用 utf8因为 utf8mb4 才是完整的 UTF-8能存生僻字和表情符号评分老师如果拿一个带生僻字的姓名测试不会当场翻车。CREATE DATABASE IF NOT EXISTS attendance_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE attendance_db; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, student_name VARCHAR(50) NOT NULL, class_name VARCHAR(50), phone VARCHAR(20) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(50) NOT NULL, teacher_id INT NOT NULL, begin_time VARCHAR(5) NOT NULL COMMENT 上课时间如 08:30, end_time VARCHAR(5) NOT NULL, CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES admin(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE student_course ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, UNIQUE KEY uk_student_course (student_id, course_id), CONSTRAINT fk_sc_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE attendance ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, check_date DATE NOT NULL, check_time TIME NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0正常 1迟到 2早退 3缺勤 4请假, UNIQUE KEY uk_stu_course_date (student_id, course_id, check_date), KEY idx_course_stu_date (course_id, student_id, check_date), CONSTRAINT fk_att_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_att_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE leave_info ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, leave_date DATE NOT NULL, leave_type TINYINT DEFAULT 0 COMMENT 0全天 1上午 2下午, reason VARCHAR(255), status TINYINT DEFAULT 0 COMMENT 0待审批 1同意 2驳回, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_leave_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_leave_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有三个地方容易在答辩时被追问。第一是外键数据量不大的期末项目用物理外键直观且能体现数据库设计能力。第二是唯一约束attendance 的 uk_stu_course_date 确保一个学生同一门课一天只能有一条考勤记录。第三是索引idx_course_stu_date 是为统计页面准备的按课程查学生考勤时这条索引能直接用上。如果建完表发现少字段不要急着删表重建。用 ALTER TABLE 加列更安全比如要给学生表加邮箱字段ALTER TABLE student ADD COLUMN email VARCHAR(100) DEFAULT NULL。改完表结构后要同步改实体类和 DAO 的映射漏改一处就是满屏空指针。3.3 初始化数据铺完一学期课表答辩不冷场很多同学交上来的系统登录进去全是空页面老师第一眼就失去兴趣。初始化数据不是可选项是演示效果的基本盘。至少要准备两个学生、两门课、四组选课关系以及当天能看到的考勤记录。INSERT INTO admin (username, password, real_name) VALUES (admin, MD5(123456), 张老师); INSERT INTO student (student_no, student_name, class_name) VALUES (2024001, 张三, 软件工程2301班), (2024002, 李四, 软件工程2301班); INSERT INTO course (course_name, teacher_id, begin_time, end_time) VALUES (软件项目管理, 1, 08:30, 10:10), (数据库原理, 1, 10:30, 12:10); INSERT INTO student_course (student_id, course_id) VALUES (1, 1), (1, 2), (2, 1); -- 给第一节课生成今天的考勤数据保证统计报表演示时有内容 INSERT INTO attendance (student_id, course_id, check_date, check_time, status) SELECT sc.student_id, sc.course_id, CURDATE(), 08:22:00, 0 FROM student_course sc WHERE sc.course_id 1;密码字段用 MD5(123456) 而不是明文答辩时能说一句「演示环境做了简单加密」体现安全意识。正式项目应该用 BCrypt但期末演示用 MD5 足够反而容易讲清楚。最后一条 INSERT 用 SELECT 从选课表批量生成考勤数据保证打开统计页面的当天就有记录。这里的时间固定写成 08:22意思是学生在上课前正常签到status 为 0。你也可以造一条 status1 的迟到记录让统计报表同时展示正常和迟到演示效果更丰富。4. 核心功能代码签到、请假、统计三个模块的落地写法4.1 签到接口四个步骤缺一不可签到是考勤系统的门面功能很多作业把它做成「点一下按钮就往表里插一条」看起来简单实际漏掉了三层东西会话校验、重复检查、迟到规则。一个合格的学生签到接口一次请求里要按顺序处理四件事。第一确认是谁在签到。studentId 必须从 session 里取绝不能从前端表单里传。第二查今天这个学生这门课有没有签过。第三判断是否迟到规则是当前时间晚于上课时间加宽限。第四写入考勤表并回显结果。package com.attendance.servlet; import com.attendance.bean.Attendance; import com.attendance.dao.AttendanceDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.time.LocalTime; WebServlet(/checkin) public class CheckinServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); // 1. 身份必须来自会话不能用表单里的 studentId HttpSession session req.getSession(); Integer studentId (Integer) session.getAttribute(studentId); String courseId req.getParameter(courseId); if (studentId null) { resp.getWriter().write(请先登录); return; } AttendanceDao dao new AttendanceDao(); // 2. 重复打卡校验数据库唯一约束兜底 if (dao.existsToday(studentId, Integer.parseInt(courseId))) { resp.getWriter().write(今天这门课已经签到过); return; } // 3. 迟到判断上课时间 15 分钟宽限 String beginTime dao.getCourseBeginTime(Integer.parseInt(courseId)); LocalTime now LocalTime.now(); LocalTime deadline LocalTime.parse(beginTime).plusMinutes(15); boolean late now.isAfter(deadline); String status late ? 1 : 0; // 4. 插入考勤记录 Attendance att new Attendance(studentId, Integer.parseInt(courseId), status); boolean ok dao.insert(att); resp.getWriter().write(ok ? (late ? 签到成功已迟到 : 签到成功) : 签到失败); } }这段代码里的业务参数是「15 分钟宽限」。每个学校的考勤规则不一样有的宽限 5 分钟有的没有宽限。我习惯把宽限分钟数定义成常量或配置项并且在需求文档的考勤规则里写明答辩时老师问起直接说「这是需求分析阶段和老师确认过的规则」比临时改代码有说服力得多。existsToday 的 SQL 是 SELECT COUNT(*) FROM attendance WHERE student_id? AND course_id? AND check_dateCURDATE()配合第 3 章的唯一约束业务层和数据库双层防重复。如果并发请求同时打进来唯一约束会让第二条 INSERT 报 DuplicateKeyExceptioncatch 住后提示用户即可。4.2 请假审批状态机要处理的三个边界请假模块看起来只是「提交→审批」但状态流转的边界条件比签到多。leave_info 表的 status 有三个值0 待审批、1 同意、2 驳回。学生提交后只能处于待审批老师审批后不可再修改。同意请假这件事还有个连带操作要把当天考勤状态置为 4请假否则统计出勤率时这个人既不算迟到也不算请假数据就对不上。WebServlet(/approveLeave) public class ApproveLeaveServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); int leaveId Integer.parseInt(req.getParameter(leaveId)); int approve Integer.parseInt(req.getParameter(approve)); // 1同意 2驳回 HttpSession session req.getSession(); Integer teacherId (Integer) session.getAttribute(teacherId); if (teacherId null) { resp.getWriter().write(请先以教师身份登录); return; } LeaveDao dao new LeaveDao(); // 越权检查只有请假单对应课程的老师才能审批 int courseTeacherId dao.getLeaveTeacherId(leaveId); if (courseTeacherId ! teacherId) { resp.getWriter().write(无权审批这门课的请假单); return; } // 状态机只有待审批状态才能流转 int currentStatus dao.getLeaveStatus(leaveId); if (currentStatus ! 0) { resp.getWriter().write(该请假单已审批不能重复操作); return; } if (!dao.updateLeaveStatus(leaveId, approve)) { resp.getWriter().write(审批失败请重试); return; } // 同意请假时写入当天考勤记录状态为请假 if (approve 1) { dao.insertAttendanceFromLeave(leaveId, 4); } resp.getWriter().write(审批完成); } }三个边界问题分别对应三行检查。第一教师身份从 session 拿防止普通学生伪造老师接口。第二课程归属校验保证 A 课程老师不能审批 B 课程的请假单。第三重复审批检查把状态限定在「待审批→同意/驳回」这条单向路径上。实现上的细节是把审批同意后插入考勤记录这步放进同一个事务。很多期末项目在这直接踩坑审批同意了考勤表里却没生成请假记录统计页数据少一块。解决方法是把 updateLeaveStatus 和 insertAttendanceFromLeave 放在一个 Connection 里要么同时成功要么同时回滚。4.3 出勤率统计一条 SQL 和它需要的索引统计报表是整个系统的收口也是软件项目管理课程最看重的部分。出勤率不是简单 count 一下就行要能按课程、按日期范围、按状态分布展示。下面这条 SQL 把关键指标一次查出来在 report.jsp 里渲染成表格。SELECT c.course_name, COUNT(a.id) AS total_count, SUM(IF(a.status 0, 1, 0)) AS normal_count, SUM(IF(a.status 1, 1, 0)) AS late_count, SUM(IF(a.status IN (2, 3), 1, 0)) AS absence_count, SUM(IF(a.status 4, 1, 0)) AS leave_count, ROUND( SUM(IF(a.status IN (0, 1, 4), 1, 0)) / COUNT(a.id) * 100, 2 ) AS attendance_rate FROM attendance a JOIN course c ON a.course_id c.id WHERE a.course_id ? AND a.check_date BETWEEN ? AND ? GROUP BY a.course_id, c.course_name;这条 SQL 的核心是 SUM(IF(...)) 配合 GROUP BY 做条件计数。attendance_rate 的分子把正常(0)、迟到(1)、请假(4)都算作出勤分母是总次数算出来的就是出勤率。这里真正关键的是统计口径请假算不算出勤有的老师认为请假有正规手续、不算旷课有的认为只要没坐在教室里就不算出勤。这个口径必须在文档的「考勤规则」里定义清楚代码里怎么写的文档里就要怎么解释。统计性能在期末数据量下不会出问题但索引仍然要有。第 3 章建的 idx_course_stu_date (course_id, student_id, check_date) 正好覆盖这条查询的 WHERE 条件GROUP BY 也能用到。一个 50 人的班级跑一学期考勤数据这是几万条记录的量级没有索引也能跑但答辩时能说出「我建了复合索引来支撑统计查询」是实打实的加分项。如果需求要求看单个学生的出勤明细把 WHERE 改成 a.student_id ? 即可索引依然有效。报表页面还可以加一个按班级过滤的下拉框把 student.class_name 加进查询条件。功能虽小但体现了需求分析里「按角色查看数据」的设计思路。5. 期末作业避坑部署、乱码、驱动与答辩材料翻车实录5.1 一张排雷表现象、原因、解决下面这张表里的问题是期末作业里出现频率最高的翻车现场。每一条我都见过不止一次按现象排查最快。现象原因解决启动 Tomcat 抛 ClassNotFoundException: com.mysql.cj.jdbc.DriverMySQL 驱动 jar 没在 WEB-INF/lib 下Maven 依赖不要用 provided或手动拷 jar 到 lib连接数据库报 The server time zone value 错误MySQL 8 驱动与服务器时区不一致连接串加 serverTimezoneAsia/Shanghai首页能开点登录后 404URL 漏了 contextPath访问路径写 http://localhost:8080/attendance/页面中文全是问号数据库客户端看正常JSP 页面或响应没设 UTF-8每个 JSP 头部声明 pageEncodingServlet 里 setCharacterEncoding数据库里存的中文本身是乱码建库时字符集不是 utf8mb4删库重建CREATE DATABASE 指定 utf8mb48080 端口被占用Tomcat 起不来上一次的 Tomcat 进程没杀干净netstat -ano 查 PIDtaskkill /PID 杀进程改了代码刷新页面功能还是旧的Tomcat 没有重新部署IDEA 里 Redeploy不要只点浏览器刷新用可视化工具给表加了字段程序取不到实体类和 DAO 的映射没同步改ALTER TABLE 后检查 bean 字段和 ResultSet 映射签到提示成功但统计页查不到记录签到插入和统计查询用的数据库不一致检查 db.properties 里 url 的库名是否都是 attendance_db最后一条最隐蔽很多同学本地有两个数据库实例或者改过库名代码里连的库和给老师演示的库不是同一个。提交前把 zip 解压到干净环境跑一遍这类问题立刻暴露。5.2 MySQL 8.0 的驱动、时区和 SSL 三连坑这是期末作业里出现频率最高的运行时报错报错信息一大段核心就两句话驱动类找不到、时区没配对。如果你用的 MySQL 是 8.x驱动必须换成 com.mysql.cj.jdbc.Driver。很多老教程写的是 com.mysql.jdbc.Driver那是 5.x 的写法8.x 虽然还兼容但会打警告我建议直接写新类名。时区问题的报错信息是 The server time zone value йʱ后面的乱码是系统时区名没被正确识别。原因很简单MySQL 8 驱动默认要拿服务器的时区而你的 JDK 和 MySQL 之间没约定一致。解决方式是在连接串里显式指定serverTimezoneAsia/Shanghai。SSL 警告是另一条容易吓到人的报错Establishing SSL connection without servers identity verification is not recommended。这不是错误是警告但期末答辩时屏幕上飘一行红字很影响印象。连接串加 useSSLfalse 关掉它。注意这行配置不能单独加MySQL 8 的 caching_sha2_password 认证插件在非 SSL 连接下需要 allowPublicKeyRetrievaltrue三个参数经常一起出现。我的 db.properties 里那行连接串就是这三连坑的完整解法。5.3 Tomcat 部署路径与 404 的三种局面404 是所有 Web 期末项目绕不开的坎而且通常不是一种原因。我排查 404 基本按三种局面来。第一种页面能打开 Tomcat 欢迎页但访问 /attendance/ 就 404说明项目没有部署上去。IDEA 里检查 ArtifactsWeb 项目要用 war exploded 方式并确认 Deploy at the server startup 里勾了当前 artifact。第二种部署成功但页面全 404一般是访问路径写错。Java Web 项目的完整访问路径是 http://localhost:8080/contextPath/servletPath。contextPath 默认是 war 包的名字如果你改了 application context路径也要跟着变。我在文档里会让同学先访问 http://localhost:8080/attendance/login.jsp能出页面再谈后面的接口。第三种登录功能点了按钮 404错误提示里能看到请求的 URL。这时查 Servlet 的 WebServlet 注解路径确认和表单的 action 完全一致大小写都不能差。Tomcat 对大小写敏感/Checkin 和 /checkin 是两回事。还有一个常见情况是改了代码后刷新页面没反应。IDEA 里 Spring Boot 有热重载但普通 Web 项目没有。改完 Java 代码要 Redeploy改完 JSP 可以只 Update Resources混淆这两步的结果就是代码改了半天演示时还是旧逻辑。这不算技术难题但期末周容易急中出错我把它列入必查项。5.4 数据库中文乱码三处必须同时换 utf8中文乱码是另一个高频翻车点而且它的坑在于「数据库客户端看是好的页面上是乱码」或者反过来。排查乱码先定位问题在哪一层不要上来就改代码。第一处是建库建表。如果 CREATE DATABASE 没指定字符集MySQL 默认可能是 latin1存进去的中文在数据库层面就坏了。这种坏是物理性的程序怎么设编码都救不回来只能删库重建。破坏性最小的做法是第 3 章写的建库语句显式声明 utf8mb4。第二处是连接。db.properties 的连接串里 characterEncodingutf8 负责告诉驱动「以 UTF-8 编码来传参数」。漏掉这个页面传入的中文在到达数据库前就变成问号。第三处是页面和响应。JSP 文件头要写 % page pageEncodingUTF-8 contentTypetext/html; charsetUTF-8 %Servlet 里做转码的 request.setCharacterEncoding(UTF-8) 和 resp.setContentType(text/html;charsetUTF-8) 也要有。这段逻辑我一般写在第 4 章签到代码的第一行任何 Servlet 都先做这两步再处理业务。排查顺序是先看数据库里的数据本身乱不乱用 MySQL 客户端 SELECT 一下如果库里乱了问题在第二处之外的建库环节如果库里正常、页面乱问题在第三处页面编码。按这个顺序排查十分钟能定位不用靠运气乱试。6. 提交前用这份顺序做验证跑通、录屏、写文档6.1 五分钟全量重演清单每次提交前我都会做一次「从零开始」的全量重演模拟评分老师拿到 zip 后自己动手跑一遍的路径。顺序固定时间控制在五分钟以内。先删掉现有数据库重新执行 sql/init.sql 和 sql/demo_data.sql保证数据干净。然后重启 Tomcat打开浏览器访问 http://localhost:8080/attendance/确认登录页能出。接着用管理员账号登录进统计页面看今天的出勤数据是否正常显示。再用一个未签到的学生账号完成一次签到操作确认提示语正常。最后再提交一条请假申请并审批同意回头看统计页的数量变化。整套操作走完用录屏软件录一遍存档答辩前再看一眼心里有底。这一步最大的价值是逼你解决「换一台机器就跑不起来」的问题。目录路径、MySQL 密码、端口占用都会在干净环境里暴露。期末评分不是在 IDEA 里跑通就算的老师可能只看你录好的视频或现场演示能复现才是真的完成。6.2 文档里必须讲清楚的三个「为什么」源码和数据库跑通只是及格线软件项目管理课程的分数差距在文档里。需求分析、设计说明、测试报告这些文档最忌讳写成「系统可以登录、可以签到、可以统计」这种废话。我改作业时会重点找三个「为什么」的答案提前想清楚把她们写进文档比答辩时临场编强得多。第一个是为什么宽限时间设为 15 分钟它是业务规则需求分析里要有出处。第二个是出勤率的统计口径请假算不算出勤迟到算不算出勤代码里怎么写文档里就要怎么定义。第三个是为什么选 JSPServlet 而不是 Spring Boot要把课程目标、开发周期、复杂度控制讲清体现的是技术选型过程。我每次都坚持干的一件事是把整套流程按这个顺序重演一遍录屏存档。等答辩那天老师问起任何操作直接调出记录对应的页面和数据回答。这个小动作帮我躲过不少追问也顺手把测试报告里的真实数据填满了。希望帮到你。本文还有配套的精品资源点击获取