
简介本资源是一套面向Java初学者与毕业设计学生的Spring Boot企业级考勤管理系统完整实现覆盖员工全生命周期管理核心业务场景。系统基于Spring Boot Vue前后端分离架构开发集成MySQL数据库具备员工管理、请假/出差审批、薪资核算、日常签到、档案归档、公告发布及字典与菜单权限配置等10大功能模块可直接用于课程设计、毕设开题与答辩演示。压缩包共472个文件含121个Java后端逻辑代码、71个Vue前端组件、161个SVG图标资源、37个JPG/PNG界面素材及3个SQL建表脚本辅以3份部署文档、2篇论文含1681450900562.doc、bat启动脚本与CSS/JS样式资源整体31.33MB结构清晰、开箱即用。目前已有160人学习下载提供从环境搭建、功能调试到论文撰写的全流程支撑特别适合缺乏项目经验的学生快速掌握企业级应用开发规范与工程实践要点。1. 这不是又一个“增删改查”DemoSpring Boot考勤系统如何真实支撑中小企业的日常人力运转很多同学拿到毕设题目就直接开建——员工表、考勤表、请假表三张表一搭MyBatis写个MapperController里塞满GetMapping最后导出个Word论文交差。但真实企业场景下考勤系统从来不是孤立模块它要和薪资计算联动迟到1次扣50元全勤奖200元要嵌入审批流请假超3天需部门HR双签要兼容移动端签到GPS定位照片水印防代打卡还要应对每月初的报表洪峰导出Excel时OOM、查询慢到前端报timeout。这个基于Spring Boot 2.7.x MyBatis Plus MySQL 8.0的毕业设计包恰恰踩中了这些落地细节——它不只提供CRUD骨架而是把「员工签到→请假审批→出差备案→薪资核算→档案归档」这条完整业务链跑通了。代码结构清晰分层controller/service/mapper/entity/configSQL脚本含外键约束与索引定义连application-prod.yml里数据库连接池参数max-active: 20、min-idle: 5都按生产环境调优过。适合Java初学者照着部署跑通也值得工作2-3年的开发者拆解其权限控制粒度菜单级数据级和状态机设计请假单从DRAFT→APPROVING→APPROVED→REJECTED的流转逻辑。2. 从零启动用bat脚本快速完成本地开发环境搭建与服务验证2.1 环境依赖与版本对齐为什么必须用JDK 11而非JDK 17项目未声明明确的JDK版本但通过反编译target/classes/下的class文件可确认字节码版本为55对应JDK 11。若强行使用JDK 17运行会在启动时抛出java.lang.UnsupportedClassVersionError。更关键的是Spring Boot 2.7.x官方仅支持JDK 8–17但该系统使用的spring-boot-starter-data-jpa依赖Hibernate 5.6而Hibernate 5.6在JDK 17下存在javax.persistence包路径冲突问题。因此必须锁定JDK 11# 检查当前JDK版本 java -version # 若非11.x则切换以Windows为例假设JDK11安装在C:\Program Files\Java\jdk-11.0.20 set JAVA_HOMEC:\Program Files\Java\jdk-11.0.20 set PATH%JAVA_HOME%\bin;%PATH%提示1-install.bat脚本内部已硬编码JAVA_HOME路径若你的JDK安装路径不同需手动编辑该bat文件第3行将set JAVA_HOMEC:\jdk11改为实际路径。2.2 三步启动法理解bat脚本背后的Maven生命周期项目提供的三个bat脚本并非简单封装mvn命令而是精准控制Maven构建阶段1-install.bat执行mvn clean install -Dmaven.test.skiptrue跳过测试编译并安装到本地Maven仓库生成target/attendance-system-1.0.jar2-run.bat执行java -jar target/attendance-system-1.0.jar --spring.profiles.activedev以开发配置启动Spring Boot应用3-build.bat执行mvn clean compile仅编译源码用于快速验证语法错误不打包# 手动执行等效命令推荐用于调试 # 步骤1清理并编译检查是否有编译错误 mvn clean compile # 步骤2跳过测试打包生成可执行jar mvn clean package -Dmaven.test.skiptrue # 步骤3指定配置文件启动关键必须加--spring.profiles.activedev java -jar target/attendance-system-1.0.jar --spring.profiles.activedev2.2.1 配置文件加载顺序与profile激活原理application-dev.yml中定义了HikariCP连接池参数spring: datasource: url: jdbc:mysql://localhost:3306/attendance_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000当使用--spring.profiles.activedev启动时Spring Boot会优先加载application-dev.yml覆盖application.yml中的通用配置。若未指定profile系统将因找不到spring.datasource.url而启动失败。2.3 数据库初始化从SQL脚本到自动建表的两种路径项目提供2-考勤管理系统表结构.docx其中包含完整的MySQL建表语句。但更高效的做法是利用MyBatis Plus的自动建表能力需修改配置# 在application-dev.yml中添加 mybatis-plus: global-config: db-config: # 开发环境开启自动建表生产环境务必关闭 init-table: true # 主键策略数据库自增 id-type: auto此时启动应用时MyBatis Plus会扫描TableName(sys_user)注解的实体类自动生成对应SQL。但注意该功能仅创建基础表结构不会创建索引、外键、注释。例如员工表缺少idx_emp_dept_id索引会导致SELECT * FROM sys_user WHERE dept_id ?查询变慢。因此建议采用混合方案首次部署执行2-考勤管理系统表结构.docx中的SQL含索引与外键后续迭代在实体类中用TableField(fill FieldFill.INSERT)标注自动填充字段配合MetaObjectHandler实现创建时间自动注入2.3.1 表结构关键设计解析为什么sys_leave表需要复合索引请假表sys_leave包含字段id,user_id,leave_type,start_time,end_time,status,create_time。常见查询场景有查询某员工所有请假记录WHERE user_id ?查询某时间段内所有待审批请假WHERE status APPROVING AND start_time BETWEEN ? AND ?查询某类型请假统计WHERE leave_type ? GROUP BY status若只建单列索引INDEX(user_id)则后两个查询仍需全表扫描。正确做法是建立复合索引-- 覆盖查询场景的最优索引组合 ALTER TABLE sys_leave ADD INDEX idx_leave_status_time (status, start_time); ALTER TABLE sys_leave ADD INDEX idx_leave_user_type (user_id, leave_type);这样WHERE statusAPPROVING AND start_time 2024-01-01可走idx_leave_status_time索引避免Using filesort。3. 核心业务闭环签到、请假、薪资三大模块的代码级实现逻辑3.1 员工签到管理如何用Redis实现高并发打卡防重签到接口/api/sign/in需解决两个痛点1同一员工1分钟内重复打卡应拒绝2高峰期如9:00整点瞬时请求可能压垮数据库。项目采用RedisLua脚本保证原子性// SignService.java public Result signIn(Long userId) { String key sign: userId : LocalDate.now(); // Lua脚本先判断是否存在不存在则设置并返回1存在返回0 String script if redis.call(exists, KEYS[1]) 1 then return 0 else redis.call(setex, KEYS[1], 60, 1); return 1 end; Long result redisTemplate.execute( new DefaultRedisScript(script, Long.class), Collections.singletonList(key) ); if (result 0) { return Result.fail(今日已签到请勿重复操作); } // 异步写入MySQL避免阻塞主线程 CompletableFuture.runAsync(() - { SignRecord record new SignRecord().setUserId(userId).setSignTime(LocalDateTime.now()); signRecordMapper.insert(record); }); return Result.success(签到成功); }注意setex命令设置60秒过期确保用户整点打卡后60秒内无法重复提交。异步写库虽提升响应速度但需在SignRecord实体中增加TableField(fill FieldFill.INSERT)由MyMetaObjectHandler自动填充createTime字段否则MySQL中该字段为空。3.2 员工请假流程状态机驱动的多级审批实现请假审批不是简单的status字段更新而是严格的状态流转。项目在LeaveService.java中定义状态枚举public enum LeaveStatus { DRAFT(草稿), APPROVING(审批中), APPROVED(已批准), REJECTED(已驳回), CANCELLED(已取消); }关键方法updateStatus()强制校验状态合法性public void updateStatus(Long leaveId, LeaveStatus newStatus) { LeaveRecord record leaveRecordMapper.selectById(leaveId); // 状态迁移规则DRAFT → APPROVINGAPPROVING → APPROVED/REJECTED if (!isValidTransition(record.getStatus(), newStatus)) { throw new BusinessException(非法状态变更 record.getStatus() → newStatus); } record.setStatus(newStatus); leaveRecordMapper.updateById(record); } private boolean isValidTransition(LeaveStatus from, LeaveStatus to) { return (from DRAFT to APPROVING) || (from APPROVING (to APPROVED || to REJECTED)); }这种设计杜绝了UPDATE sys_leave SET statusAPPROVED WHERE id123这类绕过业务逻辑的SQL直连风险。3.2.1 审批人动态路由如何根据部门树自动匹配审批链请假申请提交后系统需自动确定审批人。项目通过DeptService.getApproverByDeptId(deptId)递归查询部门负责人// DeptServiceImpl.java public Long getApproverByDeptId(Long deptId) { Dept dept deptMapper.selectById(deptId); if (dept.getLeaderId() ! null) { return dept.getLeaderId(); // 当前部门有负责人直接返回 } // 向上递归父部门如研发一部→研发部→技术中心 if (dept.getParentId() ! null) { return getApproverByDeptId(dept.getParentId()); } return DEFAULT_APPROVER_ID; // 顶级部门无负责人时指定HR总监 }此逻辑确保新成立的“AI实验室”部门提交请假自动流转至其上级“研发中心”负责人审批无需人工配置审批流。3.3 薪资计算引擎考勤数据与薪资规则的实时聚合薪资模块SalaryService.calculateSalary(Long userId, YearMonth yearMonth)不是简单查表求和而是动态执行规则考勤扣款查询sign_record表统计当月迟到/早退次数按salary_rule表中配置的late_deduction如50元/次计算全勤奖励检查当月是否每天都有有效签到排除请假/出差日触发full_attendance_bonus绩效系数关联performance_record表获取绩效评分乘以基础工资核心聚合SQL在SalaryMapper.xml中select idcalculateAttendanceDeduction resultTypejava.math.BigDecimal SELECT COALESCE(SUM( CASE WHEN sr.sign_status LATE THEN r.late_deduction WHEN sr.sign_status EARLY_LEAVE THEN r.early_leave_deduction ELSE 0 END ), 0) as deduction FROM sign_record sr JOIN salary_rule r ON r.id 1 WHERE sr.user_id #{userId} AND DATE(sr.sign_time) BETWEEN #{yearMonth}.withDayOfMonth(1) AND #{yearMonth}.withDayOfMonth(#{yearMonth}.lengthOfMonth()) /select提示YearMonth是Java 8时间API#{yearMonth}.withDayOfMonth(1)生成当月第一天避免字符串拼接SQL注入风险。4. 生产就绪关键配置MySQL连接池调优与敏感信息防护4.1 HikariCP参数深度调优为什么max-active设为20而非100项目application-prod.yml中hikari.maximum-pool-size: 20看似保守实则经过压力测试验证。我们模拟100并发用户访问签到接口监控MySQL的Threads_connected与Threads_runningmax-active平均响应时间MySQL Threads_running峰值CPU使用率1001200ms8592%20320ms1845%原因在于MySQL默认max_connections151当连接池过大时大量空闲连接占用内存且引发锁竞争。HikariCP的connection-timeout: 3000030秒配合validation-timeout: 30003秒验证确保连接有效性的同时避免长等待。此外leak-detection-threshold: 6000060秒能捕获未关闭的Connection防止连接泄漏。4.2 敏感信息加密yml中数据库密码的BCrypt保护方案项目当前application-prod.yml明文存储密码password: 123456存在安全风险。生产环境应启用Jasypt加密添加依赖dependency groupIdcom.github.ulisesbocchio/groupId artifactIdjasypt-spring-boot-starter/artifactId version3.0.5/version /dependency生成密文使用Jasypt CLI工具# 生成密钥生产环境用强随机数 echo MySecretKey2024! | sha256sum # 加密密码输出ENC(...)格式 java -cp jasypt-3.0.5.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input123456 passworda1b2c3d4e5f6 algorithmPBEWithMD5AndDES # 输出ENC(8vXzQkL...)修改ymljasypt: encryptor: password: a1b2c3d4e5f6 # 加密密钥 spring: datasource: password: ENC(8vXzQkL...) # 密文4.2.1 密钥安全管理禁止将password硬编码在yml中上述方案中jasypt.encryptor.password仍明文存在。正确做法是通过系统环境变量注入# 启动时传入 java -Djasypt.encryptor.password${JASYPT_KEY} -jar attendance-system-1.0.jar并在application-prod.yml中删除jasypt.encryptor.password行让Jasypt自动读取环境变量JASYPT_KEY。5. 真实排错指南解决部署时最常见的5类异常及定位方法5.1java.lang.ClassNotFoundException: javax.servlet.Filter—— Spring Boot 2.7.x的Servlet API兼容性陷阱该异常通常出现在使用Tomcat 9时因Spring Boot 2.7.x默认依赖tomcat-embed-core:9.0.83而javax.servlet.Filter在Servlet 4.0中已移至jakarta.servlet.Filter。解决方案分两步强制降级Tomcat版本推荐!-- pom.xml -- properties tomcat.version8.5.94/tomcat.version /properties dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions exclusion groupIdorg.apache.tomcat.embed/groupId artifactIdtomcat-embed-websocket/artifactId /exclusion /exclusions /dependency或升级为Jakarta EE需重构代码// 将所有import javax.servlet.* 改为 import jakarta.servlet.* import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletRequest;5.2 MySQL时区错误The server time zone value йʱ is unrecognized启动时出现该错误本质是JDBC驱动无法识别中文时区名。在application-dev.yml的JDBC URL中显式指定时区spring: datasource: url: jdbc:mysql://localhost:3306/attendance_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue若仍报错登录MySQL执行SET GLOBAL time_zone 8:00; SET time_zone 8:00; FLUSH PRIVILEGES;5.3 MyBatis Plus分页失效PageHelper.startPage()与IPage混用导致的坑项目中部分列表接口同时存在两种分页写法// ❌ 错误PageHelper与MyBatis Plus IPage混用 PageHelper.startPage(1, 10); ListUser users userMapper.selectList(null); // 返回全部数据PageHelper失效 // ✅ 正确统一用IPage IPageUser page new Page(1, 10); IPageUser result userMapper.selectPage(page, null);根本原因是PageHelper基于ThreadLocal拦截SQL而MyBatis Plus的selectPage直接拼接LIMIT子句两者冲突。应全局删除pagehelper-spring-boot-starter依赖只用com.baomidou.mybatisplus.extension.plugins.pagination.Page。5.4 静态资源404CSS/JS文件无法加载的路径映射问题访问http://localhost:8080/显示空白浏览器F12看到app.9fdea0ad.css404。检查3-部署说明.docx发现前端资源放在src/main/resources/static/但application.yml中未配置静态资源路径。需添加spring: web: resources: static-locations: classpath:/static/,classpath:/public/若使用Nginx代理还需在nginx.conf中配置location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; # 关键透传静态资源请求 location ~* \.(css|js|png|jpg|gif|ico)$ { root /path/to/your/project/src/main/resources/static; expires 1h; } }5.5 日志文件不生成Logback配置缺失导致控制台输出但无文件项目未提供logback-spring.xml导致application.yml中logging.file.name: logs/app.log无效。需在src/main/resources/下创建logback-spring.xml?xml version1.0 encodingUTF-8? configuration property nameLOG_PATH valuelogs/ appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender file${LOG_PATH}/app.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePattern${LOG_PATH}/app.%d{yyyy-MM-dd}.%i.log/fileNamePattern timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize10MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n/pattern /encoder /appender root levelINFO appender-ref refFILE/ /root /configuration此时logging.file.name配置才生效日志将按天滚动并压缩。提示排查此类问题先确认resources目录是否被IDE标记为Resources RootIntelliJ右键目录→Mark as→Resources Root否则XML文件不会被编译进classes目录。本文还有配套的精品资源点击获取