简介:面向软件项目管理课程设计的完整交付包,适用于计算机相关专业期末作业、课程设计场景,提供基于Java实现的学生考勤管理系统源码及配套数据库、文档。系统划分学生与教师两种角色,内置测试账号,便于验收演示,降低演示门槛。压缩包内含319个文件,以Java源码、HTML页面、CSS样式、JavaScript脚本为主,辅以SQL数据库脚本、Maven配置及项目说明文档,构成可直接导入IDEA运行的完整工程,包体大小36.23MB,文档中详细记录了项目运行步骤、测试账号与功能操作说明,读者可快速上手。目前已有969人学习,适合需要快速完成课设或参考完整项目结构的读者。通过该项目可掌握Spring Boot等项目架构、考勤业务流程设计、前后端交互以及文档撰写规范,同时获得可直接运行和二次开发的系统基础。
1. 先把这个 zip 当项目盘一遍:学生考勤管理系统到底要交什么
如果你手头正好躺着一个《软件项目管理期末作业课程设计—学生考勤管理系统源码+数据库+文档.zip》,别急着解压。这类作业包在高校里非常典型:课程名叫“软件项目管理”,要求却是把需求分析、进度计划、数据库设计、编码实现和课程设计报告一次性交齐。学生考勤管理系统正好卡在“业务简单但流程完整”的位置:要管学生、班级、课程、考勤记录,还要有老师端登记、学生端查询、统计报表。对准备照着做的人来说,这篇笔记是从验收标准倒推的做法——不吹嘘功能,只围绕“能跑通、能演示、能答辩”三件事,把项目管理文档、数据库脚本和源码串成一条完整链路。
2. 用软件项目管理的方法拆需求:考勤系统的范围、里程碑和交付物
很多人拿到“软件项目管理期末作业课程设计”这样的命题,习惯先找源码,把跑通当成唯一目标。但课程名里的“软件项目管理”五个字才是评分重心。老师会从需求覆盖度、WBS 分解、进度安排、风险记录去给项目管理部分打分,代码只是佐证。我一般会建议用两周做完系统,再用两周做文档,而不是反过来。因为程序上的小问题可以熬夜修,文档对不上需求、没有进度规划,答辩时一句话就会被问住。
2.1 先定范围:一个能过检的学生考勤系统该有哪些功能
范围管理的核心是“砍需求”。很多同学在期末作业里加刷脸签到、微信小程序、GPS 定位,最后文档没写完,代码到处报错。课程设计要的是闭环,不是复杂度。一个能过检的学生考勤管理系统,把下面这张表里优先级为“高”的功能做好就够了。
| 模块 | 典型功能 | 使用角色 | 优先级 | 验收标准 |
|---|---|---|---|---|
| 登录认证 | 用户名密码登录、退出、角色跳转 | 全体 | 高 | 错误密码不能进入系统,三类角色进入不同主页 |
| 班级管理 | 班级增删改查 | 管理员 | 高 | 新增班级后可在学生管理和考勤登记中看到 |
| 学生管理 | 学生信息维护、按班级筛选 | 管理员/教师 | 高 | 新增学生后能在考勤登记下拉框里选中 |
| 课程管理 | 课程维护、指定授课教师 | 管理员 | 中 | 课程与教师关联正确 |
| 考勤登记 | 按课程、日期、节次登记每个学生状态 | 教师/管理员 | 高 | 同一学生同一课程同一节次不能重复登记 |
| 考勤查询 | 按学生、课程、日期范围查询 | 全体 | 高 | 查询结果与登记数据一致 |
| 统计报表 | 出勤率、缺勤排名 | 教师/管理员 | 中 | 数据准确,能截图放进文档 |
| 可选扩展 | 请假申请、导出 Excel、二维码签到 | 全体 | 低 | 不做不扣分,做错反而扣分 |
优先级为“低”的功能,我的建议是只写进“扩展计划”一节,不要真正实现。老师问起来,你能说出思路,还能解释为什么没做——因为项目时间和成本不允许。这是软件项目管理里最标准的“范围控制”话术。
2.2 把期末作业拆成 WBS:四周计划与三个里程碑
WBS 是为了把模糊的“做一个考勤系统”拆成可估算、可检查的任务。常见做法是四周完成:第一周做需求和数据库设计,第二周写代码,第三周补测试并准备演示数据,第四周专门写文档和答辩材料。下面这个 WBS 可以直接用作任务分解底稿。
学生考勤管理系统 WBS 1. 项目启动 1.1 需求调研(课堂场景、考勤规则) 1.2 范围确认(功能列表、角色划分) 2. 设计阶段 2.1 数据库设计(ER 图、建表 SQL) 2.2 界面原型(登录页、考勤登记页、统计页) 3. 编码阶段 3.1 登录与权限控制 3.2 学生/班级管理模块 3.3 考勤登记模块 3.4 考勤统计模块 4. 测试与数据 4.1 功能测试用例 4.2 准备演示数据 5. 文档与答辩 5.1 软件项目管理文档(WBS/甘特图/风险记录) 5.2 课程设计报告 5.3 答辩 PPT拆完任务后,要在项目管理文档里画甘特图。不用 Project,用 Excel 或 draw.io 就能画:横轴是四周时间,纵轴是上面这些任务,给每个任务拉一条水平条。重点是标出三个里程碑:第 1 周末,attendance_db.sql能在 MySQL 里一次执行成功,所有表和测试数据就位;第 3 周初,登录、考勤登记、统计报表三个核心功能可演示;第 4 周末,源码、数据库、文档按老师要求的命名格式打包成 zip。这三个里程碑是自检点,也是答辩时“进度管理”的证据。
2.3 需求追踪矩阵:让文档和源码对得上的关键
课程设计最常见的扣分项是文档里写的功能和代码做的不一致。解决这个问题不需要什么高级工具,一张需求追踪矩阵就够了。每一条需求都编上号,写明对应页面、数据库表和代码文件,完成后更新状态。这样老师问“REQ-003 在哪里实现的”,你能直接指到文件,而不是在项目里翻半天。
| 需求编号 | 功能描述 | 优先级 | 涉及页面 | 数据库表 | 代码文件 | 状态 | | REQ-001 | 管理员登录 | 高 | login.jsp | t_user | LoginServlet.java | 完成 | | REQ-002 | 班级管理 | 高 | class_list.jsp | t_class | ClassServlet.java | 完成 | | REQ-003 | 学生管理 | 高 | student_list.jsp | t_student | StudentServlet.java | 完成 | | REQ-004 | 考勤登记 | 高 | attendance_add.jsp | t_attendance | AttendanceServlet.java | 完成 | | REQ-005 | 考勤统计 | 中 | stats.jsp | v_student_attendance | StatsServlet.java | 完成 |这张矩阵要在写代码之前就建好,每完成一个模块就更新一次状态,不要等最后补。如果中途改了设计,比如把“迟到 10 分钟算缺勤”改成“迟到算另一种状态”,就在变更记录里追加一行:变更原因、变更内容、影响到的表。答辩时老师最怕听到“我忘了改”,有了变更记录,反而显得你做了完整的需求管理。
3. 数据库设计:考勤系统的表结构和初始化 SQL 脚本
学生考勤管理系统这类选题,数据库设计决定了后面代码的体感。很多同学一上来就写表,写到一半才发现学生登录没地方存、考勤无法区分节次。正确顺序是从用例反推:谁登录、谁登记、统计要按什么粒度出数,然后建表。这与数据库课程设计的思路一致,也能作为软件项目管理文档中“需求分析”的输入。
3.1 从用例反推数据模型:五张表之间的关系
从用例推导出的核心模型很稳定:t_user存登录账号,t_student存学生档案,t_class存班级,t_course存课程,t_attendance存考勤记录。逻辑关系是:一个班级有多个学生,一个教师用户负责多门课程,一个学生通过考勤记录与课程建立多对多关系。
-- 建库:字符集统一用 utf8mb4,避免中文乱码 CREATE DATABASE IF NOT EXISTS attendance_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE attendance_db; CREATE TABLE t_class ( class_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '班级ID', class_name VARCHAR(50) NOT NULL UNIQUE COMMENT '班级名称', grade_year YEAR NOT NULL COMMENT '入学年份' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='班级表'; CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(30) NOT NULL UNIQUE COMMENT '登录用户名', password VARCHAR(64) NOT NULL COMMENT '密码,建议存 MD5/SHA256 摘要', role ENUM('admin','teacher','student') NOT NULL COMMENT '角色', student_id CHAR(10) DEFAULT NULL COMMENT '学生角色关联学号', UNIQUE KEY uk_user_student (student_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='登录用户表'; CREATE TABLE t_student ( student_id CHAR(10) PRIMARY KEY COMMENT '学号,如 2021001001', name VARCHAR(50) NOT NULL COMMENT '姓名', class_id INT NOT NULL COMMENT '所属班级', phone VARCHAR(20) DEFAULT NULL COMMENT '手机号', FOREIGN KEY (class_id) REFERENCES t_class(class_id) ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表'; CREATE TABLE t_course ( course_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '课程ID', course_name VARCHAR(100) NOT NULL COMMENT '课程名称', teacher_id INT NOT NULL COMMENT '授课教师用户ID', schedule VARCHAR(100) DEFAULT NULL COMMENT '上课时间描述,如周一3-4节', FOREIGN KEY (teacher_id) REFERENCES t_user(user_id) ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表'; CREATE TABLE t_attendance ( attendance_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '考勤记录ID', student_id CHAR(10) NOT NULL COMMENT '学号', course_id INT NOT NULL COMMENT '课程ID', class_date DATE NOT NULL COMMENT '上课日期', period TINYINT NOT NULL COMMENT '节次,1-4 表示第1-2节、第3-4节等', status ENUM('present','late','absent','leave') NOT NULL COMMENT '考勤状态', remark VARCHAR(200) DEFAULT NULL COMMENT '备注', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '登记时间', UNIQUE KEY uk_stu_course_date_period (student_id, course_id, class_date, period), FOREIGN KEY (student_id) REFERENCES t_student(student_id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES t_course(course_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考勤记录表';几个关键设计决策要能讲清楚。学号用CHAR(10)而不是INT,是因为学号可能以 0 开头,用数值类型会把前导 0 丢掉。t_user里的student_id允许为空,因为管理员和教师不需要关联学生档案,这就是“可空外键”的典型用法。考勤表里的唯一索引uk_stu_course_date_period是防重复登记的数据库层兜底,即使代码漏了判重,SQL 层也能挡下第二次插入。
外键策略也值得写进文档。班级删除时用ON DELETE RESTRICT,因为一旦删除班级,系统就不知道学生属于哪个班,宁可报错也不要静默删掉。考勤记录删除时用ON DELETE CASCADE,因为考勤只是学生和课程的历史流水,学生档案或课程没了,流水保留没有意义。
3.2 初始化数据:测试账号、班级、课程和考勤样例
没有演示数据的系统在答辩时非常吃亏。老师点开统计页面,看到一片空白,提问角度立刻变成“为什么没数据”,而不是“功能怎么实现”。初始化数据不要贪多,3 个角色账号、2 个班级、2 个学生、2 门课、几条考勤流水足够支撑 5 分钟演示。
USE attendance_db; -- 密码字段演示用 MD5,实际系统至少要做加盐哈希 INSERT INTO t_class (class_name, grade_year) VALUES ('计科2101', 2021), ('软工2102', 2021); INSERT INTO t_user (username, password, role, student_id) VALUES ('admin', MD5('123456'), 'admin', NULL), ('zhangteacher', MD5('123456'), 'teacher', NULL), ('2021001001', MD5('123456'), 'student', '2021001001'), ('2021001002', MD5('123456'), 'student', '2021001002'); INSERT INTO t_student (student_id, name, class_id, phone) VALUES ('2021001001', '张伟', 1, '13800000001'), ('2021001002', '李娜', 1, '13800000002'); INSERT INTO t_course (course_name, teacher_id, schedule) VALUES ('软件工程', 2, '周一3-4节'), ('数据库原理', 2, '周三1-2节'); INSERT INTO t_attendance (student_id, course_id, class_date, period, status, remark) VALUES ('2021001001', 1, '2024-11-04', 1, 'present', NULL), ('2021001002', 1, '2024-11-04', 1, 'late', '迟到10分钟');这条脚本执行时要特别注意插入顺序:班级、用户、学生、课程、考勤。因为外键依赖,必须先有t_class才能插t_student,先有t_user里的教师账号才能插t_course。如果从别处复制脚本后报外键错误,多半是顺序乱了,或者旧库里有残留数据。验证方式也简单,执行完跑一句SELECT COUNT(*) FROM t_attendance;,返回 2 条就算成功。
3.3 数据库连接池:别让连接配置成为连接不上的元凶
课程设计里最常见的 MySQL 连接方式是用DriverManager.getConnection()每次现连。这在小 Demo 里没问题,但答辩时老师问“如果 100 个学生同时签到,数据库会不会崩”,你就需要引出连接池。常见做法是用 Tomcat 的 JNDI DataSource,配置放在META-INF/context.xml里。
<!-- META-INF/context.xml,放在 Web 应用的 META-INF 目录下 --> <Context> <Resource name="jdbc/attendanceDB" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/attendance_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai" username="root" password="你的数据库密码" maxTotal="20" maxIdle="5" maxWaitMillis="10000"/> </Context>maxTotal是连接池最多维护的数据库连接数,maxIdle是空闲时保留的连接数,maxWaitMillis是请求连接的最大等待时间,超过就报超时。这几个参数要能在文档里解释,但不需要背数字。重点是和DriverManager对比:连接池复用 TCP 连接,避免每次请求都做三次握手和数据库身份认证,这是软件项目管理里“性能设计”的加分项。
4. 核心功能实现:登录、签到、统计的最小可运行路径
源码部分的常见实现是 JSP + Servlet + MySQL,这也是 java 课程设计案例源码里出现频率最高的组合。它不像 Spring Boot 那样把配置藏起来,每一行都在明面上,答辩时按包讲,老师更容易相信是你自己写的。下面的代码骨架可以直接套进一个标准 JavaWeb 工程,编译环境用 JDK 8 + Tomcat 9。
4.1 登录与权限控制:一个 Filter 解决 80% 的页面访问问题
登录模块如果在每个 Servlet 里都写一遍“判断 Session 是否为空”,不仅重复,还容易漏。更稳的做法是写一个AuthFilter,拦截所有请求,没登录就统一踢回登录页。
@WebFilter("/*") public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 注意:这里不主动创建 Session,避免静态资源也生成无用 Session HttpSession session = request.getSession(false); Object loginUser = session != null ? session.getAttribute("loginUser") : null; String uri = request.getRequestURI(); // 登录页、登录接口、静态资源放行 if (loginUser != null || uri.endsWith("login.jsp") || uri.endsWith("LoginServlet") || uri.contains("/css/") || uri.contains("/js/")) { chain.doFilter(request, response); return; } response.sendRedirect(request.getContextPath() + "/login.jsp"); } }这段 Filter 的逻辑很简单:先取 Session,如果用户已经登录,直接放行;如果访问的是登录页或登录接口,也放行;其余请求全重定向到login.jsp。注意request.getSession(false)里的false很关键,它不会为一个没登录的请求自动创建 Session,能减少服务器内存浪费。登录成功后,把用户对象放进 Session 的loginUser属性,后续页面就能通过<%= session.getAttribute("loginUser") %>判断当前角色。
4.2 考勤登记接口:先查重再插入,用事务兜底
考勤登记是系统的核心写操作,最容易踩的坑是重复提交。前端按钮禁用是一道防线,但不够,后端必须做判重。下面的方法用事务处理,先查一遍,查到就回滚,查不到才插入。
public String addAttendance(HttpServletRequest request) { int studentId = Integer.parseInt(request.getParameter("studentId")); int courseId = Integer.parseInt(request.getParameter("courseId")); String date = request.getParameter("date"); int period = Integer.parseInt(request.getParameter("period")); String status = request.getParameter("status"); try (Connection conn = DBUtil.getConnection()) { conn.setAutoCommit(false); // 开启事务 // 先查重:同一学生、同一课程、同一日期、同一节次只能有一条 PreparedStatement check = conn.prepareStatement( "SELECT attendance_id FROM t_attendance " + "WHERE student_id=? AND course_id=? AND class_date=? AND period=? FOR UPDATE"); check.setInt(1, studentId); check.setInt(2, courseId); check.setString(3, date); check.setInt(4, period); ResultSet rs = check.executeQuery(); if (rs.next()) { conn.rollback(); return "该学生这一节已经登记过,不能重复提交"; } PreparedStatement insert = conn.prepareStatement( "INSERT INTO t_attendance(student_id, course_id, class_date, period, status) " + "VALUES(?,?,?,?,?)"); insert.setInt(1, studentId); insert.setInt(2, courseId); insert.setString(3, date); insert.setInt(4, period); insert.setString(5, status); insert.executeUpdate(); conn.commit(); return "登记成功"; } catch (SQLException e) { // 唯一索引兜底:如果两个请求同时通过检查,这里会触发 Duplicate 错误 return "登记失败,可能是重复登记或数据错误"; } }这里用FOR UPDATE对命中的记录加行锁,两个请求同时到达时,第二个会等第一个提交后再查,从而查到这个节次已有记录。如果没有这行锁,先查再插的缝隙里可能插入重复数据,最终要靠uk_stu_course_date_period唯一索引拦住。对课程设计来说,能讲出这两层防重,已经很能体现工程意识。参数date统一用yyyy-MM-dd格式,period用 1 到 4 表示节次,status传present、late、absent、leave四个值,和数据库枚举保持一致。
4.3 统计报表:用视图把复杂 JOIN 封装成单表查询
统计报表如果直接在 Servlet 里拼十几行 GROUP BY 和 CASE WHEN,代码会很难读,也不好测试。更清晰的做法是建视图,把统计口径固化在数据库层,Java 代码只负责查询和展示。
CREATE VIEW v_student_attendance AS SELECT s.student_id, s.name AS student_name, c.course_name, COUNT(a.attendance_id) AS total_times, SUM(CASE WHEN a.status = 'present' THEN 1 ELSE 0 END) AS present_times, ROUND( SUM(CASE WHEN a.status = 'present' THEN 1 ELSE 0 END) / COUNT(a.attendance_id) * 100, 2 ) AS attendance_rate FROM t_student s JOIN t_attendance a ON s.student_id = a.student_id JOIN t_course c ON a.course_id = c.course_id GROUP BY s.student_id, c.course_id;视图把“总出勤次数、实到次数、出勤率”一次算好,页面查询变成SELECT * FROM v_student_attendance WHERE course_id = 1 ORDER BY attendance_rate ASC。要注意视图里没有WHERE条件,按课程、日期过滤要在视图外面做。如果业务要求迟到也算出勤,改CASE WHEN a.status IN ('present','late')即可,这就是统计口径的统一入口。数据库增删改查的“查”做到这一步,已经覆盖了列表查询、单条详情和聚合统计三种形态。
5. 避坑日志:从解压到答辩最常见的 6 个翻车点
下面这些坑不是某一个 zip 包独有,而是这类期末作业交付物的共性问题。我按从解压到答辩的顺序分成两组,每一条都可以当排查手册用。很多问题看起来是玄学,追到底都是环境版本、约束顺序或事务边界的问题。
5.1 启动阶段:Tomcat 闪退、数据库连不上、中文乱码
现象:双击startup.bat后窗口一闪而过,浏览器访问http://localhost:8080直接 404。
原因:最常见是 JDK 和 Tomcat 版本不匹配,或者JAVA_HOME、CATALINA_HOME环境变量没配。其次是 8080 端口被其他进程占用,Tomcat 启动失败但界面只显示闪退。
解决:不要双击,改成命令行进入 Tomcat 的bin目录执行catalina run,这样报错信息不会一闪而过。然后检查环境变量,JAVA_HOME要指向 JDK 安装目录,不是jre。端口占用用netstat -ano | findstr 8080查出 PID,结束占用进程。版本组合直接用最常见的 JDK 8 + Tomcat 9 或 Tomcat 8.5,避免高版本 JDK 的模块化问题。
现象:控制台报Access denied for user 'root'@'localhost',或者Communications link failure。
原因:前者是密码错误或连接串没写对,后者是 MySQL 服务没启动、端口不是 3306、驱动版本不匹配。用 MySQL 8.x 时还容易忽略serverTimezone参数。
解决:先用命令行mysql -uroot -p确认 MySQL 能登录,再检查连接串。MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver,连接串至少包含useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai。驱动 jar 必须放在WEB-INF/lib下,放错位置在编译时不报错,运行时才报ClassNotFoundException。
现象:SQL 脚本里中文注释在表结构里变成问号,页面也出现???。
原因:数据库连接字符集和文件编码不一致。SQL 文件虽然是 UTF-8 保存的,但客户端默认连接字符集可能是latin1,或者建库时没指定utf8mb4。
解决:建库语句固定写DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,JDBC 连接串加characterEncoding=utf8,JSP 文件头保持pageEncoding="UTF-8"。这三处都做对,中文乱码基本绝迹。
5.2 业务数据阶段:重复签到、统计翻倍、外键删除失败
现象:刷新一下页面,同一个学生的同一条考勤记录出现了两条。
原因:考勤登记 Servlet 只做了页面层按钮禁用,后端没有判重,数据库也没有唯一索引。点击提交后请求被重复发送,或者浏览器刷新重放了上一次 POST。
解决:后端用 4.2 的“先查再插 + 事务”,数据库再加UNIQUE KEY uk_stu_course_date_period (student_id, course_id, class_date, period)。已经插进去的重复数据要清理,先查出来再删,不能直接删整个表,否则演示数据也没了。
现象:统计视图里的出勤率看起来翻倍,比如一共 4 条记录,统计出 8 条。
原因:多表 JOIN 时表间存在一对多关系。最常见的是t_studentJOINt_attendance时,同一个学生在班级表和课程表方向各 JOIN 一次,产生笛卡尔积。比如一个学生有两门课、每门课两条考勤,JOIN 后可能被重复计算。
解决:核心是明确统计粒度。统计每个学生的出勤率,必须GROUP BY s.student_id, c.course_id,或者先在子查询里聚合考勤表再 JOIN 学生表。不要直接对 JOIN 结果做COUNT,先SELECT * FROM v_student_attendance看看原始行数是否合理,再谈公式。
现象:删除一个学生或课程时,数据库报Cannot delete or update a parent row: a foreign key constraint fails。
原因:t_attendance里还有指向该学生或课程的考勤记录,外键默认的RESTRICT阻止了删除。
解决:如果确实要物理删除,先删考勤记录再删主表,或者在建表时对考勤表用ON DELETE CASCADE。课程设计里我更推荐用逻辑删除:给t_student加一个is_deleted字段,置为 1 表示已停用,历史考勤数据还能保存下来。答辩时讲“为什么没删物理记录”,可以顺势带出数据完整性设计。
6. 答辩前一晚:用演示脚本和验收清单把源码、数据库、文档串起来
我习惯在答辩前晚花两小时做一次正式“走带”,不写新代码,只按演示流程完整跑一遍。走带的脚本要固定下来,按顺序操作,每步都写下预期结果。例如:启动 MySQL,执行attendance_db.sql,确认t_attendance有 2 条数据;启动 Tomcat,用admin / 123456登录,看到管理员菜单;进入考勤登记页,选课程“软件工程”、日期、节次,给张伟登记“出勤”,提示成功后再提交一次,必须提示重复;打开统计报表,看到张伟的出勤率与刚才登记的数据一致;换成学生账号登录,只能看到本人统计,看不到其他学生。这五步走完没问题,再整理 zip 包。
打包前还要做三件小事:删掉工程里的target、out、.idea目录,避免老师解压后看到一个几 MB 的垃圾文件;数据库脚本单独放在sql文件夹并命名带日期,比如attendance_db_20241220.sql;把需求追踪矩阵的状态全部核对一遍,确保文档、代码、数据库三者对得上。答辩时老师常问的三个问题不需要背答案,但要有思路:为什么考勤状态用枚举不用数字、为什么删学生不动历史考勤、项目延期了你怎么办。前两个都能从数据库设计章节找到答案,第三个就讲 WBS 里的缓冲时间。最后提醒一句:千万不要在答辩现场现查数据库连接密码,那是我见过最尴尬的翻车方式。希望帮到你。
本文还有配套的精品资源,点击获取