
考勤管理系统在Java课程设计和毕业设计里几乎是永不过时的选题。每年都能看到大量java_ssm员工考勤管理系统相关的需求但这个题目其实可以做得很有深度。我手上这套基于SSMSpring SpringMVC MyBatis的员工考勤管理系统完整跑通了从数据库设计、框架整合到功能实现和IDEA部署的全流程这里把整个项目的设计思路、核心代码、配置细节和踩坑记录一次讲清楚给正在做类似项目的同学一个可以直接参考的完整范本。这个系统适合两类人一是需要完成Java课程设计、毕业设计的学生想找一个功能完善、逻辑清晰、能答辩讲得明白的项目二是刚学完SSM框架、想通过一个完整项目把三大框架串起来练手的开发者。我会尽量把每一步的为什么也讲清楚而不是单纯丢一堆代码让你抄。1. 为什么考勤系统是SSM项目的最佳练手选题很多人做课程设计时喜欢选管理系统但容易陷入CRUD凑页面的尴尬。考勤系统最不一样的地方在于它的业务规则非常明确——上下班打卡、迟到早退判断、请假审批、考勤统计这些规则逼着你必须去思考状态怎么管理、时间怎么计算、统计怎么汇总而不是简单地往数据库里插数据。1.1 这个系统的业务价值在哪从企业实际场景看考勤系统要解决的是三个核心问题记录员工每天几点来、几点走必须有据可查。规则什么算迟到、什么算早退、请假怎么批这些规则要固化成程序逻辑不能靠人肉判断。统计月底HR要算全勤、扣款、加班补贴统计结果必须准确且可导出。这三个问题恰好对应了SSM框架各层要解决的典型问题页面交互和数据展示交给SpringMVC业务规则和事务控制交给Service层和Spring数据存取交给MyBatis。一个项目学完等于把SSM三个框架都用在了刀刃上。1.2 为什么选SSM而不是Spring Boot说实话现在Spring Boot早就成了主流但课程设计和毕业设计导师往往更认可SSM。原因很实际SSM整合过程中需要手写大量配置文件web.xml、spring.xml、spring-mvc.xml、MyBatis配置这个过程能体现你对框架底层原理的理解而Spring Boot的自动配置把所有环节都藏起来了答辩时反而没什么可讲的。另外SSM项目的分层非常清晰写出来的代码结构天然适合展开讲解。比如我自己在做功能扩展时要加一个加班申请模块从Controller到Mapper每个层动哪些文件一目了然。对于需要答辩的课程设计来说这种结构本身就是加分项。1.3 开发环境与版本选择这套系统的开发环境和版本如下虽然是两年前的组合但胜在稳定、资料多、遇到问题随便一搜就有答案JDK 1.8不要用太高版本SSM经典组合对JDK 8最友好Maven 3.6Tomcat 8.5适配JDK 8部署配置简单MySQL 5.7如果你本机是MySQL 8.0也行但要注意驱动和连接参数后文有坑IDEA 2020.3或更新版本社区版也够用但做Web项目建议用Ultimate版依赖管理Maven统一在pom.xml里维护这里特别提醒一下MySQL版本差异是第一个容易踩的坑。5.7和8.0的驱动类名不同8.0需要额外配置时区很多同学项目明明代码没问题却卡在数据库连不上这一步多半是连接串写错了。2. 功能模块划分从需求到代码的分解过程拿到需求先别急着写代码把功能模块画清楚哪怕只是用Word列个清单后面开发会快很多。这套系统的功能围绕两种角色展开管理员也可以理解成HR或部门主管和普通员工登录后进入完全不同的功能界面。2.1 员工端功能清单员工是这个系统最高频的使用者每天都要打开来打卡。员工端的功能我整理成一张表功能说明核心难点登录注册员工工号密码登录新员工可注册密码加密存储上下班打卡点击按钮记录打卡时间迟到/早退判断查看考勤记录按日期查看自己的打卡历史日期范围查询请假申请提交请假单填写类型、时间、事由状态流转设计查看请假记录查看自己所有请假记录及审批状态状态筛选个人信息维护修改手机号、邮箱、密码数据校验2.2 管理员端功能清单管理员的职责比员工复杂一个量级既要管人又要管数据部门管理部门增删改查删除前要检查该部门下是否有员工。员工管理对员工信息的增删改查重置密码离职状态标记。考勤记录管理按部门、日期范围、姓名筛选考勤记录补卡审批。考勤统计按月/日汇总部门出勤、迟到、早退、缺勤人数。请假审批审核员工提交的请假申请批准或驳回。公告管理发布放假通知、作息时间调整等公告。2.3 业务流程中的关键设计考勤状态怎么定义打卡记录的核心是状态字段的设计这直接关系到SQL难度和统计逻辑。我把每次打卡拆成两条记录早上一次、晚上一次每条记录包含时间和结果状态。状态我用字典常量管理上班打卡状态正常NORMAL、迟到LATE、缺卡MISSING下班打卡状态正常NORMAL、早退EARLY、缺卡MISSING这里有个容易被忽略的设计点缺卡和未打卡不能混为一谈。缺卡是当天没有打上班卡或下班卡而未打卡是当天完全没有任何记录。统计时这两种情况算法完全不同缺卡要单独标记出来不能当成旷工处理。3. 数据库表结构设计这个系统的地基SSM项目的数据库设计直接决定后面MyBatis写SQL的难度。表设计得烂后面每个查询都要写一堆绕来绕去的关联设计得好统计功能就是几条简单SQL的事。这套系统一共设计了6张核心表每一张都仔细斟酌过。3.1 员工表employee员工表是系统的用户表用于登录认证和基础信息展示CREATE TABLE employee ( id INT NOT NULL AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL COMMENT 工号登录账号, password VARCHAR(64) NOT NULL COMMENT 密码MD5加密, emp_name VARCHAR(30) NOT NULL COMMENT 姓名, gender CHAR(1) DEFAULT 男 COMMENT 性别, department_id INT DEFAULT NULL COMMENT 所属部门ID, position VARCHAR(50) DEFAULT NULL COMMENT 职位, phone VARCHAR(20) DEFAULT NULL, email VARCHAR(50) DEFAULT NULL, entry_date DATE DEFAULT NULL COMMENT 入职日期, status INT DEFAULT 1 COMMENT 1在职 0离职, PRIMARY KEY (id), UNIQUE KEY uk_emp_no (emp_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;工号emp_no务必加唯一索引。员工离职时做逻辑删除status置0而不是物理删除这样历史考勤数据才能保留。3.2 部门表departmentCREATE TABLE department ( id INT NOT NULL AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL COMMENT 部门名称, manager_id INT DEFAULT NULL COMMENT 负责人员工ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3.3 考勤记录表attendance这是整个系统最核心的表需要同时支持按日查询、按人统计、按部门汇总所以字段尽量单独拆开CREATE TABLE attendance ( id INT NOT NULL AUTO_INCREMENT, emp_id INT NOT NULL COMMENT 员工ID, att_date DATE NOT NULL COMMENT 打卡日期, check_in_time DATETIME DEFAULT NULL COMMENT 上班打卡时间, check_out_time DATETIME DEFAULT NULL COMMENT 下班打卡时间, in_status VARCHAR(20) DEFAULT MISSING COMMENT 上班状态, out_status VARCHAR(20) DEFAULT MISSING COMMENT 下班状态, late_minutes INT DEFAULT 0 COMMENT 迟到分钟数, early_minutes INT DEFAULT 0 COMMENT 早退分钟数, work_type VARCHAR(10) DEFAULT NORMAL COMMENT 工作日/休息日/节假日, PRIMARY KEY (id), KEY idx_emp_id_date (emp_id, att_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意联合索引(emp_id, att_date)。考勤查询最频繁的场景就是查某个人某段时间的记录这个联合索引能让查询速度提升非常明显。3.4 请假表leave_requestCREATE TABLE leave_request ( id INT NOT NULL AUTO_INCREMENT, emp_id INT NOT NULL, leave_type VARCHAR(20) NOT NULL COMMENT 事假/病假/年假/调休, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, reason VARCHAR(500) DEFAULT NULL, status INT DEFAULT 0 COMMENT 0待审批 1已批准 2已驳回, apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, approve_time DATETIME DEFAULT NULL, PRIMARY KEY (id), KEY idx_emp_id (emp_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3.5 管理员表和公告表管理员独立建表admin_user字段就是id、登录名、密码、姓名。注意不要把管理员和员工混在一张用户表里因为课程设计答辩时导师很容易问这两种角色的权限边界是怎么控制的——分开设计可以在代码层面用不同表查询不同角色讲起来很干净。4. SSM框架整合配置文件里的关键细节很多同学搭SSM时配置都是复制粘贴的出了错根本不知道是哪里的问题。这里我把这套项目里的核心配置逐步拆开讲每个文件负责什么为什么要这样写。4.1 pom.xml依赖版本要统一SSM项目最容易出现的异常是ClassNotFoundException或AbstractMethodError多半是依赖版本冲突。我建议直接固定一套经过验证的版本组合properties spring.version5.3.9/spring.version mybatis.version3.5.9/mybatis.version /properties !-- 核心依赖spring-context, spring-webmvc, spring-jdbc, mybatis, mybatis-spring -- !-- mybatis-spring版本必须和Spring版本匹配这里用2.0.6 -- !-- 数据库mysql-connector-java 8.0.29对应MySQL 8.0 -- !-- 连接池druid 1.2.8 -- !-- JSON工具jackson-databind 2.12.4 -- !-- 分页插件pagehelper 5.3.0 -- !-- 日志slf4j-api logback-classic --版本匹配规则是Spring 5.x配mybatis-spring 2.x这个不能乱配。Spring 4.x配mybatis-spring 1.x如果配错会在启动时报NoSuchBeanDefinitionException但日志又不明显排查起来非常头疼。4.2 web.xml监听器、分发器、乱码过滤器SSM是Java Web项目第一步配置web.xml。核心三件事启动Spring容器、启动SpringMVC分发器、统一编码。!-- 启动Spring容器 -- listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-context.xml/param-value /context-param !-- SpringMVC 前端控制器 -- servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping !-- 乱码过滤器 -- filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping乱码过滤器一定要放在所有过滤器的第一个位置。这个顺序有时候书上不讲但实际部署时如果后面再加了登录拦截器过滤顺序错了就会出现中文参数到Controller变成的情况。4.3 spring-context.xml数据源、事务、扫描这个文件管理的是Spring核心容器包括DataSource、事务管理器、Service层扫描等!-- 读取数据库配置文件 -- context:property-placeholder locationclasspath:jdbc.properties/ !-- 数据源 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean !-- SqlSessionFactory -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nametypeAliasesPackage valuecom.example.attendance.entity/ property namemapperLocations valueclasspath:mapper/*.xml/ !-- 这个配置非常重要下节详细说 -- property nameconfiguration bean classorg.apache.ibatis.session.Configuration property namemapUnderscoreToCamelCase valuetrue/ /bean /property /bean !-- 事务管理器 -- bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/ !-- Service层扫描 -- context:component-scan base-packagecom.example.attendance.service/4.4 一个能救命的配置mapUnderscoreToCamelCase数据库字段是emp_no、check_in_time这种下划线风格Java实体类是empNo、checkInTime如果不开mapUnderscoreToCamelCaseMyBatis查询结果根本映射不上所有字段都是null。这个配置必须在SqlSessionFactory里设置很多教程漏掉这一步导致很多人以为MyBatis的resultType映射有问题其实只是驼峰映射没打开。4.5 spring-mvc.xml注解驱动、视图解析器、静态资源Controller层配置相对简单但有几个容易忽略的点context:component-scan base-packagecom.example.attendance.controller/mvc:annotation-driven/mvc:default-servlet-handler/这里mvc:default-servlet-handler特别重要。前端页面引用了CSS、JS、图片等静态资源如果不加这一行所有静态资源请求都会被DispatcherServlet拦截页面样式全丢。加了之后Spring会把静态资源请求交还给容器默认的Servlet处理。5. 核心功能代码实现登录、打卡、统计配置搭好之后就进入正题。我把这套系统里最有代表性的四个功能拿出来完整讲一遍代码实现思路。5.1 登录和角色识别登录功能本身不难关键点是登录成功后怎么区分员工和管理员。我的做法是登录时查两张表哪张表有记录就按哪个角色处理然后在Session里存一个loginType字段// 登录时判断角色 Employee emp employeeService.login(empNo, password); if (emp ! null) { session.setAttribute(loginUser, emp); session.setAttribute(loginType, EMPLOYEE); return redirect:/employee/index; } Admin admin adminService.login(adminNo, password); if (admin ! null) { session.setAttribute(loginUser, admin); session.setAttribute(loginType, ADMIN); return redirect:/admin/index; }拦截器里校验Session的loginType就能控制页面访问权限。这里密码建议用MD5加盐虽然MD5不算安全但课程设计完全够用答辩时还能讲出为了安全我做了MD5加密这个点。5.2 打卡逻辑状态判断和并发安全打卡是考勤系统的灵魂。员工点一下上班打卡按钮后端大致要做这样几件事判断今天是否已经打上班卡同一人同一天只能有一条记录。获取当前系统时间和规定的上班时间比较。判断状态是正常还是迟到计算迟到分钟数。写入或更新考勤记录。核心代码逻辑// 上班打卡 public boolean checkIn(Employee emp) { Date now new Date(); Date today DateUtils.truncate(now, Calendar.DATE); Attendance record attendanceMapper.selectByEmpAndDate(emp.getId(), today); if (record null) { // 首次打卡创建记录 record new Attendance(); record.setEmpId(emp.getId()); record.setAttDate(today); record.setCheckInTime(now); // 9:00后算迟到 if (now.after(workStartTime)) { record.setInStatus(LATE); record.setLateMinutes((now.getTime() - workStartTime.getTime()) / 60000); } else { record.setInStatus(NORMAL); } attendanceMapper.insert(record); } else if (record.getCheckOutTime() null) { // 上班卡已经打过打下班卡 // 18:00前算早退 } else { // 一天已经打过两次卡 return false; } return true; }这里有几个细节值得留意。同一人一天只能有一条记录的约束要在Service层提前判断不要只依赖数据库否则并发点两个按钮就会出现两条打卡记录。另外上班时间和下班时间建议放在配置表或常量类里不要硬编码在业务代码中后面改夏令时或调整作息只需要改一处配置。5.3 考勤统计一条SQL还是多条SQL考勤统计通常是答辩时最容易被问到的点。很多人的实现方式是写循环遍历员工每个员工查一次考勤表再汇总——这在小数据量下没问题但逻辑完全经不起推敲。我采用的是分组统计SQL比如统计每天每条记录的打卡情况SELECT att_date, COUNT(CASE WHEN in_status NORMAL THEN 1 END) AS normal_count, COUNT(CASE WHEN in_status LATE THEN 1 END) AS late_count, COUNT(CASE WHEN in_status MISSING THEN 1 END) AS missing_count FROM attendance GROUP BY att_date ORDER BY att_date DESC如果是按月汇总每个员工的出勤情况就按员工分组SELECT emp_id, COUNT(CASE WHEN in_status NORMAL THEN 1 END) AS normal_days, COUNT(CASE WHEN in_status LATE THEN 1 END) AS late_days, COUNT(CASE WHEN out_status EARLY THEN 1 END) AS early_days FROM attendance WHERE att_date BETWEEN #{startDate} AND #{endDate} GROUP BY emp_id这种写法只需要一条SQL、一次数据库查询MyBatis映射到List后前端用表格展示即可。而且这种SQL在答辩时讲起来非常有说服力——我用CASE WHEN配合GROUP BY对考勤状态做了聚合统计避免了循环查询数据库的性能问题。5.4 请假审批状态机和操作权限请假模块的核心是状态机。状态只有三个待审批、已批准、已驳回。我严格控制了状态流转的规则员工提交请假状态从无到待审批此时可以撤销。管理员审批通过状态从待审批到已批准。管理员审批驳回状态从待审批到已驳回。已批准和已驳回不允许再做任何操作不能重复审批。这个规则在Service层用if判断控制而不是在Controller层写业务逻辑。举个例子管理员驳回时要注意员工重新提交的请假单会生成一条新的记录不会复用旧记录避免审批历史出现歧义。这个点在答辩时也挺加分的。6. IDEA导入与Tomcat部署最容易翻车的环节代码写得再好部署不成功也白搭。SSM项目的部署有几个高频踩坑点我一个个说都是我实际趟过的。6.1 IDEA导入Maven项目的正确姿势拿到源码后无论从网上下载的还是自己写的IDEA导入步骤是File → New → Project from Existing Sources。选择项目根目录下的pom.xml。选择Import as Maven Project。等待Maven下载依赖第一次大概5-10分钟取决于网络速度。这里最容易出的问题Maven下载慢或下载失败。解决方案是修改Maven镜像源为阿里云公共仓库mirror idaliyun/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror6.2 配置Tomcat时常见的问题配置Run → Edit Configurations → Tomcat Server → Local在Deployment标签页里把项目以war exploded方式添加这样改代码后重启更快也方便热部署。Application context建议设为/attendance不要用根路径后面访问就是http://localhost:8080/attendance。部署环节最常见的三个错误404问题多半是Artifact没有添加成功或Application context配错了。打开http://localhost:8080确认Tomcat本身能访问再看项目路径。ClassNotFoundExceptionorg.springframework.web.context.ContextLoaderListener说明Tomcat运行时没有加载到项目的Maven依赖。右键项目 →Open Module Settings→Artifacts把lib目录里的依赖包加进来。这个操作在IDEA 2020之后被很多人忽略段位高一点的工程师建议养成每次导入项目后检查Artifacts的习惯。数据库连接失败报Access denied for user rootlocalhost时检查jdbc.properties里的用户名、密码尤其注意MySQL 8.0默认的认证插件是caching_sha2_password而低版本连接驱动不支持这个认证方式需要改URL和驱动。6.3 MySQL 8.0的连接配置坑如果是MySQL 8.0jdbc.properties要这样写jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/attendance_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 jdbc.usernameroot jdbc.password你的密码注意驱动类名是com.mysql.cj.jdbc.Driver不再是com.mysql.jdbc.Driver后者在8.0下会报Driver has not been revoked这类错误。serverTimezone必须写否则报一堆时区异常。characterEncoding统一用utf8mb4能覆盖全部中文和表情符号。6.4 页面静态资源丢失和AJAX乱码部署成功后打开页面样式全丢九成是spring-mvc.xml里没加mvc:default-servlet-handler/。另外JSP页面引用CSS时路径要带上${pageContext.request.contextPath}不然换一台机器部署后路径就写死了。AJAX请求返回中文乱码除了web.xml里配了CharacterEncodingFilter之外还要在spring-mvc.xml里配置消息转换器强制使用UTF-8。实际上我们Controller返回的往往是JSON乱码的根源在于Response的Content-Type里没有charset用RequestMapping(produces application/json;charsetUTF-8)可以逐个接口解决生产级做法则是统一配置StringHttpMessageConverter的编码。7. 给答辩加分的扩展点与优化方向核心功能做完之后这套系统已经算是一个完整的SSM课程设计项目。但如果你想在答辩时拿更高分或者上线给真实小团队用下面这几个方向非常值得投入精力。它们的技术难度和现有系统架构完全兼容不会伤筋动骨。7.1 让考勤数据看得见接入ECharts统计图表考勤统计列表做得再漂亮也不如一个图表直观。EChartsApache ECharts可以通过CDN方式引入和后端完全解耦不需要改任何Java代码。你只需要增加一个接口返回统计数据ResponseBody RequestMapping(/admin/chartData) public MapString, Object chartData(String month) { ListMapString, Object data attendanceService.getMonthlySummary(month); MapString, Object result new HashMap(); result.put(code, 0); result.put(data, data); return result; }前端通过jQuery的$.ajax拿到数据后用ECharts画一个部门出勤对比的柱状图和一个迟到原因的饼图。答辩时打开页面数据一张图表直接呈现出来导师的观感立刻不一样。这个改动的投入产出比极高强烈建议做。7.2 定时任务每天自动锁卡并生成缺勤标记打卡时间不是实时判断的吗为什么还需要定时任务因为真实场景是忘了打卡员工上班忘了点按钮到下午才想起来上班卡就是空的。如果只靠实时判断这条记录就永远缺了上班卡。合理的设计是写一个定时任务每天凌晨自动扫描前一天的考勤记录把打卡状态为空的记录标记为缺卡。SSM里用Spring Task就能实现在spring-context.xml里开启注解定时task:annotation-driven schedulertaskScheduler/ task:scheduler idtaskScheduler pool-size5/然后写一个定时Job方法Component public class AttendanceCheckTask { Scheduled(cron 0 30 1 * * ?) public void autoCheckMissingCards() { // 找到昨天还没有打卡状态的记录 // 把 in_status 或 out_status 为 NULL 的记录补成 MISSING } }这个功能在答辩时是实打实的业务亮点一举说明你考虑到了真实考勤场景中的边界情况而不只是做一个能打卡的demo。7.3 对接企业微信或钉钉的提醒功能如果使用了别人封装好的SDK接一个考勤异常通知功能是可行的当员工打卡成功或出现迟到记录时通过Webhook机器人推送到企业微信群或钉钉群。技术本质就是发一个HTTP POST请求带着封装好的JSON消息体。这个功能适合作为项目展示时的锦上添花能明显体现出你的架构思维——考勤系统不只是企业内部页面还可以和办公协同工具联动。7.4 安全层面还能补什么课程设计阶段大家普遍不重视安全但这是一个可以在答辩时展示思考的维度密码加密至少用MD5加盐讲清楚MD5不是加密算法而是哈希算法以及它为什么不安全、为什么后续要考虑BCrypt。SQL注入防护MyBatis的#{}预编译机制本身就防注入但如果你在XML里用${}拼接字段比如动态排序列名就一定要做白名单校验。Session过期处理在拦截器里统一校验Session是否过期过期后跳转登录页并提示重新登录。统一异常处理用ControllerAdvice和ExceptionHandler做全局异常拦截不让500错误页面裸奔。这些在代码里改动成本极低但答辩时每一句都经得起追问。8. 项目运行自查清单与常见异常表最后给你一份自查清单和异常对照表不管你是要验收代码还是要调试这套系统按这个顺序检查能省下很多排查时间。先是一份启动自查清单确认数据库已创建编码为utf8mb4SQL脚本执行成功表名、字段名和代码里一致。确认jdbc.properties用户名密码正确URL符合本机MySQL版本。确认IDEA里Maven配置使用本地仓库且依赖无红色报错。确认Artifacts里已包含所有依赖lib。确认启动前没有其他进程占用8080端口。启动后看IDEA的Run窗口有没有Initializing Spring root WebApplicationContext以及Tomcat started日志。浏览器访问http://localhost:8080/attendance先看登录页再逐条测试打卡、统计、审批。常见异常对照表现象可能原因处理方式项目启动报404Artifacts没配好 / context path不对重新配置Tomcat DeploymentTomcat启动成功但访问空白Spring容器初始化失败日志没看清查看catalina日志看有没有Bean创建失败的堆栈页面中文全是??数据库连接URL少了characterEncodingjdbc.url加上characterEncodingutf8mb4数据库查出来字段全是nullmapUnderscoreToCamelCase没开启在SqlSessionFactory里开启驼峰映射提示Column emp_no not foundMyBatis的SQL里没写正确的列名检查mapper XML中的SQL和表结构是否一致报Access denied for user数据库用户名密码错误核对jdbc.properties并确认MySQL服务正常登录成功但页面没有样式缺少default-servlet-handlerspring-mvc.xml加mvc:default-servlet-handler页面跳转时报500大概率是Controller里返回的JSP路径不对检查Controller return的字符串和WEB-INF/jsp下的文件是否匹配9. 最后再说点个人经验做完这个项目我最大的一个体会是**SSM框架项目的复杂度控制关键在于把业务规则想清楚后再动手。**如果一上来就写代码很容易把考勤逻辑写成一团乱麻——按钮点了就插一条记录统计就是循环查表最后代码堆了三层自己都讲不清楚。如果你正在做类似的课程设计或毕业设计我建议按这套思路来推进先把考勤状态定义清楚迟到、早退、缺卡、请假再把一个员工从入职到离职会经历的所有考勤动作列一遍最后才是建表、搭框架、写代码。这套系统里我用红色作为页面主色调其他视觉风格你也可以按自己喜好替换跟核心逻辑完全无关。希望这篇拆解能让你少走一些弯路如果你在搭SSM环境、部署Tomcat时卡住了回头再多看几遍第6章的内容——那些坑基本都是每个做SSM项目的人都会遇到的。