ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SSM员工考勤管理系统设计与实现详解:从零搭建到功能扩展

SSM员工考勤管理系统设计与实现详解:从零搭建到功能扩展 作为一个在Java开发这条路上摸爬滚打了好几年的人我太清楚SSM员工考勤管理系统这类项目在大家学习生涯中的分量了。基本上每个学Java的、做课程设计的、准备毕业设计的都会遇到这个“员工考勤管理系统”它几乎成了SSM框架入门和综合运用的标准练手项目。今天我想把这个项目的来龙去脉、核心细节、实现过程还有我当年踩过的坑系统地聊一聊希望能给正在做这个项目的你一些实在的参考。这个项目本身不难但涉及的知识点非常综合Spring的IoC和AOP、SpringMVC的请求流转、MyBatis的持久层映射、前端页面的数据展示、以及Excel导出这类实用功能。它更像是把大学里学的Java Web知识串起来的一个闭环。无论你是刚开始学SSM、准备课程设计答辩还是想找一个能写进简历的练手项目这个考勤系统都非常合适。它能让你理解一个“真实”的业务系统是怎么从零搭建起来的而不是只停留在写几个增删改查的Demo层面。提示整个项目的源码结构、业务逻辑和设计思路我会尽量还原并把核心代码和配置都贴出来。如果你正卡在某个报错或者某个功能实现上可以直接跳到对应的章节去查。1. 考勤系统项目整体设计与模块拆解很多人拿到项目第一反应是“先跑起来再说”这没错但要想真正理解这个系统尤其是面试或答辩时能讲清楚设计思路我们得先退一步从业务角度把整个系统的功能结构梳理明白。考勤系统本质上解决的是“谁、在什么时间、做了什么考勤动作、结果如何被统计和审批”这一条主线问题。1.1 系统角色与权限边界一个经典的考勤管理系统围绕用户身份通常会划分出三类角色这三类角色决定了你能看到什么页面、能操作什么功能管理员拥有全部权限负责员工信息管理、部门管理、考勤规则设置上下班时间、迟到阈值、旷工判断等、查看所有员工的考勤统计、处理请假/加班申请。普通员工核心操作是上下班打卡查看自己的考勤记录和统计结果提交请假申请、加班申请以及查看个人休假余额。部门经理可选角色很多课设会合并到管理员中主要负责审批本部门员工的请假和加班申请。在SSM框架里这个权限控制通常通过拦截器HandlerInterceptor来实现。我们在登录成功后把用户信息存到Session里然后写一个拦截器在请求进入Controller之前判断当前访问的路径对应哪个角色如果角色不匹配就直接重定向到登录页或者权限不足提示页。一开始我不太建议直接上Spring Security或者Shiro因为这些安全框架本身的配置和概念对新手来说又是一层额外负担。用拦截器手写一遍角色校验你对请求流转的理解会更深刻。1.2 核心功能模块一览我在设计这个系统时把功能模块划成了七大块这七大块也是市面上绝大多数考勤系统的基础模块功能说明关联角色登录与登出基于Session的登录校验、验证码校验、密码加密存储所有角色员工管理员工信息的增删改查、按部门筛选、启用/禁用账号管理员部门管理部门结构维护部门下员工数量统计管理员考勤打卡上班卡、下班卡、打卡时间记录、迟到早退标记员工考勤统计按月/按日查看考勤汇总支持导出Excel、图表展示管理员请假管理请假申请、审批流、销假、请假记录查询员工、管理员加班管理加班申请、审批、加班时长统计员工、管理员这个结构比较经典。其中考勤打卡和考勤统计是核心中的核心也是代码里最能体现业务逻辑难度的地方。后面我会细讲这两块是怎么实现的。1.3 为什么选择SSM而不是Spring Boot这是个绕不开的问题。既然现在Spring Boot都普及了为什么课程设计还是SSM我的理解是SSM项目能让你看到更多的“手动配置”过程web.xml里注册DispatcherServlet、applicationContext.xml里配置数据源和事务管理器、spring-mvc.xml里开启注解驱动、mybatis-config.xml里配置驼峰映射。这些配置在Spring Boot里可能只是一行依赖或者一个注解但在SSM里你必须自己动手也就被迫理解了底层是怎么工作的。如果面试官问你项目经验你讲SSM项目时能说出“我在spring-mvc.xml里配置了静态资源映射因为DispatcherServlet会拦截所有请求如果不配置js和css文件会被当作Controller去扫描处理导致404”这句话就是加分项。而在Spring Boot项目里你根本感知不到这个问题。2. 核心技术栈与工具选型工具选型这块在写项目文档的时候经常被一笔带过但恰恰是“为什么选它”这个部分能让你的项目报告看起来不是一个简单的功能堆砌。我这里结合这个考勤系统的实际需求把技术选型的理由和版本选择说清楚。2.1 后端框架组合后端三层架构非常清晰Spring层负责管理所有对象的生命周期和依赖关系也就是IoC容器。同时用声明式事务管理处理考勤数据的写入一致性——比如打卡时既要插入考勤记录又要更新当天的考勤状态统计任何一步失败都要回滚。SpringMVC层负责接收前端请求把请求参数绑定到Controller方法入参调用Service层处理业务最后把Model数据渲染到视图。它的执行流程是面试必考题但实际操作中我们只需要关注Controller、Service、Mapper这三层的调用关系。MyBatis层负责SQL与Java方法的映射。我选择MyBatis而不是JPA主要是因为它对SQL有完全的控制权。考勤统计经常需要写复杂的多表关联和条件统计SQL用MyBatis的where标签和foreach标签处理动态条件特别方便。2.2 开发工具与数据库版本IDEA这个项目就是基于IDEA构建的。IDEA对Maven和SSM项目的支持比Eclipse流畅很多尤其是CtrlAltB查看接口实现类这个方法排查问题时效率极高。Maven用Maven做依赖管理。这个没什么好说的关键在于pom.xml里的依赖版本要统一避免冲突。Tomcat 8.5 JDK 1.8经典组合。JDK 1.8虽然老了但稳定适配绝大多数教材和网上教程排查问题的成本也最低。MySQL 5.7业务量不大的教务场景完全够用。如果使用MySQL 8需要注意驱动类名变更成com.mysql.cj.jdbc.Driver并且连接URL里要加serverTimezoneAsia/Shanghai不然会报时区错误。2.3 前端技术方案前端我用了比较轻量级的方案JSP JSTL Bootstrap jQuery Layui。很多人在毕业设计中纠结要不要用Vue我的建议是如果没有强制要求SSM项目就用JSP就好因为它可以让后端渲染和页面联动更直观。如果你用Vue前后端分离意味着你还需要处理跨域问题这又是一个新的坑。在SSM课设阶段保证核心业务完整跑通、流程清晰比追求前端技术炫酷重要得多。页面上负责打卡区域我用了jQuery定时器去刷新当前时间同时用AJAX异步请求后端打卡接口这样可以做到不刷新页面就展示打卡成功的结果。3. 数据库表设计与核心流程实现数据库设计是考勤系统的灵魂。我第一次独立做这个项目时只设计了三张表结果写到统计功能时发现数据根本对不上后来推翻重来。这个部分我直接把我最后整理出来的、经历过多轮测试的表结构分享出来。3.1 核心表结构设计我用了五张核心业务表外加两张辅助表第一张是员工表字段包括id、emp_no工号、name、password、dept_id、position、phone、email、hire_date、status。这里特别说明一下密码不要明文存储至少用MD5加盐加密。我见过很多课程设计直接明文存密码答辩时如果老师问一句“你的密码安全吗”就容易被问住了。用DigestUtils.md5DigestAsHex((password salt).getBytes())就能简单实现加盐。第二张是部门表字段比较简单。第三张是考勤记录表这是核心中的核心字段包括id、emp_id、att_date日期、clock_in_time上班打卡时间、clock_out_time下班打卡时间、status正常、迟到、早退、缺卡、旷工。同时我加了一个work_hours字段用来存储当天实际工作时长方便月底统计。第四张是请假表字段包括id、emp_id、leave_type事假/病假/年假/调休、start_time、end_time、reason、status待审批/通过/驳回、approver_id。第五张是加班表结构跟请假表类似但多了一个overtime_hours字段。为了让权限和菜单灵活一点我额外加了user_role和menu两张表。很多管理人员角色的SSM项目会忽略菜单表直接在页面写死菜单其实在课设答辩中有动态菜单是一个不错的亮点。3.2 打卡与迟到判断的SQL与业务逻辑打卡的核心逻辑在Service层。我先根据emp_id和att_date查询当天是否已经有考勤记录。如果没有说明是今天第一次打卡记上班时间。如果已经有记录则更新下班时间。这里有个细节设计表结构时最好把上下班打卡放在同一条记录里而不是每次打卡插入一条记录。否则统计考勤天数时还得按天分组去重麻烦且容易出错。迟到和早退怎么判断我在t_config表里存了早上上班时间比如09:00和下午下班时间比如18:00。打卡时用一个工具方法把ClockInTime和上班时间比较如果打卡时间比上班时间晚就自动把状态置为“迟到”否则置为“正常”。下班同理如果下班打卡时间比标准下班时间早就置为“早退”。这个判断逻辑写在AttendanceServiceImpl里核心代码大致如下public int handlePunch(Integer empId, Date punchTime, String type) { Attendance record attendanceMapper.selectByEmpIdAndDate(empId, new Date()); // 读取考勤配置 Config config configMapper.selectByKey(work_start_time); String workStart config.getConfigValue(); // 形如 09:00 SimpleDateFormat sdf new SimpleDateFormat(HH:mm); Date startTime sdf.parse(workStart); if (上班.equals(type)) { if (record null) { // 第一次打卡 Attendance att new Attendance(); att.setEmpId(empId); att.setAttDate(new Date()); att.setClockInTime(punchTime); // 判断是否迟到 String punchTimeStr sdf.format(punchTime); Date punchDate sdf.parse(punchTimeStr); if (punchDate.after(startTime)) { att.setStatus(迟到); } else { att.setStatus(正常); } return attendanceMapper.insert(att); } } else if (下班.equals(type)) { if (record ! null) { record.setClockOutTime(punchTime); // 判断是否早退 String configStr configMapper.selectByKey(work_end_time).getConfigValue(); Date endTime sdf.parse(configStr); String punchTimeStr sdf.format(punchTime); Date punchDate sdf.parse(punchTimeStr); if (punchDate.before(endTime)) { record.setStatus(早退); } return attendanceMapper.updateById(record); } else { // 没有上班打卡直接打下班卡 return -1; // 前端提示先打上班卡 } } return 0; }这里有一个关键点判断迟到时不能拿Date对象直接比。因为Date里包含年月日而配置的上班时间是“时分”格式强转可能产生跨天误差。稳妥的做法是统一格式化成HH:mm字符串再解析成Date比较或者用LocalTime来比JDK 1.8完全支持。3.3 月度考勤统计与图表展示月度统计这部分是常见的难点。我需要做出一个可以同时展示“出勤天数、迟到次数、早退次数、请假天数、加班时长”的汇总报表。如果只用一条SQL会写得很复杂且难以维护。我的做法是分步聚合先查该月每天每人一条的记录再在Java代码里做二次统计。具体思路是调用attendanceMapper.selectByMonth(empId, yearMonth)查出该员工这个月所有考勤记录然后在Service层遍历用Calendar或LocalDate判断是工作日还是周末统计出勤。同时把每条记录里的迟到、早退、缺卡状态分别累加计数。请假的天数则是查请假表中状态为“通过”且时间跨度为该月的记录按天折算。最后把这些数据封装到一个AttendanceStatVO对象里Controller把它传到页面使用ECharts的柱状图展示每月加班情况用饼图展示考勤状态分布。注意周末打卡和节假日打卡不要计入正常出勤否则统计会虚高。最简单的办法是维护一张节假日配置表先判断是否为周末再判断是否节假日。虽然有些粗糙但作为课程设计足够了。4. 关键功能模块代码实现详解从这一步开始我们把项目拆开按照“登录 - 员工管理 - 打卡 - 审批 - 统计导出”这条业务链路把每个环节的核心实现和细节讲透。这一部分也是你写需求文档和项目报告时最重要的素材。4.1 登录流程与拦截器权限校验登录模块虽然简单但我建议把校验码、密码加密、Session管理都做进去。Controller层面接收用户名、密码、验证码先用kaptcha生成的验证码和用户输入比对不一致直接返回错误信息。然后查库比对用户名和密码成功则把员工信息和角色放入Session。拦截器实现权限校验这一步建议用HandlerInterceptor。preHandle方法里判断请求URI是否包含/admin/路径如果包含再判断Session中是否有用户以及角色是否是管理员。如果不是则返回false并重定向到403页面。静态资源一定要在spring-mvc.xml里放行mvc:resources mapping/static/** location/static//否则你的login.js和style.css和被拦截器拦截页面会变得很难看这是新手最容易踩的坑。4.2 员工管理的分页与条件查询员工管理就是一个标准的分页加条件查询。我用的是PageHelper插件pom.xml引入pagehelper依赖然后在mybatis-config.xml里配置插件。Controller中接收pageNum、pageSize、deptId、keyword参数调用Service层时先PageHelper.startPage(pageNum, pageSize)然后再执行查询返回的PageInfo对象里包含了总记录数、总页数、当前页数据列表等所有分页信息直接放进Model传给前端即可。dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper/artifactId version5.1.11/version /dependency在Mapper XML里条件是动态的这里一定要用where标签不然容易出现多条件拼接时多一个and的问题select idselectByCondition resultTypecom.example.entity.Employee SELECT * FROM t_employee where if testdeptId ! null and deptId ! AND dept_id #{deptId} /if if testkeyword ! null and keyword ! AND (name LIKE CONCAT(%, #{keyword}, %) OR emp_no LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY hire_date DESC /select4.3 请假审批流程与状态流转请假审批流程我采用了简单的单级审批员工提交申请后状态为“待审批”管理员登录后看到待审批列表点击通过或驳回。这里要注意一个细节驳回时要填写驳回原因而且点击“通过”之后要考虑是否自动减少员工的剩余请假天数。我实现时是在审批通过的事务里同时更新t_leave_balance表中的对应假期余额保证事务一致性。审批通过后的操作不复杂但逻辑顺序比较重要先更新请假单状态再扣减假期余额最后记录审批人和审批时间。这三步在一个Transactional方法里完成任何一步异常都整个回滚。这里体现出SSM里声明式事务的价值。4.4 考勤Excel导出Excel导出是考勤系统里比较亮眼的一个功能也是很多同学觉得有难度的地方。其实实现方式非常简单用Apache POI在Java代码里创建HSSFWorkbook创建Sheet和Row遍历考勤统计结果填进去最后把workbook写入HttpServletResponse的输出流同时设置响应头让浏览器下载文件。关键代码片段如下public void exportAttendance(HttpServletResponse response, Integer empId, String yearMonth) throws IOException { ListAttendanceStatVO list attendanceService.getMonthStat(empId, yearMonth); // 创建Excel工作簿 HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(考勤统计); String[] headers {工号, 姓名, 出勤天数, 迟到次数, 早退次数, 请假天数, 加班时长}; HSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } int rowNum 1; for (AttendanceStatVO vo : list) { HSSFRow row sheet.createRow(rowNum); row.createCell(0).setCellValue(vo.getEmpNo()); row.createCell(1).setCellValue(vo.getEmpName()); row.createCell(2).setCellValue(vo.getWorkDays()); row.createCell(3).setCellValue(vo.getLateCount()); row.createCell(4).setCellValue(vo.getEarlyCount()); row.createCell(5).setCellValue(vo.getLeaveDays()); row.createCell(6).setCellValue(vo.getOvertimeHours()); } response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment;filenameattendance_ System.currentTimeMillis() .xls); workbook.write(response.getOutputStream()); workbook.close(); }注意导出时文件的编码问题文件名如果是中文在不同浏览器下可能乱码稳妥起见用URLEncoder.encode(fileName, UTF-8)组装一下。5. 常见问题与排查技巧实录这部分我整理了做SSM考勤系统时大家最容易遇到的几个坑每一个都是我自己或帮学弟调代码时真实碰到过的。把它们列成速查表遇到问题更快定位。问题现象可能原因解决方案Tomcat启动报ClassNotFoundException: DispatcherServletspring-webmvc依赖没引入或版本冲突检查pom.xml中spring-webmvc依赖清理Maven本地仓库后reimport页面中文乱码JSP编码不统一、数据库连接URL缺characterEncodingJSP页面加pageEncodingUTF-8连接URL加characterEncodingutf-8使用CharacterEncodingFilterMyBatis报Invalid bound statement (not found)Mapper接口和XML文件的namespace或方法id对不上检查XML文件的namespace是否指向接口的全限定名方法id是否和接口方法名一致账号能登录但页面静态资源全挂DispatcherServlet拦截了/static/*请求在spring-mvc.xml中加mvc:resources mapping/static/** location/static//打卡后日期对不上MySQL时区问题JDBC URL配置serverTimezoneAsia/Shanghai数据库连接字符集配置utf-8PageHelper分页不生效PageHelper版本与MyBatis版本不兼容升级到pagehelper 5.1.11以上并在mybatis-config.xml中正确配置插件5.1 数据库连接配置的隐藏坑如果你用Maven管理依赖jdbc.properties文件里配置的URL一定要小心。MySQL 5.7一般用jdbc:mysql://localhost:3306/db_name?useUnicodetruecharacterEncodingutf-8但MySQL 8之后驱动类从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver同时URL里还需要加serverTimezoneAsia/Shanghai否则连数据库会直接抛SQLException: The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。这个报错很常见遇到别慌改URL配置就行。5.2 Spring事务不生效的排查思路在SSM里事务是配在Service层的如果你发现数据写了一半报错却没有回滚先检查三件事第一applicationContext.xml里是否配置了tx:annotation-driven第二Service类是否被Spring扫描到也就是包扫描路径对不对第三方法是否为public因为Spring的声明式事务默认基于动态代理只有public方法才会被拦截。还有一个容易忽略的点是同一个类内部方法自调用时注解事务是不生效的因为调用发生在对象内部而没有经过代理对象。5.3 部署路径与访问地址的坑IDEA中部署Web项目时Application context默认是/访问地址就是http://localhost:8080/。有些同学改了Application context为/ssm_attendance然后访问的时候忘记加这个前缀一直404排查了半天。再一个就是Tomcat的Deployment里要把artifact选成war exploded不然每次修改代码都要重新打包非常耽误时间。6. 项目升级与扩展方向建议如果课程设计要求不复杂做到上面这个程度已经可以答辩了。但如果你想让项目“更有看点”或者在简历里多写两行以下几个扩展点可以试试成本和收益都比较可观。6.1 引入Redis缓存打卡状态打卡操作是高频率写入每次查询剩余假期、当前考勤状态时都要查数据库。可以把当天已经打卡的信息放入Redis以attendance:{empId}:{date}为KeyValue存储打卡时间和状态缓存时间设为当天结束。这样打卡接口的响应速度会明显提升而且可以在答辨认“为什么要引入缓存”时讲得头头是道。6.2 用Quartz定时任务自动处理未打卡员工系统可以配置一个定时任务比如每天23:59执行一次查询当天未打下班卡的员工自动把他们的考勤状态更新为“缺卡”或者“早退”不用人工每天检查。Quartz在SSM里整合不算复杂引入quartz依赖配置SchedulerFactoryBean写一个Job继承QuartzJobBean在executeInternal方法里写统计逻辑就行。6.3 增加简单的消息通知当审批通过或者驳回时给员工生成一条站内信员工登录后在首页下拉框中看到未读消息数。这个功能可以自己建一张t_message表也可以接入WebSocket做实时通知。考虑到课程设计的工作量建表加轮询就足够了但描述起来可以叫“轻量级消息模块”。6.4 前端图表可视化目前统计展示用了ECharts这是纯前端组件。你可以把首页改造为一个数据驾驶舱今日应到人数、实到人数、迟到人数、请假人数用4个统计卡片展示下面再用折线图展示最近7天的考勤趋势。前端实现不复杂但对项目整体观感提升非常明显的。7. 写在最后的实操心得这个SSM员工考勤管理系统我自己前前后后完整写过一遍也带朋友改过好几遍。最大的感受是真正难的不是某个框架的某一个API而是怎么把一个个独立的功能点串成一个完整的业务闭环。你单独写一个打卡接口很简单写一个请假表CRUD也不难但把“员工打卡”和“月度统计”、“请假审批”、“加班时长”之间用数据关联起来让整条流程自洽这才是项目真正的价值所在。如果你也是拿来作为课程设计或毕业设计我给一个非常实在的建议不要直接去下载一个源码然后改个名字交上去。哪怕你把源码里每一个Controller、每一张表、每一条SQL都自己动手敲一遍收获都会很大。因为答辩时老师很可能会翻到某个细节问你没有亲手做过很难真正讲清楚。最后再分享一个小技巧在IDEA里调试SSM项目时把MyBatis的SQL日志打开。在applicationContext.xml里配置log4j或者直接在application.yml里配好logging.level让控制台能打印出每次执行的SQL语句和参数。这样一来遇到“数据对不上”的问题时你能很清楚地看到是不是SQL条件多了或者少了定位问题的速度直接翻倍。这个小习惯我一直用到现在很有用。
返回列表