简介:这是基于Spring Boot开发的学生考勤管理系统完整源码包,适合Java开发者、毕业设计或课程设计人员学习参考。系统涵盖用户身份验证、学生信息管理、签到签退记录、出勤统计与异常处理等模块,并支持课程安排与数据报告生成,能够有效提升学校考勤管理效率。资源共428个文件,压缩包大小约15MB,主要包含107个java源码、43个vue前端页面、21个js脚本、14个xml配置及SQL、yml等环境配置,另有docx文档和PPT说明,便于快速理解项目结构与部署流程。目前已有49人学习下载。源码结构清晰,分层合理,附带项目启动脚本及数据库脚本,读者可据此搭建运行环境,并结合文档掌握从安装到使用的完整流程;对于希望了解Spring Boot整合Vue前后端分离开发、权限控制或考勤业务逻辑的开发者,具有不错的参考价值。
1. 拿到这个 zip,先想清楚它对应哪一类 Spring Boot 工程
学生考勤管理系统是典型的 CRUD 加状态流转项目,这个 zip 里一般是源码、数据库脚本和一段 README。常见组合是 Spring Boot 框架做后端,MyBatis-Plus 操作 MySQL,Redis 做缓存和防重复签到,前端用 Thymeleaf 或者打包好的 Vue 静态文件。它要解决的业务很明确:把学生的签到、签退、请假、缺勤统计从 Excel 挪到网页上,让系统自己算出每个人某一天是正常、迟到还是缺勤。适合拿来做 springboot 毕业设计,也适合小团队内部直接二次开发。真正决定这个 zip 能不能跑起来的,往往不是代码写得多好,而是配置和环境的匹配。JDK 版本、MySQL 账号、Redis 是否启动,这三样比代码更容易让整个系统卡死在启动阶段。
2. 拆开 zip 之前:用 pom.xml 和目录结构判断 Spring Boot 项目的成色
拿到压缩包后的第一件事不是解压后双击运行,而是先判断它是不是一个完整的 Maven 工程,依赖有没有写全,版本体系是不是一致。这一步能省掉后面大量“启动不了”“找不到类”“端口冲突”的排查时间。
2.1 先看文件清单里有没有这些关键文件
在终端里解压并查看结构:
unzip springboot项目学生考勤管理系统.zip -d attendance cd attendance find . -maxdepth 2 -type f | sort输出里至少应该看到pom.xml、src/main/java、src/main/resources/application.yml或application.properties,以及至少一个.sql文件。没有pom.xml的项目通常是用 Gradle 构建,对应文件是build.gradle,IDEA 导入方式不同。如果 zip 里只有源码没有 SQL 脚本,后面需要自己重建表,工作量会明显增加。
还有一种常见情况是 README 里写了“导入 IDEA 直接运行”,却没有说数据库初始化方式。这种项目即使代码没有编译错误,启动后一查接口就是 500。正确的做法是先看src/main/resources目录,确认有没有db/init.sql、doc/或根目录下的*.sql。三个关键文件(pom.xml、application.yml、SQL 脚本)都在,这个 springboot 项目才具备本地运行的基础。顺便检查有没有.git目录,如果有,说明源码历史保留完整,后续改挂了还可以 diff 回来。
2.2 从 pom.xml 读出 Spring Boot 版本和依赖族
pom.xml 的 parent 部分直接决定整个项目的技术栈基线:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.13</version> <relativePath/> </parent>2.7.13是 Spring Boot 2.7.x 系列,默认支持 JDK 8/11/17,Servlet API 是 javax 命名空间,MyBatis-Plus 一般搭配 3.5.x。如果是 3.2.x 或 3.3.x,那 JDK 必须是 17+,所有javax.servlet都要替换成jakarta.servlet。很多“ClassNotFoundException: javax.servlet”的报错,本质是 Spring Boot 版本和 JDK 版本不匹配。判断版本后还要看依赖,考勤系统最常出现的是这几个:
spring-boot-starter-web:提供 REST 接口和内置 Tomcat。mybatis-plus-boot-starter:数据库 ORM,继承BaseMapper后开箱即用。spring-boot-starter-data-redis:缓存、幂等、Session 共享。spring-boot-starter-validation:参数校验。spring-boot-starter-actuator:健康检查和监控端点。
如果 pom.xml 里没有spring-boot-maven-plugin,打包成可执行 jar 时会有问题,可以手动补到<build>节点:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>Spring Boot 自动装配原理在这里可以直接用上:引入 starter 后,框架靠spring.factories(2.x)或AutoConfiguration.imports(3.x)自动加载配置类。比如引入 Redis starter,RedisAutoConfiguration就会生效,给你创建StringRedisTemplate和RedisTemplate。但这不代表 Redis 一定连得上,连接参数由 application.yml 控制,配置错时启动不报错,第一次访问缓存才报错。
| Spring Boot 主版本 | JDK 要求 | Servlet API | 常见 MyBatis-Plus 版本 |
|---|---|---|---|
| 2.7.x | 8 / 11 / 17 | javax.servlet | 3.5.x |
| 3.2.x | 17+ | jakarta.servlet | 3.5.5+ |
2.3 三层架构和常用注解怎么对应考勤场景
考勤系统基本都是 controller-service-mapper 三层,用几个常用注解就能判断代码写得到不到位:
@SpringBootApplication public class AttendanceApplication { public static void main(String[] args) { SpringApplication.run(AttendanceApplication.class, args); } }@SpringBootApplication是@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan的合成注解。它从启动类所在包开始扫描组件,所以启动类必须放在 controller、service、mapper 包的最外层。放错位置会出现“类都在,但接口全部 404”的情况。
Controller 层的典型写法:
@RestController @RequestMapping("/api/attendance") public class StudentAttendanceController { @PostMapping("/checkin") public Result checkIn(@RequestBody CheckInDTO dto) { return attendanceService.checkIn(dto.getStudentId()); } }@RestController直接让返回值变成 JSON,@RequestBody把前端 JSON 绑定到 DTO,@PostMapping限定 POST 方法。Service 层用@Service标记,Mapper 层用@Mapper或启动类上的@MapperScan注册到 Spring 容器。
这一层是 springboot 面试题的高发区:Spring 的声明式事务@Transactional默认只对 RuntimeException 回滚,受检异常不会自动回滚。考勤系统的签到和写记录必须放在同一个事务里,否则会出现签到了但记录没落库,这个细节在答辩或评审时被问到的概率很高。
3. 本地跑通:Spring Boot 配置、MySQL 和 Redis 的最小可用组合
3.1 先解决 JDK 和 Maven 环境,再谈 springboot 配置
本地跑不起来的第一个原因通常是 JDK 版本,和代码无关。如果你用 IDEA 新建 Spring Boot 项目,默认选到 3.3,然后照搬 2.7 的配置,就会遇到 springboot 版本太高带来的兼容性问题。比如项目是 Spring Boot 2.7,本机是 JDK 21,跑起来大概率出现UnsupportedClassVersionError或者反射访问异常。解决办法不是改代码,而是给这个项目单独指定 JDK 8 或 11。用 IDEA 打开项目后,在 Project Structure 里把 Project SDK 和 Modules 的 language level 切到对应版本,再把 Maven 的 runner 默认 JRE 也切过去,三个地方不一致时 Maven 还是会用系统 JDK 编译。
推荐先跑一次编译:
mvn clean compile这一步把所有依赖从这个 zip 对应的 Maven 仓库拉下来,能提前暴露依赖下载失败、仓库地址不可达的问题。很多别的项目会把settings.xml一起压进 zip,如果你直接放到~/.m2下面,可能指向内网私服,本地环境访问不到,就会卡在Could not resolve dependencies。这时候把settings.xml里的 mirror 改成阿里云公共仓库就够用。
springboot 配置不是“装一个框架”的概念,它的本质是把 starter 依赖的自动装配参数填到application.yml。所以环境问题解决后,重点就落在配置上。
3.2 application.yml 里最容易改错的三组参数
一个最小可用的考勤系统配置如下:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/attendance_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver data: redis: host: localhost port: 6379 database: 0 timeout: 3000ms mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0第一组容易错的是数据库 URL 里的serverTimezone。MySQL 8 驱动必须有serverTimezone=Asia/Shanghai,否则启动时直接报The server time zone value 'Öйú' is unrecognized。如果项目还连 SQL Server,连接串又完全是另一种写法,不要混用。
第二组是 Redis 的database参数。本地 Redis 默认有 16 个库,项目里可能用的 database 1,而配置写 database 0。这种错误不报异常,但读写缓存全不命中。可以把timeout调短到 3000ms,连接异常会更快暴露。
第三组是 MyBatis-Plus 的逻辑删除。如果实体类上有@TableLogic的deleted字段,而全局配置没写logic-delete-field,MyBatis-Plus 生成 SQL 时不会自动追加deleted=0,删除就变成物理删除,考勤记录一旦删了就找不回来。
注意:Spring Boot 2.x 的 Redis 配置前缀是
spring.redis,3.x 是spring.data.redis,两套配置不能直接混用。如果拿高版本项目抄低版本配置,Redis 连不上但不报错,排查时要先看启动日志里的配置打印。
3.3 初始化 SQL:学生、考勤记录、请假单怎么建表
在 zip 里找到 SQL 就执行,找不到就自己建。下面是三张最核心的表:
CREATE DATABASE IF NOT EXISTS attendance_db DEFAULT CHARACTER SET utf8mb4; USE attendance_db; CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(32) NOT NULL UNIQUE COMMENT '学号', name VARCHAR(64) NOT NULL, class_name VARCHAR(64), deleted TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE attendance_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL, work_date DATE NOT NULL, check_in_time DATETIME, check_out_time DATETIME, status VARCHAR(20) COMMENT 'NORMAL/LATE/ABSENT/LEAVE', deleted TINYINT DEFAULT 0, UNIQUE KEY uk_student_date (student_id, work_date) ); CREATE TABLE leave_request ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL, leave_type VARCHAR(20), start_time DATETIME, end_time DATETIME, reason VARCHAR(255), approved TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );utf8mb4必须加,不然后面插中文会报Incorrect string value。uk_student_date唯一键是防重复签到的最底层保障,即使代码出现并发,数据库也会拒绝同一天同一个学生的第二条记录。设计上把签到记录做成“一天一行”,再通过check_in_time和check_out_time表示当天两次打卡,如果以后要支持一天多次进出,再改成打卡明细表。
执行 SQL 的方式:
mysql -uroot -p < init.sql执行完用SHOW TABLES;确认三张表都在。如果项目里有admin_user表,登录账号的密码加密方式要看清楚,常见的是 BCrypt,也有项目用 MD5,两者无法互相登录。
3.4 启动验证:看日志而不是只看端口
启动项目前,确认 MySQL 和 Redis 都在运行:
redis-cli ping mysqladmin -uroot -p status然后启动 Spring Boot:
mvn spring-boot:run看到Started AttendanceApplication in 5.8 seconds且没有 ERROR,才算启动成功。不要只看到Tomcat started on port(s): 8080就以为端到端通了,数据源和 Redis 是在启动阶段初始化的,如果它们有问题启动会直接失败。Spring Boot 日志里要重点看有没有 MyBatis-Plus 的版本警告,以及数据源初始化有没有异常。
启动成功后,调用一个最基础的接口:
curl http://localhost:8080/api/student/list返回[]而不是 500,说明数据库连接和表映射都通了。联调时把 SQL 日志打开,就是前面配置里的StdOutImpl,控制台会打印每条 SQL 和参数。如果没有打印,检查mybatis-plus.configuration.log-impl是否生效。也可以接着看:
curl http://localhost:8080/actuator/health返回{"status":"UP"}说明健康检查通过。如果没有引入 actuator,这个地址是 404,不用纠结。
4. 把考勤业务做成 Spring Boot 接口:签到、统计、定时任务
4.1 考勤的 3 张核心表和状态机
上一章建好了表,这章看业务。考勤系统的核心不是“增删改查”,而是状态怎么流转。一个学生的当天状态可以用这个表来表达:
| 状态 | 触发动作 | 前置条件 |
|---|---|---|
| NORMAL | checkIn 成功且时间早于规定时刻 | 当天无记录 |
| LATE | checkIn 成功但时间晚于规定时刻 | 当天无记录 |
| LEAVE | leave_request 被审批通过 | 审批字段 approved=1 |
| ABSENT | 定时任务在当天结束后计算 | 无 check_in_time 且无审批通过的请假 |
这里有一个设计取舍:不要把状态直接交给前端来传,后端要根据时间和服务端数据自己推导。前端传什么就存什么,会被人用工具直接改成“NORMAL”绕过迟到判定。所以签到接口只接收 studentId,状态由后端判断。
请假审批通过后,要同步影响考勤状态。比较简单的做法是不直接 update 考勤记录,而是在计算日报时把未签到但有有效请假的记录判定为 LEAVE。这样考勤记录表始终只存“实际打卡”的原始事实,统计表存“推导结果”,两边不打架。
4.2 用 MyBatis-Plus 写签到 / 签退接口
MyBatis-Plus 的 Mapper 继承BaseMapper<T>后,单表 CRUD 不用写 SQL。签到的核心方法可以这么写:
@Service public class AttendanceService { @Autowired private AttendanceRecordMapper recordMapper; @Transactional public AttendanceRecord checkIn(Long studentId) { LocalDate today = LocalDate.now(); AttendanceRecord record = recordMapper.selectOne( new LambdaQueryWrapper<AttendanceRecord>() .eq(AttendanceRecord::getStudentId, studentId) .eq(AttendanceRecord::getWorkDate, today)); if (record == null) { record = new AttendanceRecord(); record.setStudentId(studentId); record.setWorkDate(today); record.setCheckInTime(LocalDateTime.now()); record.setStatus(LocalTime.now().isBefore(classStartTime) ? "NORMAL" : "LATE"); recordMapper.insert(record); } return record; } }LambdaQueryWrapper是 MyBatis-Plus 中避免 SQL 拼写错误的关键,方法引用AttendanceRecord::getStudentId在编译期就能检查字段名。@Transactional保证 check_in_time 写入失败时不会留下半条脏数据。注意 selectOne 在查询到多条记录时会抛异常,所以数据表唯一键uk_student_date在这里是第二道保险。
签退接口和签到几乎一样,区别是把check_out_time更新到已有记录上:
@Transactional public void checkOut(Long studentId) { LocalDate today = LocalDate.now(); AttendanceRecord record = recordMapper.selectOne( new LambdaQueryWrapper<AttendanceRecord>() .eq(AttendanceRecord::getStudentId, studentId) .eq(AttendanceRecord::getWorkDate, today)); if (record != null) { record.setCheckOutTime(LocalDateTime.now()); recordMapper.updateById(record); } }如果 record 为 null,说明没签到就直接签退,可以抛出业务异常,由全局异常处理器转成统一返回值。大多数 springboot + mybatis-plus 的项目都会配一个@RestControllerAdvice异常处理器,避免把堆栈直接返回给前端。
4.3 Redis 在考勤里到底缓存什么,以及怎么设置过期时间
考勤系统的 Redis 只缓存两类数据:学生基本信息和签到幂等标记。考勤记录本身不缓存,因为查询量不大,而且数据实时性要求高。缓存学生信息是为了避免每次下拉列表都查 MySQL:
@Autowired private StringRedisTemplate redisTemplate; public List<Student> listStudents() { String key = "attendance:student:list"; String json = redisTemplate.opsForValue().get(key); if (json != null) { return JSON.parseArray(json, Student.class); } List<Student> list = studentMapper.selectList(null); redisTemplate.opsForValue().set(key, JSON.toJSONString(list), Duration.ofMinutes(10)); return list; }Duration.ofMinutes(10)表示学生列表 10 分钟过期,避免数据更新后看不到最新结果。另一个更关键的应用是防止重复签到:
public boolean tryLockCheckIn(Long studentId) { String key = "attendance:checkin:" + studentId + ":" + LocalDate.now(); return Boolean.TRUE.equals( redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofHours(12))); }setIfAbsent是原子操作,同一个 key 只有第一次调用能设置成功,第二次会返回 false。如果并发请求同时进来,只有真正允许的请求能写 MySQL,另一个直接返回“已签到”。这个方案和数据库唯一键形成两层防护,即使 Redis 失效,数据库也不会产生重复记录。注意 springboot 整合 redis 时,如果直接用RedisTemplate而不是StringRedisTemplate,要注意序列化方式,否则存进去的是二进制乱码。
4.4 用 Spring Boot 定时任务把“应到/实到/请假”算成日报
每天结束后需要统计今天多少人应到、多少人实到、多少人迟到、多少人请假、多少人缺勤。用@Scheduled实现最直接:
@EnableScheduling @SpringBootApplication public class AttendanceApplication { // ... }任务类:
@Component public class AttendanceDailyJob { private final AttendanceRecordMapper recordMapper; public AttendanceDailyJob(AttendanceRecordMapper recordMapper) { this.recordMapper = recordMapper; } @Scheduled(cron = "0 30 23 * * ?") public void buildDailySummary() { LocalDate today = LocalDate.now(); List<AttendanceRecord> records = recordMapper.selectList( new LambdaQueryWrapper<AttendanceRecord>() .eq(AttendanceRecord::getWorkDate, today)); long normal = records.stream().filter(r -> "NORMAL".equals(r.getStatus())).count(); long late = records.stream().filter(r -> "LATE".equals(r.getStatus())).count(); // 应到人数需要从 student 表读取,未打卡且无请假的人标记为 ABSENT } }cron = "0 30 23 * * ?"表示每天 23:30 执行,避开当天课程结束前还有晚自习的情况。Spring Boot 定时任务默认在一个线程上跑,任务多了会互相阻塞。如果同一个项目里还有其他定时任务,需要在配置文件里调大线程池:
spring.task.scheduling.pool.size=5定时任务里不要直接对考勤记录做物理删除或全表更新,最好把汇总结果写入一张attendance_daily_summary表,保留历史用于导出。任务执行失败时要能看日志,所以可以在 Job 里加上 try-catch 并用日志输出错误。定时任务的重跑也要考虑幂等,先按work_date删掉旧汇总再插入。
5. 交付前最后的收尾:日志、隔离、打包与常见报错处理
5.1 别把 Actuator 和 Heapdump 暴露到公网
如果 pom.xml 里有 actuator,本地调试很舒服,但交付时记得限制端点暴露范围。/actuator/env会把application.yml里的数据库密码打出来,/actuator/heapdump能直接下载 JVM 堆内存,里面可能含 Redis 连接串、用户信息等敏感数据,这也是 springboot heapdump 敏感信息泄露漏洞的常见入口。上线前把 exposure 收敛到 health 和 info 就够了:
management: endpoints: web: exposure: include: health,info5.2 打包成可运行 zip 的检查清单
交付前按这个清单过一遍:
| 检查项 | 验证方法 |
|---|---|
spring-boot-maven-plugin存在 | 查看 pom.xml build 节点 |
| 生产环境数据库密码未被硬编码 | 检查 application-prod.yml |
| SQL 脚本和 README 放在 zip 内 | find 命令确认 |
| README 写明 JDK、MySQL、Redis 版本 | cat README.md |
| 本地启动后再用远程环境验证 | java -jar启动一次 |
执行打包:
mvn clean package -DskipTests java -jar target/attendance-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod-DskipTests跳过测试,--spring.profiles.active=prod指定生产环境配置。打包后直接把 jar 和init.sql、README 放一起压缩,比把整个源码目录丢进去更友好。
5.3 三个高频报错和对应处理
Access denied for user 'root'@'localhost'。MySQL 8 默认 root 账号可能使用 auth_socket 或远程访问限制。开发环境最简单的做法是给 root 设置密码:ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';,然后刷新权限。Unable to connect to Redis。先redis-cli ping看 Redis 进程是否正常,再看配置前缀是spring.redis还是spring.data.redis。Spring Boot 3.x 只认spring.data.redis,前缀错了不会启动失败,只是运行时连接超时。Invalid bound statement (not found)。Mapper 接口没有被扫描到,或者 XML 文件没有配置。启动类上加@MapperScan("com.xxx.mapper"),再把mapper-locations指向 XML 目录就能解决。
最后一个建议:把java -jar跑起来后,按签到、签退、请假、统计日报四条流程各走一遍,再压缩交付。这个验证动作比任何代码审查都更接近真实场景。
本文还有配套的精品资源,点击获取