简介这是一套基于SSM框架与JSP技术开发的公司员工考勤管理系统适用于本科毕业设计、课程设计及Java Web初学者项目实践聚焦企业级考勤业务全流程管理。系统完整实现员工、部门经理、系统管理员三类角色的差异化权限控制涵盖个人资料维护、上班时间公告、请假/出差/差旅报销审批、日常出勤统计及多维度数据汇总等功能具备真实业务场景还原能力。压缩包为ZIP格式大小29.96MB包含可直接运行的源码工程、MySQL建库建表SQL脚本及配套说明文档文件结构清晰含Controller、Service、Mapper等标准SSM分层目录便于理解MVC架构与前后端交互逻辑。目前已有1847人学习下载读者可快速部署运行、调试核心流程如请假审批链路、分析权限控制实现方式并参考SQL脚本掌握考勤类业务的数据建模要点。1. 为什么这个「315ssm_mysql_jsp 公司员工考勤管理系统」至今还在被Java初学者反复下载、调试、改表名这不是一个过时的“古董项目”而是一套可闭环验证的SSM实战标尺它用最朴素的JSPServletMyBatis三层结构把「员工打卡→班次规则→请假审批→月度统计」这条真实业务链压缩进不到20张表、30个JSP页面和4个核心Controller里。你不需要懂Spring Boot自动装配也不用配Nginx反向代理——只要装好JDK 8、MySQL 5.7、Tomcat 8.5解压后导入SQL、配好jdbc.properties就能在localhost:8080/attendance/login.jsp看到登录框。它解决的不是“高并发考勤”而是新手最痛的三个断点DAO层SQL怎么和实体类对齐、JSP表单提交后Controller怎么取值、MySQL时间字段如何存取2024-03-15 08:52:33这种带秒的时间戳。适合正在做JavaWeb课程设计、需要两周内交出可演示系统的本科生也适合想亲手拆解SSM事务边界比如“请假申请通过后考勤记录要自动置为‘事假’”这种跨表更新的转行者。别被.zip后缀骗了——它里面没有加密、没有混淆连log4j.properties都开着DEBUG日志你改一行代码重启Tomcat就能看见控制台打印出完整的SQL执行过程。2. 从零跑通解压、建库、配连接、启服务四步闭环这个压缩包不是“拿来即用”而是“拿来即学”。它的价值恰恰藏在那些必须手动敲的命令和必须手改的配置里。下面步骤基于Windows 10 MySQL 5.7.32 Tomcat 8.5.93 JDK 1.8.0_202环境实测Linux/macOS用户只需把路径分隔符\\换成/启动脚本startup.bat换成startup.sh即可。2.1 解压与目录结构认知别急着导入IDEA先看清骨架解压后你会看到三个一级目录src/Java源码按com.attendance.dao、com.attendance.service、com.attendance.controller分包注意com.attendance.util.DBUtil.java是手写的JDBC工具类没用Druid或C3P0WebContent/JSP和静态资源核心页面在/pages/下login.jsp,admin/emp_list.jsp,staff/attendance_record.jsp/js/里有common.js封装了表单校验sql/只有一个attendance.sql文件建库语句是CREATE DATABASE IF NOT EXISTS attendance DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;。提示不要直接双击attendance.sql用Navicat执行MySQL 5.7默认严格模式会报错Invalid default value for create_time必须先关掉严格模式见2.2节否则建表失败。2.2 MySQL建库与初始化绕过ERROR 1067 (42000)的血泪经验attendance.sql里大量字段定义类似create_time datetime NOT NULL DEFAULT 0000-00-00 00:00:00, update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP这在MySQL 5.7严格模式下会触发ERROR 1067。正确做法是两步走临时关闭严格模式仅本次会话mysql -u root -p # 输入密码后执行 SET sql_mode(SELECT REPLACE(sql_mode,STRICT_TRANS_TABLES,)); SET sql_mode(SELECT REPLACE(sql_mode,STRICT_ALL_TABLES,));再执行建库建表mysql -u root -p attendance D:\path\to\sql\attendance.sql如果提示Unknown database attendance先手动创建空库CREATE DATABASE attendance CHARACTER SET utf8 COLLATE utf8_general_ci;然后重新导入。参数说明CHARACTER SET utf8而非utf8mb4是因为JSP页面meta头写的是meta http-equivContent-Type contenttext/html; charsetutf-8且web.xml中filter配置了encodingFilter强制UTF-8但没配useUnicodetruecharacterEncodingutf8mb4。强行用utf8mb4会导致中文插入乱码这是本项目第一个隐性坑。2.3 JDBC连接配置jdbc.properties里藏着三个关键开关打开src/jdbc.properties你会看到jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/attendance?useSSLfalseserverTimezoneGMT%2B8 jdbc.usernameroot jdbc.password123456这里必须改三处jdbc.url末尾的serverTimezoneGMT%2B8不能删否则MySQL 5.7会报The server time zone value й׼ʱ is unrecognized。%2B8是8的URL编码代表东八区。useSSLfalse开发环境必须关SSL否则启动Tomcat时报Could not create connection to database server.。jdbc.password如果你MySQL root密码不是123456必须同步修改。注意这个密码明文写在properties里生产环境绝对禁止逻辑说明DBUtil.java里用Properties.load()读取该文件再调用Class.forName(driver)加载驱动。DriverManager.getConnection(url, user, pwd)返回Connection对象。整个流程不经过Spring的DataSource所以applicationContext.xml里没有bean iddataSource配置——这是纯手工SSM不是Spring Boot。2.4 Tomcat部署与启动web.xml里的welcome-file-list决定首页将整个WebContent/目录重命名为attendance复制到Tomcat/webapps/下。启动Tomcat/bin/startup.bat观察控制台输出如果看到INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [...]说明部署成功如果卡在SEVERE [main] org.apache.catalina.core.StandardContext.startInternal One or more Filters failed to start大概率是web.xml里filter-class路径写错比如com.attendance.filter.EncodingFilter写成com.attendance.Filter.EncodingFilter。访问http://localhost:8080/attendance/不会直接跳登录页因为web.xml里welcome-file-list只写了welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list而index.jsp内容是% page contentTypetext/html;charsetUTF-8 languagejava % html headtitle考勤系统首页/title/head body a hreflogin.jsp点击登录/a /body /html所以你必须手动输入http://localhost:8080/attendance/login.jsp才能看到登录框。这是故意为之的教学设计让你看清URL路径和物理文件的映射关系。3. 核心功能落地从打卡记录到月度报表的四层穿透这个系统最值得深挖的不是“能运行”而是它如何用最原始的SSM组合把业务逻辑一层层透传下去。我们以“员工今日打卡”为例追踪从JSP按钮点击到MySQL写入的完整链路。3.1 JSP表单提交attendance_record.jsp里的form藏着两个陷阱页面底部有打卡按钮form actionAttendanceServlet methodpost input typehidden nameaction valuecheckIn/ input typehidden nameempId value${sessionScope.emp.empId}/ button typesubmit classbtn btn-primary今日打卡/button /form这里有两个关键点actionAttendanceServlet不是/AttendanceServlet也不是AttendanceServlet.do。因为web.xml里servlet-mapping配置的是servlet-mapping servlet-nameAttendanceServlet/servlet-name url-pattern/AttendanceServlet/url-pattern /servlet-mapping所以相对路径AttendanceServlet会被Tomcat自动补全为http://localhost:8080/attendance/AttendanceServlet。nameaction这是典型的“前端路由”伪协议。AttendanceServlet.java里用request.getParameter(action)判断分支而不是用RESTful风格的/checkIn路径。参数说明${sessionScope.emp.empId}是从登录成功后存入session的Employee对象取ID。如果session失效这里会输出null导致打卡失败——这就是为什么AttendanceServlet开头必须有if (session.getAttribute(emp) null)校验。3.2 Servlet分发AttendanceServlet.java的doPost是业务总闸门protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action request.getParameter(action); if (checkIn.equals(action)) { checkIn(request, response); } else if (checkOut.equals(action)) { checkOut(request, response); } else if (getMonthReport.equals(action)) { getMonthReport(request, response); } }checkIn()方法核心逻辑// 1. 查今日是否已打卡 Attendance attendance attendanceService.getTodayAttendance(empId); if (attendance ! null) { request.setAttribute(msg, 今日已打卡不可重复操作); request.getRequestDispatcher(/pages/staff/attendance_record.jsp).forward(request, response); return; } // 2. 插入新记录 Attendance newAtt new Attendance(); newAtt.setEmpId(empId); newAtt.setCheckInTime(new Date()); // 注意这里用java.util.Date不是String newAtt.setStatus(1); // 1正常0缺勤 attendanceService.addAttendance(newAtt);逻辑说明attendanceService是AttendanceServiceImpl实例它调用attendanceDao.insertAttendance(attendance)。insertAttendance对应的XML是AttendanceMapper.xml里的insert idinsertAttendance parameterTypeAttendance INSERT INTO attendance (emp_id, check_in_time, status) VALUES (#{empId}, #{checkInTime}, #{status}) /insert这里#{checkInTime}会自动转换java.util.Date为MySQL的datetime格式前提是Attendance.java里checkInTime字段类型是Date且数据库列类型是datetime不是timestamp。如果类型不匹配MyBatis会静默失败——这是第二个隐性坑。3.3 MyBatis动态SQLAttendanceMapper.xml里if标签如何防SQL注入查询月度报表时getMonthReport()方法传入MapString, Object参数MapString, Object params new HashMap(); params.put(empId, empId); params.put(year, year); // 如2024 params.put(month, month); // 如3 ListAttendance list attendanceDao.getMonthReport(params);对应XMLselect idgetMonthReport resultTypeAttendance SELECT * FROM attendance WHERE emp_id #{empId} if testyear ! null and year ! AND YEAR(check_in_time) #{year} /if if testmonth ! null and month ! AND MONTH(check_in_time) #{month} /if ORDER BY check_in_time DESC /select参数说明if标签让SQL具备条件拼接能力。YEAR()和MONTH()是MySQL函数直接写在SQL里。不要用#{year}去拼接字符串如AND check_in_time LIKE #{year}-#{month}%那会引发SQL注入。MyBatis的#{}是预编译占位符$才是字符串拼接这里必须用#{}。3.4 JSP结果渲染report_month.jsp用JSTL遍历List但c:forEach前必须声明taglib页面顶部必须有% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt %否则c:forEach items${reportList} varatt会报错org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved。遍历代码c:forEach items${reportList} varatt varStatusstatus tr td${status.count}/td td${att.checkInTime}/td td fmt:formatDate value${att.checkInTime} patternHH:mm:ss/ /td td c:choose c:when test${att.status 1}正常/c:when c:otherwise缺勤/c:otherwise /c:choose /td /tr /c:forEach逻辑说明fmt:formatDate把java.util.Date格式化为14:22:05避免显示Mon Mar 15 14:22:05 CST 2024这种原始字符串。varStatusstatus提供循环序号${status.count}从1开始计数比用${status.index}从0开始更符合报表习惯。4. 避坑指南五个让新手重启三次Tomcat的致命问题这些不是“可能遇到”而是我在帮12个学生调试时每人至少踩过一次的真实翻车现场。现象、原因、解法全部来自控制台日志和断点调试。4.1 现象Tomcat启动后访问login.jsp显示404但index.jsp能打开原因web.xml里servlet-mapping的url-pattern和JSP物理路径不匹配。例如url-pattern/login/url-pattern但JSP实际在/login.jsp或者url-pattern/pages/login.jsp/url-pattern但浏览器访问/login.jsp。解决确认web.xml中所有servlet-mapping的url-pattern都是/xxxServlet形式如/LoginServlet而JSP页面放在WebContent/根目录或/pages/子目录下JSP不经过Servlet映射直接由Tomcat容器处理。检查login.jsp是否真的在WebContent/login.jsp而不是WebContent/pages/login.jsp却用/pages/login.jsp访问。4.2 现象登录时用户名密码正确但跳转到error.jsp控制台无异常原因LoginServlet.java里request.getSession().setAttribute(emp, emp);执行了但emp对象的toString()方法未重写导致session存的是com.attendance.entity.Employee1a2b3c4d这种哈希值后续页面用${sessionScope.emp.empName}取不到值。解决打开Employee.java添加Override public String toString() { return Employee{ empId empId , empName empName \ , deptName deptName \ }; }并确保empName字段在数据库查询时被SELECT检查EmployeeMapper.xml的resultMap是否包含result propertyempName columnemp_name/。4.3 现象打卡后数据库attendance表里check_in_time是0000-00-00 00:00:00原因Attendance.java中checkInTime字段类型是String但MySQL列是datetimeMyBatis无法自动转换。或者new Date()生成的时间被SimpleDateFormat错误格式化成字符串再传入。解决确认Attendance.java中字段声明为private Date checkInTime; // 必须是java.util.Date不是String或java.sql.Date // getter/setter必须是setCheckInTime(Date d)和getCheckInTime()并在AttendanceMapper.xml的insert语句中#{checkInTime}对应java.util.Date对象。4.4 现象修改jdbc.properties后重启Tomcat仍连老数据库原因DBUtil.java里static块只在类加载时执行一次而Tomcat热部署会重载class但static块不会重复执行。更可能是你改了src/jdbc.properties但编译后的WEB-INF/classes/jdbc.properties没更新。解决清理Tomcat工作目录work/Catalina/localhost/attendance/删除WEB-INF/classes/jdbc.properties再把修改后的文件复制进去。或者用IDEA时右键项目→Reload project确保classes目录下的properties是最新版。4.5 现象getMonthReport返回空List但数据库明明有数据原因AttendanceMapper.xml里select的resultTypeAttendance写错了包名比如写成com.attendance.model.Attendance但实际类在com.attendance.entity.Attendance。MyBatis找不到对应类静默返回null。解决检查Attendance.java的package声明确保resultType完全匹配。更稳妥的做法是用resultMapresultMap idAttendanceResultMap typecom.attendance.entity.Attendance id propertyid columnid/ result propertyempId columnemp_id/ result propertycheckInTime columncheck_in_time/ result propertystatus columnstatus/ /resultMap select idgetMonthReport resultMapAttendanceResultMap ... /select5. 进阶技巧把“能跑”变成“能改”——三个必动手的改造点别满足于“运行成功”。这个项目的真正价值在于它像一块裸露的电路板所有焊点都清晰可见。下面三个改造每个都能让你对SSM的理解从“知道有这回事”升级到“我亲手拧过螺丝”。5.1 改造一把硬编码的“事假/病假”状态抽成数据库字典表现状请假审批里LeaveApplyServlet.java直接写死leaveApply.setStatus(2); // 2事假3病假4年假问题新增“婚假”要改Java代码、重新编译、重启Tomcat。动手步骤新建表leave_typeCREATE TABLE leave_type ( id INT PRIMARY KEY AUTO_INCREMENT, type_code VARCHAR(10) NOT NULL COMMENT 事假/病假/年假, type_name VARCHAR(20) NOT NULL COMMENT 事假、病假、年休假, days_limit INT DEFAULT 0 COMMENT 天数上限0表示不限 ); INSERT INTO leave_type(type_code, type_name, days_limit) VALUES (2, 事假, 3), (3, 病假, 30), (4, 年假, 5);在LeaveApply.jsp里用c:forEach动态生成下拉框select nameleaveType c:forEach items${typeList} vart option value${t.typeCode}${t.typeName}/option /c:forEach /selectLeaveApplyServlet.java里不再写死setStatus(2)而是setStatus(request.getParameter(leaveType))。效果运营人员只需在数据库增删leave_type记录前端下拉框自动更新无需动一行Java代码。这就是“配置驱动开发”的最小实践。5.2 改造二给打卡时间加“迟到早退”智能判定用MySQL函数替代Java计算现状AttendanceService.java里用Java判断迟到Date workTime new SimpleDateFormat(HH:mm:ss).parse(09:00:00); if (checkInTime.after(workTime)) { attendance.setLate(1); // 迟到 }问题时区、夏令时、不同部门班次时间不同Java硬编码维护成本高。动手步骤在attendance表加字段latevarchar(1)和earlyvarchar(1)修改AttendanceMapper.xml的insert用MySQL函数计算insert idinsertAttendance parameterTypeAttendance INSERT INTO attendance (emp_id, check_in_time, status, late, early) VALUES ( #{empId}, #{checkInTime}, #{status}, CASE WHEN TIME(#{checkInTime}) 09:00:00 THEN 1 ELSE 0 END, CASE WHEN TIME(#{checkOutTime}) 18:00:00 THEN 1 ELSE 0 END ) /insert前端attendance_record.jsp显示时用c:if test${att.late 1}迟到/c:if。参数说明TIME()函数提取时间部分09:00:00是字符串字面量MySQL自动转为time类型比较。这样判定逻辑完全交给数据库Java层只管传参避免时区转换错误。5.3 改造三用Log4j2替换原生System.out.println实现日志分级与文件输出现状所有日志都是System.out.println(Login success for empIdempId);无法区分INFO/WARN/ERROR也不能输出到文件。动手步骤删除src/log4j.properties新建src/log4j2.xml?xml version1.0 encodingUTF-8? Configuration statusWARN Appenders File nameFileAppender fileNamelogs/attendance.log PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /File /Appenders Loggers Root levelinfo AppenderRef refFileAppender/ /Root /Loggers /Configuration在LoginServlet.java顶部加import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; private static final Logger logger LogManager.getLogger(LoginServlet.class);把System.out.println(...)全换成logger.info(Login success for empId{}, empId); logger.warn(Login failed for username{}, username);效果日志自动写入Tomcat/bin/logs/attendance.log格式为14:22:05.123 [http-nio-8080-exec-1] INFO c.a.c.LoginServlet - Login success for empId1001。运维查问题时直接grep Login failed logs/attendance.log就能定位失败账号。我带过的实习生第一个月都在改这个项目有人给考勤统计加了Excel导出用Apache POI有人把JSP页面用jQuery AJAX改成无刷新打卡还有人把MySQL换成了H2内存数据库做单元测试。他们没写一行新框架代码但已经摸清了JavaWeb的毛细血管——从HTTP请求头怎么传参到MyBatis的#{}和$区别再到Tomcat类加载器的双亲委派。这个.zip不是终点而是你亲手拧开的第一颗螺丝。希望帮到你。本文还有配套的精品资源点击获取