ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Boot员工考勤管理系统开发实战:从业务拆解到部署全流程

Spring Boot员工考勤管理系统开发实战:从业务拆解到部署全流程 在企业里跑过考勤系统的都应该知道真正麻烦的不是写代码而是把业务规则想清楚。请假怎么算、加班怎么算、外勤怎么处理、月底怎么统计这些规则捋不顺代码写得再花哨也是白搭。Springboot企业人员考勤管理系统就是一个典型的Java Web课程设计/毕业设计级别项目但它覆盖的技术栈非常完整Spring Boot做后端框架、MySQL存数据、MyBatis操作数据库再加上前端页面和部署调试一套下来基本把Java后端开发的主干流程都走了一遍。这篇文章我就以这个考勤系统为例把项目从设计到部署的完整思路拆开来讲包括业务模块怎么划分、数据库表怎么设计、核心代码怎么实现、开发环境怎么搭建、遇到问题怎么排查给正在做类似项目的朋友一个可以直接参考的实操指南。这个项目特别适合两类人一类是正在准备Spring Boot课程设计、毕业设计的学生另一类是想通过一个完整项目把Spring Boot开发流程串起来的自学新手。因为考勤系统这个业务场景足够典型既包含用户登录、权限管理这类通用模块也包含打卡、请假、统计这类业务逻辑较强的功能难度适中不会一上来就把人劝退又能把CRUD之外的真实业务处理练到手。1. 项目整体设计与业务场景拆解1.1 考勤系统到底在解决什么问题先别急着聊技术把业务场景想清楚。企业考勤管理的核心诉求其实就一句话搞清楚每个人每天到底来没来、几点来的、几点走的、中间有没有请假、有没有加班。围绕这个诉求系统必须解决几个核心问题员工怎么记录上下班时间是打卡、签到还是管理员代录请假、加班、外勤这些异常情况怎么登记、怎么审批月底怎么快速算出每个人的出勤天数、迟到次数、加班时长不同角色员工、管理员能看什么、能操作什么把这些问题拆解成功能模块就非常清晰了模块核心功能使用者登录认证账号密码验证、角色区分所有用户个人考勤上班打卡、下班打卡、查看个人记录员工请假管理提交请假申请、审批、查看记录员工、管理员考勤管理查看所有员工考勤记录、手动补录管理员统计报表按日期范围统计出勤/迟到/加班管理员员工管理维护员工信息、分配账号管理员这个划分方式基本就是大多数Spring Boot管理系统的标准套路一个通用的登录权限模块 N个业务模块。业务模块之间通过外键或者逻辑关联比如请假记录会影响考勤统计但代码层面保持相对独立这样开发时可以并行推进也方便后期维护。1.2 为什么选Spring Boot而不是其他框架现在做Java Web项目Spring Boot基本是唯一的主流选择了。对比传统的SSHStrutsSpringHibernate或者SSMSpringSpringMVCMyBatis手动配置模式Spring Boot最大的优势就是把配置地狱给消灭了。以前搭一个SSM项目要写一大堆XML配置文件数据源、事务、MyBatis映射、视图解析器每一样都要手动配置稍微配错一个地方项目就起不来。Spring Boot通过自动配置机制只需要在pom.xml里引入对应的starter依赖框架会自动完成大部分默认配置这就是所谓的约定大于配置。我见过很多课程设计还是用传统的SSM框架去写真没必要。现在企业里新项目基本都是Spring Boot起步学校布置的课题也越来越多地要求用Spring Boot。从学习成本来说Spring Boot比SSM少踩一半的坑尤其对于新手能把更多精力放在业务代码上而不是浪费在配置文件的调试里。1.3 项目整体的功能权限设计思路权限这块考勤系统一般不会做得很复杂基本上就两个角色管理员和普通员工。这里有一个设计要点值得说不要在代码里写死角色判断而是用拦截器统一处理。也就是在Spring Boot里实现一个HandlerInterceptor拦截所有请求判断当前登录用户的角色是否有权限访问某个URL。比如/admin/**开头的路径只允许管理员访问/employee/**开头的路径普通员工就能访问。这样做的优势是权限逻辑集中管理后期如果要加一个部门主管角色只需要修改拦截器和数据库角色表不需要改动各个业务Controller里的代码。前端页面也是同理根据登录角色动态渲染菜单。管理员登录后能看到员工管理考勤管理统计报表这些菜单员工登录后只看到我的考勤请假申请。这种做法虽然比前端用v-if判断稍微多写几行代码但安全性高很多因为前端判断只是用户体验层面的真正的权限控制一定要在后端做。2. 核心技术栈与架构方案选型2.1 后端技术选型一览这个项目用到的核心技术栈如下技术组件选型选型理由开发语言Java 8稳定、生态成熟、Spring Boot官方支持好后端框架Spring Boot 2.x自动配置、快速开发、社区资料多ORM框架MyBatisSQL可控性强适合课程设计展示SQL能力数据库MySQL 5.7/8.0免费、主流、资料多前端模板Thymeleaf 或 JSP后端渲染简单直接适合单体应用构建工具Maven依赖管理、打包部署方便开发工具IntelliJ IDEA社区版免费、插件丰富这里要特别说一下Spring Boot版本的选择。现在Spring Boot 3.x已经发布很久了但如果你是在做课程设计或者毕业设计我的建议是优先选择Spring Boot 2.7.x系列。原因有三点第一2.x的资料实在太多了遇到任何问题搜索一下基本都有解决方案而3.x因为基于Jakarta EE规范很多代码和2.x不兼容网上的资料数量少一个量级第二2.x对JDK 8的支持最友好很多学校的实验环境还是JDK 8用3.x还得装JDK 17第三2.x版本的Spring Boot配合MyBatis、PageHelper等第三方库的兼容性已经非常稳定基本不会出现莫名其妙的版本冲突。2.2 为什么用MyBatis而不用JPA现在Java持久层框架两大阵营MyBatis和Spring Data JPA。对于考勤管理系统这种业务相对固定的项目我推荐用MyBatis原因很实际考勤统计往往涉及复杂的SQL查询比如统计某个月每个人的出勤天数、迟到次数需要多表关联查询加条件聚合。MyBatis最大的优势就是你写SQL的自由度极高想怎么写就怎么写SQL调优也方便。而JPA虽然单表操作用起来很爽但一旦涉及复杂查询要么写JPQL要么用原生SQL要么靠 Specification 拼接条件学习曲线反而更陡。还有一个现实因素是大多数Java岗位面试都会问MyBatis而且课程设计的评分标准里SQL编写能力是一个重要考察点。用MyBatis能更好地展示你的数据库功底而用JPA的话很多逻辑被框架封装了答辩的时候反而不容易讲清楚。2.3 前端方案Thymeleaf还是前后端分离现在很多Spring Boot课程设计喜欢用Vue做前后端分离再配上Element UI之类的组件库。但我个人认为如果这个项目是单人开发、时间有限的情况用Thymeleaf做服务端渲染的性价比更高。前后端分离意味着你要同时维护两套项目和两套部署流程前端用Node环境、后端用Java环境中间还要处理跨域问题。而且课程设计答辩的时候老师更关注的是你的业务逻辑实现而不是你用了多时髦的前端技术。Thymeleaf直接在后端模板里写HTML通过th:each、th:if等语法渲染数据一套项目启动就能看效果调试路径短对新手来说出错的概率也小得多。当然如果你的课题明确要求前后端分离那就另当别论。否则我建议优先选择Thymeleaf毕竟脚本和框架的复杂度越低你踩坑的机会就越少。3. 数据库设计与核心业务表结构3.1 核心数据表怎么规划考勤管理系统的数据库是整个项目的地基表设计不合理后面写代码就是灾难。按照我做过类似项目的经验核心表至少需要这几张数据表用途关键字段t_employee员工信息表id, name, department, position, phonet_user用户账号表id, username, password, role, employee_idt_attendance考勤记录表id, employee_id, work_date, check_in_time, check_out_time, statust_leave请假申请表id, employee_id, start_time, end_time, reason, statust_leave_type请假类型表id, type_name, days_per_yeart_attendance_config考勤规则配置表id, work_start_time, work_end_time, late_minutes这里有两个设计上的坑需要特别注意第一个坑是员工和用户的关系。我建议把t_employee和t_user分开建表用employee_id做外键关联。因为员工信息属于基础数据用户账号属于系统数据后面如果有离职操作只需要禁用用户账号不需要删除员工历史记录。如果你把员工信息和账号信息混在一张表里后续维护会很别扭。第二个坑是考勤记录表不要只存上下班打卡时间还要存一个status字段。因为考勤状态正常、迟到、早退、缺卡不是通过一次查询实时算出来的而是每天定时任务或者打卡时就算好存储下来的。这样做的好处是月末统计报表的时候SQL非常简单直接按status分组计数就行不需要每次都做时间比较计算。3.2 考勤统计的SQL核心逻辑考勤系统最考验技术的其实就是统计功能。以统计某个月每个员工的出勤天数为例核心SQL大致是这样的SELECT e.id AS employee_id, e.name AS employee_name, COUNT(CASE WHEN a.status NORMAL THEN 1 END) AS normal_days, COUNT(CASE WHEN a.status LATE THEN 1 END) AS late_days, COUNT(CASE WHEN a.status EARLY_LEAVE THEN 1 END) AS early_leave_days, COUNT(CASE WHEN a.status ABSENT THEN 1 END) AS absent_days FROM t_employee e LEFT JOIN t_attendance a ON e.id a.employee_id AND DATE_FORMAT(a.work_date, %Y-%m) #{month} GROUP BY e.id, e.name ORDER BY e.id这段SQL的逻辑是先以员工表为主表左连接考勤记录表。然后通过COUNT(CASE WHEN ...)的写法一条SQL就把各种状态的数量全部统计出来。这里用LEFT JOIN而不是INNER JOIN很关键因为有员工可能某个月没有打卡记录但他仍然是公司的在册员工统计结果里必须显示他出勤0天而不是干脆不显示这个人。3.3 数据库初始化脚本的准备整套系统部署的时候数据库初始化是个大工程。我习惯的做法是准备两个SQL文件schema.sql创建数据库、建表语句data.sql插入初始数据默认管理员账号、测试员工、考勤规则配置等初始化脚本里有一个细节很重要必须先在脚本开头写上DROP DATABASE IF EXISTS然后CREATE DATABASE再USE。因为重复部署系统的时候如果数据库已经存在执行建表语句会报错。用这种方式可以保证脚本可以重复执行方便调试。另外一个建议是管理员初始密码用MD5加密后的密文直接写在SQL里比如admin的MD5密文是21232f297a57a5a743894a0e4a801fc3这样SQL脚本插入的数据可以直接登录不需要先写一个加密工具类去生成密文再手动更新数据库。4. 核心功能模块代码实现4.1 登录认证与拦截器登录功能看似简单但有几个细节值得注意。首先密码不能明文存储我这里用的是MD5加盐的方式简单实用。Spring Boot的DigestUtils.md5DigestAsHex()一行代码就能搞定加密。当然如果项目要求更高安全性可以用BCryptPasswordEncoder但课程设计用MD5足够了。登录成功之后把用户ID、角色、姓名存入Session然后通过拦截器统一校验是否登录、是否有权限。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return false; } return true; } }注册拦截器的时候要注意顺序先判断是否登录再判断是否有管理员权限。PathVariable类型的接口在拦截器里要注意URL匹配规则比如/admin/**这种写法是可以匹配多级路径的。4.2 打卡与考勤状态判定打卡功能是考勤系统的核心业务逻辑上要处理三个判断流程是否已经打过上班卡、是否迟到、是否已经打过下班卡。我的设计思路是这样的t_attendance表中每天每个员工只有一条记录上班打卡时插入记录下班打卡时更新记录。插入的时候根据配置的上班时间判断如果打卡时间晚于work_start_time late_minutes状态就是LATE否则是NORMAL。下班更新的逻辑同理如果早于配置的下班时间状态改成EARLY_LEAVE否则保持原状态再补上check_out_time。这里有一个容易踩坑的点同一张表里的日期字段比较。比如判断是否迟到不能只比较时间部分因为不同日期的打卡记录不能混在一起。我建议在Service层把work_date字符串如2024-01-15传进去然后用LocalTime.parse分别解析配置的上班时间和员工的打卡时间再通过isAfter()做比较逻辑清晰也不容易出错。4.3 请假审批流程的实现请假模块相对简单核心是状态流转。员工提交请假申请 - 状态为PENDING- 管理员审批 - 状态变为APPROVED或REJECTED。这里有一个设计上的思考审批之后要不要自动更新考勤记录答案是肯定的但要做成定时任务或者手动触发而不是在审批通过的事务里直接改考勤表。因为请假可能是明天、下周甚至下个月的事审批通过时那些日期的考勤记录可能还没生成打卡是当天实时生成的所以正确的做法是考勤统计的时候如果一个员工某天没有打卡记录但存在审批通过的请假记录则该日标记为LEAVE请假不算缺勤。也就是说月末统计的逻辑应该是这样-- 判断某员工某天是否有有效的请假记录如果有则状态为LEAVE UPDATE t_attendance a SET a.status LEAVE WHERE a.employee_id #{employeeId} AND a.status IS NULL AND EXISTS ( SELECT 1 FROM t_leave l WHERE l.employee_id a.employee_id AND l.status APPROVED AND a.work_date BETWEEN DATE(l.start_time) AND DATE(l.end_time) )这个逻辑让请假和考勤解耦了也避免了审批通过时当天考勤记录不存在导致无法更新的尴尬。4.4 统计报表的Service层设计统计报表模块我的建议是不要用简单的ListMapString, Object返回而是建一个AttendanceStatDTO把统计结果封装成一个对象。public class AttendanceStatDTO { private Integer employeeId; private String employeeName; private Integer normalDays; private Integer lateDays; private Integer earlyLeaveDays; private Integer absentDays; private Integer leaveDays; private Integer totalDays; }这样做的优势很明显前端页面使用th:object绑定的时候可以直接取属性代码可读性好而且如果统计逻辑要扩展比如增加出差类型只需要在DTO里加字段即可。Controller层的代码也会非常干净GetMapping(/report) public String report(RequestParam String month, Model model) { ListAttendanceStatDTO stats attendanceService.getMonthlyStats(month); model.addAttribute(stats, stats); return admin/report; }5. 开发环境搭建与调试部署全流程5.1 本地开发环境配置清单如果你的电脑是全新的要从零开始搭环境整个流程大概是这样的步骤工具版本建议备注1JDK1.8或11配置JAVA_HOME环境变量2Maven3.6配置阿里云镜像加速依赖下载3MySQL5.7或8.0记住root密码配置utf8mb4编码4IDEA社区版即可安装Lombok插件5Navicat可选可视化操作数据库更方便JDK环境变量配置网上教程很多这里只提一个容易忽略的点Maven的settings.xml一定要配阿里云镜像。不配镜像的话第一次加载Spring Boot依赖可能要下载十几分钟到半小时而且经常超时失败。配好镜像后基本两分钟搞定。mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror5.2 IDEA创建Spring Boot项目的两种方式在IDEA里创建Spring Boot项目有两种方式我都试过各有利弊。第一种是使用IDEA自带的Spring Initializr新建项目 - 选择Spring Initializr - 填写项目坐标 - 选择需要的依赖。这种方式最省事IDEA会帮你从start.spring.io拉取项目骨架自动生成pom.xml和主启动类。但有一个坑有时候IDEA默认访问不了start.spring.io会报连接超时这时候需要把Server URL改成阿里云的镜像地址https://start.aliyun.com。第二种是去 Spring Initializr官网 页面勾选依赖生成一个压缩包解压后用IDEA打开。这种方式的好处是可以提前看到项目的依赖树方便截图放到论文里。不管用哪种方式核心就是pom.xml里的依赖个人建议基础依赖选这四个就够了spring-boot-starter-web、spring-boot-starter-thymeleaf、mybatis-spring-boot-starter、mysql-connector-java。其他像Lombok、Validation看需求加。5.3 配置文件的编写技巧application.yml是整个项目的总开关我把一个标准的配置模板贴出来并标注了每个配置项的作用server: port: 8080 # 项目启动端口如冲突可改8081等 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/attendance_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false # 开发时关闭模板缓存修改HTML立即生效 mybatis: mapper-locations: classpath:mapper/*.xml # MyBatis的XML映射文件位置 type-aliases-package: com.example.attendance.entity # 实体类所在包 configuration: map-underscore-to-camel-case: true # 数据库下划线字段自动映射骆驼峰属性有几个关键点必须说明。serverTimezoneAsia/Shanghai这个参数是必须的否则MySQL 8的驱动在连接时会报时区错误。map-underscore-to-camel-case建议设置为true这样数据库里employee_id字段可以自动映射到实体类的employeeId属性不用每个字段都写TableField注解。thymeleaf.cachefalse强烈建议开发阶段开着否则你改了HTML要重启项目才能看到效果。5.4 调试部署的完整步骤项目开发和调试阶段的部署其实就是在IDEA里点击运行。但很多新手卡在项目能跑但数据库连不上这个阶段。我用一份标准流程来写大家跟着做就行启动MySQL服务用Navicat或命令行执行schema.sql和data.sql初始化数据库修改application.yml中的数据库账号密码为本地实际的账号密码在IDEA里打开项目等待Maven加载完所有依赖确认没有红色错误点击主启动类的main方法运行看到Tomcat started on port(s): 8080的日志说明启动成功浏览器访问http://localhost:8080/login用管理员账号登录测试要注意的一点是如果你发现日志里没有报错但页面访问404先确认一下Controller的RequestMapping路径是否和页面访问路径一致。另外运行前确保target目录下没有旧的编译残留可以用mvn clean清理一下再运行。6. 常见问题与排查技巧实录6.1 数据库相关的典型问题这一节记录的是我帮别人调试这个项目时最常遇到的几个问题每一个都是真实踩过的坑。第一个问题是启动时报Access denied for user rootlocalhost。这个比较简单就是数据库密码不对。但要注意MySQL 8的密码认证插件是caching_sha2_password有些老版本的连接驱动不支持会导致虽然密码正确但还是连不上。解决方案是把驱动升级到mysql-connector-java 8.0.x以上或者执行SQL把root用户的认证插件改成mysql_native_password。第二个问题是中文乱码。这个通常发生在插入数据或显示数据的时候。排查思路是先确认数据库和表的字符集是utf8mb4再确认JDBC连接URL里有characterEncodingutf8参数最后确认页面模板的charset也是UTF-8。三层都没问题乱码基本能解决。第三个问题很隐蔽数据库连接成功但SQL执行报Unknown column。这多半是MyBatis的resultType映射没有配置map-underscore-to-camel-case导致数据库的employee_id无法映射到实体类的employeeIdSQL查出来的字段是NULL。对照上一节的配置检查一下即可。6.2 前端页面相关的典型问题使用Thymeleaf的过程中有几个报错信息容易把人绕晕。比如说页面报Whitelabel Error Page这个太笼统了第一步要先看IDEA控制台完整的异常堆栈。最常见的几种情况是模板文件放错了目录Spring Boot默认找src/main/resources/templates下的文件如果你把HTML放在static目录里Controller是找不到的th:each语法写错了比如少写了th:前缀或者循环变量名引用不对页面上访问了不存在的属性值比如${user.name}但user对象为nullThymeleaf直接抛异常排查建议是先把thymeleaf.cachefalse确保配置了然后一步一步缩小范围先访问一个没有数据的静态页面确认模板能渲染再访问Controller返回数据的页面逐步定位问题出在哪一层。6.3 项目启动与端口问题项目能正常编译但启动不了通常有这几种情况。端口被占用是最常见的Port 8080 was already in use的报错一出来要么改application.yml里的server.port为其他端口要么用命令行杀掉占用进程。还有一个情况也经常碰到启动报找不到主类或者Unable to find main class。这个多半是因为IDEA里的Project Structure配置乱了解决方案是右键项目 - Maven - Reload Project然后Build - Rebuild Project。如果还不行把IDEA的缓存清一下File - Invalidate Caches出现时选择Invalidate and Restart。6.4 部署到服务器时的注意事项如果你要把这个系统部署到服务器上给别人用则需要额外处理几件事。打包命令用mvn clean package -DskipTests跳过测试能省不少时间。打出来的jar包在target目录下用java -jar xxx.jar运行。Linux服务器上运行的话需要处理MySQL的远程连接权限默认root用户只允许本机登录。部署时还要注意后台运行的问题直接用java -jar启动的话你退出SSH会话进程就没了。我一般用nohup java -jar xxx.jar app.log 21 来后台运行日志重定向到app.log文件里方便排查问题。7. 项目扩展方向与迭代思路完成了基础功能之后这个系统其实还可以往很多方向扩展。从课程设计的角度看多做一两个亮点功能对评分帮助很大。可以考虑的扩展方向一是加入部门维度考勤信息里带上所属部门管理员可以按部门查看统计报表这个改动不复杂但能体现你对业务的理解深度。二是增加钉钉/企业微信机器人通知每天定时把前一天未打卡的名单推送给管理员这个小功能在答辩时讲起来很有亮点。三是引入权限框架Spring Security或者Sa-Token把现在手写的登录拦截器替换成更规范的安全框架也能展示你的知识面。从我自己带项目的经验来看每次做这种管理系统数据库设计和权限设计都是最考验功底的环节。考勤系统虽然业务不复杂但表与表之间的关联关系、状态流转的逻辑、统计报表的SQL写法都是非常经典的经验积累。这个项目做完Spring Boot的基本开发套路、MyBatis的SQL编写能力、前后端联调的经验基本都能掌握后面再去接触微服务、分布式这些进阶技术就不会觉得基础不扎实了。最后再分享一个我在实际调试中的习惯每写完一个功能模块就停下手头的工作把完整的测试流程走一遍。比如写完打卡功能就注册一个测试员工模拟早上晚到一小时打卡确认状态是迟到再模拟正常上班打卡确认状态是正常。这种写完就测的习惯看起来慢实际上能帮你省下后面联调时大量的排查时间。考勤系统这种课程设计级别的项目本身没有太多高深的技术难点最大的难点反而在于细心和耐心把每一个业务细节都考虑到位系统自然就稳定可靠了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进