当前位置: 首页 > news >正文

SSM框架实现的员工考勤管理系统(含MySQL建库脚本与部署指南)

本文还有配套的精品资源,点击获取

简介:基于Spring+SpringMVC+MyBatis(SSM)搭建的企业级考勤管理项目,适配JDK 8、Eclipse开发环境和Tomcat 8/9,数据库使用MySQL。压缩包内含完整可运行源码(ems.zip)、一键执行的建表与初始数据脚本(ems.sql),以及详细部署步骤和运行说明(项目说明.txt)。系统采用双角色权限设计:管理员拥有全部模块操作权限,员工仅能查看和维护本人相关信息。功能模块包括部门管理(组织架构维护)、职位管理(岗位类型配置)、员工档案管理(基础信息增删改查)、考勤记录(打卡、请假、加班、出差等状态录入与统计)、薪资计算(自动关联岗位与考勤生成工资条)、意见提交(员工反馈通道+后台审核归档)、个人信息管理(头像、联系方式、密码等自主更新)。所有模块均通过本地实测,无缺失依赖、无编译报错,支持直接导入Eclipse运行,适合Java初学者练手、课程设计或本科毕业设计快速落地。

1. 项目概述:为什么这个SSM考勤系统值得你花两小时认真读完

我带过六届Java方向的毕业设计,每年都会收到至少四十份“基于SSM的XX管理系统”——其中八成在导入Eclipse后卡在ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet,三成倒在MySQL连接失败,剩下两成能跑起来,但点开员工列表就报空指针。真正能从解压到登录后台、完成一次完整打卡流程、再导出一份考勤统计表,全程不查百度不改三行以上代码的项目,五年下来不超过五套。眼前这套“SSM考勤系统”,就是那五套里最稳、最干净、最适合新手上手的一套。

它不是PPT里的架构图,也不是GitHub上挂着“TODO”标签的半成品。它是一套被真实部署进三家小型制造企业行政部、用作临时考勤替代方案的轻量级系统——当然,做了脱敏和简化,但所有模块的交互逻辑、权限校验粒度、数据库字段设计,都保留了生产环境该有的严谨性。关键词里写的“SSM考勤系统”“Java毕设源码”“MySQL建库脚本”,每一个都不是虚词:ems.sql脚本执行后,你立刻得到7张结构清晰、外键约束完整、中文注释到位的数据表;ems.zip解压进Eclipse,右键Run As → Run on Server,选中你本地的Tomcat,三分钟内就能看到登录页;而项目说明.txt里写的每一步操作,我都亲手在Windows 10 + JDK 8u291 + Eclipse 2021-06 + Tomcat 9.0.56 + MySQL 8.0.33环境下逐字验证过。

它解决的不是“能不能跑”的问题,而是“为什么这么跑”的问题。比如,为什么部门表里有个dept_order字段却没在页面暴露编辑入口?因为这是为后续组织架构树形展示预留的排序位,现在不用,但字段必须存在,否则扩展时要改表结构——这种细节,文档不会写,但代码里藏着。再比如,考勤记录里请假类型用的是VARCHAR(20)存“事假/病假/年假”,而不是用TINYINT存数字编码,初看是冗余,实则是为后期对接OA系统做语义兼容,避免前端硬编码映射。这些经验,不是教科书教的,是我在给客户修第三遍“请假审批流不触发邮件通知”时,把日志翻烂才抠出来的。

如果你正卡在毕设开题答辩前的代码焦虑里,或者刚学完MyBatis动态SQL还分不清<if><choose>该用哪个,又或者想用一个真实项目反向吃透Spring MVC的拦截器链怎么织入权限控制——那么别急着去搜“SSM整合教程”,先把这包源码解压,按本文步骤走一遍。接下来的内容,我会带你一层层剥开它的骨架:不是告诉你“这里写个Controller”,而是解释“为什么这个Controller要继承BaseController”;不是只贴ems.sql内容,而是带你算清楚每张表的主键策略怎么避开MySQL 8.0的严格模式报错;不是罗列“管理员能干啥、员工能干啥”,而是用一张权限路由表,说清URL路径和角色权限之间那根看不见的线是怎么绷紧的。它不炫技,但每一步都踩在Java Web开发的真实地面上。

2. 整体架构与设计思路拆解:SSM不是拼凑,而是齿轮咬合

2.1 为什么选SSM而非Spring Boot?——教学场景下的理性选择

现在提Java Web,第一反应是Spring Boot。但当你打开pom.xml,会发现它用的是spring-webmvc 4.3.29.RELEASEmybatis 3.4.6,连spring-boot-starter-web的影子都没有。这不是技术陈旧,而是精准匹配教学场景的主动取舍。

Spring Boot的自动配置像一把瑞士军刀,开箱即用,但刀刃太钝——学生知道@SpringBootApplication一加,项目就起来了,却不知道DispatcherServlet是谁注册的、ViewResolver怎么解析JSP路径、DataSource怎么被注入进SqlSessionFactory。而SSM组合,每个组件职责单一、边界清晰:Spring负责IoC容器和事务管理,Spring MVC专注HTTP请求调度,MyBatis死磕SQL映射。就像教人骑自行车,先让你分别练蹬踏、握把、刹车,再合成骑行,比直接给你一辆电动助力车更能建立肌肉记忆。

更关键的是依赖可控性。pom.xml里没有<scope>provided</scope>之外的战争式依赖冲突。我试过把这份pom.xml直接粘贴进一个空白Maven项目,mvn clean compile一次通过。而很多Spring Boot项目,光是解决spring-boot-starter-thymeleafspring-boot-starter-freemarker的模板引擎冲突,就能耗掉新手半天。SSM的XML配置虽然多几行,但每一行都在眼皮底下:applicationContext.xml<context:component-scan>扫哪些包,spring-mvc.xml<mvc:annotation-driven>开了哪些特性,mybatis-config.xml<typeAliases>怎么简化Mapper接口路径——全是可调试、可打断点、可逐行理解的实体代码。

提示:如果你已熟悉Spring Boot,想把本项目升级过去,核心改造点只有三处:① 将web.xml中的ContextLoaderListenerDispatcherServlet声明移入@SpringBootApplication类;② 把applicationContext.xml中的数据源、事务、Service扫描逻辑,用@Configuration类+@Bean方法重写;③mybatis-config.xml的配置项,通过MybatisAutoConfigurationconfigurationProperties注入。但对毕设而言,原样运行更稳妥。

2.2 双角色权限模型:不是RBAC,而是URL级硬隔离

系统文档说“双角色权限”,但翻代码你会发现,它没用Shiro或Spring Security,甚至没建rolepermission表。权限控制藏在两个地方:前端菜单渲染和后端拦截器。

先看前端。WebRoot/js/common.js里有个initMenu()函数,它根据sessionStorage.getItem('userRole')的值(”admin”或”employee”),动态拼接左侧导航HTML。管理员看到全部七个菜单项,员工只看到“考勤记录”“薪资查询”“意见提交”“个人信息”这四项。这很朴素,但有效——用户根本看不到不该点的按钮,从源头杜绝越权操作。

后端更实在。src/com/ems/interceptor/AuthInterceptor.java是真正的守门人。它实现HandlerInterceptor接口,在preHandle()方法里做三件事:
1. 检查当前请求URL是否以/admin/开头(如/admin/dept/list);
2. 若是,再检查session.getAttribute("userRole")是否等于"admin"
3. 若不满足,直接response.sendRedirect(request.getContextPath()+"/login.jsp?error=access_denied"),连Controller的边都不让沾。

这种设计牺牲了灵活性(没法细粒度到“员工只能查看自己部门考勤”),但换来了零配置、零学习成本的绝对安全。对于毕设,你不需要解释“为什么不用RBAC”,只需要在答辩时指着AuthInterceptor.java第23行说:“这里用URL前缀和Session角色做硬匹配,确保非管理员无法访问任何以/admin/开头的资源,经压力测试,万级并发下拦截准确率100%。”——老师只会点头,不会追问ACL策略。

注意:AuthInterceptor的注册在spring-mvc.xml里,<mvc:interceptors>标签下。千万别漏掉这行配置,否则权限形同虚设。我见过三个学生因为复制配置时删掉了<mvc:interceptors>的闭合标签,导致整个系统裸奔。

2.3 数据库设计哲学:宁可冗余,不可断裂

打开ems.sql,你会惊讶于它的“啰嗦”。比如员工表emp_info,除了dept_id外,还存了dept_name;考勤表att_record里,除了emp_id,还冗余了emp_namepost_name。老司机一看就皱眉:“这不符合第三范式啊!”但这就是教学项目的智慧。

想象一个场景:学生要在EmpController.java里写一个listByDeptId()方法,返回员工列表并带上部门名称。如果emp_info表里只有dept_id,他就得在Mapper XML里写<resultMap>关联查询dept_info表,还得处理dept_name为空的NPE风险。而本项目,直接SELECT emp_id, emp_name, dept_name FROM emp_info WHERE dept_id = #{deptId},一行SQL搞定,结果集直接映射到EmpInfo实体类,连@Results注解都不用加。

再看薪资计算。salary_info表里有base_salary(基本工资)、bonus(绩效奖金)、deduction(扣款),但没存total_salary(应发工资)。计算逻辑放在Service层的SalaryService.calculateTotal()方法里:total = base + bonus - deduction。这样设计,一是避免数据不一致(万一base_salary改了,total_salary忘了更新),二是让学生必须动手写业务逻辑,而不是当个SQL搬运工。

所有冗余字段都有明确注释,比如emp_info.dept_name的注释是“冗余部门名称,用于列表展示免关联查询”。这不是偷懒,是把数据库设计的权衡过程,明明白白摊在学生面前——范式是教条,可用性才是王道。

3. 核心模块与实操要点:从建库到登录的全流程拆解

3.1 MySQL建库脚本深度解析:不只是执行,更要读懂设计意图

ems.sql不是一堆CREATE TABLE的堆砌,它是一份带着注释的数据库设计说明书。我们逐段拆解:

-- 创建ems数据库,字符集显式指定为utf8mb4,兼容emoji和生僻字 CREATE DATABASE IF NOT EXISTS ems CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE ems; -- 部门表:dept_order字段为后续树形排序预留,status字段支持软删除 DROP TABLE IF EXISTS dept_info; CREATE TABLE dept_info ( dept_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID', dept_name VARCHAR(50) NOT NULL COMMENT '部门名称', dept_order INT DEFAULT 0 COMMENT '部门排序序号,数值越小越靠前', status TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-禁用(软删除)', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门信息表'; -- 员工表:关键点在于password字段长度设为64,为未来SHA256加密留足空间 DROP TABLE IF EXISTS emp_info; CREATE TABLE emp_info ( emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID', emp_code VARCHAR(20) UNIQUE NOT NULL COMMENT '员工工号,唯一标识', emp_name VARCHAR(30) NOT NULL COMMENT '员工姓名', gender TINYINT DEFAULT 1 COMMENT '性别:1-男,2-女', dept_id INT NOT NULL COMMENT '所属部门ID', dept_name VARCHAR(50) COMMENT '冗余部门名称,免关联查询', post_id INT NOT NULL COMMENT '职位ID', post_name VARCHAR(30) COMMENT '冗余职位名称', phone VARCHAR(20) COMMENT '手机号', email VARCHAR(50) COMMENT '邮箱', password VARCHAR(64) NOT NULL COMMENT '密码(SHA256加密后存储)', avatar VARCHAR(200) COMMENT '头像路径', status TINYINT DEFAULT 1 COMMENT '状态:1-在职,0-离职', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';

关键参数计算与避坑点:

  • password VARCHAR(64)的由来:项目用的是DigestUtils.sha256Hex(),SHA256哈希值是64位十六进制字符串(32字节×2)。如果误设为VARCHAR(32),插入时会被截断,导致永远无法登录。我曾帮一个学生debug,他把密码字段改成VARCHAR(32),结果所有用户密码都失效,重置后还是登不进去,最后发现是字段长度不够。

  • dept_order INT DEFAULT 0的妙用:虽然当前页面没提供排序拖拽功能,但DeptController.list()方法里,SQL是SELECT * FROM dept_info ORDER BY dept_order ASC, create_time DESC。这意味着,只要你在ems.sql的初始化数据里,把销售部的dept_order设为10,技术部设为5,列表里技术部就永远排在销售部前面。未来加个简单的下拉框修改dept_order,排序功能就出来了,不用动SQL。

  • status TINYINT DEFAULT 1的统一性:所有主业务表(dept_info,emp_info,att_record,salary_info)都用了这个字段,且约定1=启用/在职/正常0=禁用/离职/异常。这保证了Service层可以写通用的enableById()disableById()方法,用反射获取实体类的status字段名,避免每个表都写一套开关逻辑。

执行脚本的正确姿势:

  1. 启动MySQL服务(确保是8.0+版本,5.7可能因CURRENT_TIMESTAMP精度报错);
  2. 在MySQL命令行或Navicat中,不要直接右键执行ems.sql文件;
  3. 先手动执行CREATE DATABASE IF NOT EXISTS ems CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  4. 再执行USE ems;
  5. 最后,将ems.sql文件内容全选复制,粘贴执行。

为什么不能一键执行?因为ems.sql开头没有CREATE DATABASE语句,它假设你已经建好库。很多学生跳过这步,直接执行,结果报错Unknown database 'ems',然后慌乱中在MySQL里建了个ems库但没设字符集,导致中文存成问号。记住:字符集必须是utf8mb4,不是utf8(MySQL的utf8实际是utf8mb3,不支持4字节Unicode)。

3.2 Eclipse环境配置:绕过最常见的三个编译地狱

ems.zip解压后,目录结构是标准的Dynamic Web Project。但在Eclipse里导入,常卡在三个地方:

地狱一:JDK版本不匹配
- 项目.settings/org.eclipse.jdt.core.prefs里写着org.eclipse.jdt.core.compiler.compliance=1.8,意味着必须用JDK 8。
- 如果你电脑装了JDK 17,Eclipse默认用新版本,编译会报错The type java.lang.Object cannot be resolved
-解法:Window → Preferences → Java → Installed JREs,Add → Standard VM → Next → JRE home选JDK 8安装路径(如C:\Program Files\Java\jdk1.8.0_291)→ Finish。然后在项目上右键 → Properties → Java Build Path → Libraries → Remove掉错误的JRE System Library → Add Library → JRE System Library → Workspace default JRE(即刚配的JDK 8)。

地狱二:Tomcat运行时库缺失
- 错误现象:启动Tomcat时报java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet
- 根本原因:Eclipse没把WEB-INF/lib下的jar包加入部署路径。
-解法:项目右键 → Properties → Deployment Assembly → 点击右侧Add → Java Build Path Entries → Next → 选中Maven Dependencies→ Finish。这一步必须做,否则所有第三方jar都不会被打包进WAR。

地狱三:WebRoot路径识别错误
- 有些Eclipse版本(尤其是2022+)导入时,会把WebRoot当成普通文件夹,不识别为Web Content Root。
-解法:项目右键 → Properties → Project Facets → 勾选Dynamic Web Module→ Version选3.04.0→ Apply → OK。然后再次Properties → Deployment Assembly → 点击Add → Folder → Next → 选中WebRoot文件夹 → Finish。此时WebRoot图标会变成小地球。

做完这三步,右键项目 → Run As → Run on Server → 选中你的Tomcat → Finish。浏览器打开http://localhost:8080/ems,看到登录页,你就成功穿越了编译地狱。

3.3 登录与权限流转:一次登录背后的三次Session写入

登录看似简单,但背后是SSM三层框架的精密协作。我们跟踪一次管理员登录admin/admin123的全过程:

  1. 前端提交login.jsp表单action="/ems/login"method="post",提交usernamepassword
  2. Spring MVC接收LoginController.javalogin()方法被调用,@RequestParam String username@RequestParam String password接收参数;
  3. Service层校验LoginService.checkLogin(username, password)执行,核心是empMapper.findByCode(username)查员工,再用DigestUtils.sha256Hex(password)加密比对;
  4. Session写入:校验成功后,request.getSession().setAttribute("userId", emp.getEmpId())setAttribute("userName", emp.getEmpName())setAttribute("userRole", emp.getRole())——注意,这里emp.getRole()返回的是字符串"admin""employee",不是数据库里的数字编码;
  5. 重定向return "redirect:/admin/index.jsp",跳转到管理员首页。

为什么Session要写三个属性?

  • userId:后续所有操作(查考勤、改密码)都需要员工ID,从Session取比从Cookie取安全;
  • userName:页面顶部显示“欢迎,张三”,避免每次都要查库;
  • userRoleAuthInterceptor靠它做权限判断,这是整个双角色模型的基石。

实操心得:如果登录后跳转到首页,但左上角显示“欢迎,null”,说明userName没写入Session。检查LoginService.checkLogin()方法,确认emp.getEmpName()返回值不为null(数据库初始化数据里,emp_code='admin'的员工emp_name是‘系统管理员’,不是空字符串)。

4. 实操过程与核心环节实现:手把手跑通第一个考勤记录

4.1 从零开始:部署、登录、添加一名员工的完整链路

现在,我们把前面所有知识点串起来,走一遍最基础但最完整的业务闭环:部署系统 → 管理员登录 → 添加一名新员工 → 员工登录 → 打一次上班卡。

Step 1:数据库准备(5分钟)
- 确保MySQL 8.0服务运行;
- 打开命令行,输入mysql -u root -p,输入密码进入MySQL;
- 执行CREATE DATABASE ems CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- 执行USE ems;
- 将ems.sql文件内容全选复制,粘贴执行(注意:不要包含文件开头的-- MySQL Script...注释行);
- 执行SELECT COUNT(*) FROM emp_info;,应返回11(初始化数据含10名员工+1名管理员)。

Step 2:Eclipse导入与配置(10分钟)
- 解压ems.zip到工作区(如D:\workspace\ems);
- Eclipse → File → Import → Existing Projects into Workspace → Browse选中ems文件夹 → Finish;
- 按3.2节修复JDK、Deployment Assembly、WebRoot路径;
- 右键项目 → Run As → Run on Server → 选Tomcat → Finish;
- 浏览器打开http://localhost:8080/ems,出现登录页。

Step 3:管理员登录与添加员工(8分钟)
- 用户名admin,密码admin123,登录;
- 左侧菜单点击“员工管理” → “员工列表”,看到11条记录;
- 点击右上角“添加员工”按钮,弹出表单;
- 填写:工号emp007,姓名李四,性别,部门选“技术部”,职位选“Java开发工程师”,手机号13800138000,邮箱lisi@ems.com,密码123456(系统会自动SHA256加密);
- 点击“保存”,页面提示“添加成功”,列表刷新后出现emp007

Step 4:员工登录与打卡(5分钟)
- 点击右上角“退出”,回到登录页;
- 用户名emp007,密码123456,登录;
- 左侧菜单只剩四项,点击“考勤记录” → “打卡”;
- 页面显示今日日期、当前时间,点击“上班打卡”按钮;
- 弹出提示“打卡成功”,att_record表新增一条记录:emp_id=7att_type=1(1=上班),att_time=2023-10-27 09:00:22

至此,一个最小可行闭环完成。你不仅看到了代码跑起来,更亲手参与了数据从录入、存储、查询到展示的全链路。这比看十篇SSM整合教程都管用。

4.2 考勤模块深度实现:如何用MyBatis动态SQL应对复杂查询

考勤管理是系统最复杂的模块,因为它要支持多条件组合查询:按员工姓名、按部门、按考勤类型(上班/下班/请假/加班/出差)、按日期范围。AttRecordController.list()方法调用attRecordService.listByCondition(condition),而condition是一个AttRecordQuery对象,封装了所有可选查询条件。

核心在AttRecordMapper.xmllistByConditionSQL片段:

<select id="listByCondition" resultType="com.ems.entity.AttRecord"> SELECT ar.*, ei.emp_name, ei.post_name, di.dept_name FROM att_record ar LEFT JOIN emp_info ei ON ar.emp_id = ei.emp_id LEFT JOIN dept_info di ON ei.dept_id = di.dept_id WHERE 1=1 <if test="empName != null and empName != ''"> AND ei.emp_name LIKE CONCAT('%', #{empName}, '%') </if> <if test="deptId != null and deptId != 0"> AND ei.dept_id = #{deptId} </if> <if test="attType != null and attType != 0"> AND ar.att_type = #{attType} </if> <if test="startTime != null"> AND ar.att_time >= #{startTime} </if> <if test="endTime != null"> AND ar.att_time <= #{endTime} </if> ORDER BY ar.att_time DESC </select>

为什么用<if>不用<where>

<where>标签会自动去掉第一个AND,但这里WHERE 1=1是故意写的。因为<if>条件可能全不满足(比如用户什么都没填),WHERE 1=1保证SQL语法永远正确。如果用<where>,当所有<if>都不成立时,SQL变成SELECT ... FROM ... WHERE,直接语法错误。

日期范围查询的陷阱:

startTimeendTimeString类型(如"2023-10-01"),不是Date。因为前端传的是文本框值,后端没做类型转换。所以SQL里用ar.att_time >= #{startTime},MyBatis会自动把字符串转成DATE类型比较。但要注意,att_time字段是DATETIME,所以"2023-10-01"会被MySQL解释为"2023-10-01 00:00:00",查询当天数据没问题;但如果用户填"2023-10-01""2023-10-01",想查全天,实际只查了00:00:00这一秒。解决方案在AttRecordService.listByCondition()里:如果startTimeendTime相同,自动把endTime设为startTime + ' 23:59:59'。这个细节,项目说明.txt里没写,但代码里有。

4.3 薪资模块联动逻辑:考勤、岗位、薪资的三角关系

薪资不是独立计算的,它像一个齿轮组,咬合着考勤和岗位两个模块:

  • 岗位决定基数post_info表里有base_salary字段(基本工资),SalaryService.calculateBase(empId)方法根据员工的post_id查出对应职位的基本工资;
  • 考勤影响浮动att_record表里,att_type=3(请假)和att_type=4(加班)直接影响bonusdeductionSalaryService.calculateBonus(empId, month)会统计该员工当月加班总时长,按200元/小时计算奖金;同时统计请假天数,按日薪×天数扣款;
  • 最终生成SalaryService.generateSalary(empId, yearMonth)方法,把base_salarybonusdeduction塞进salary_info表,并生成total_salary = base_salary + bonus - deduction

关键代码位置:

  • SalaryService.java第87行:public SalaryInfo generateSalary(Integer empId, String yearMonth)
  • SalaryMapper.xml第45行:<insert id="insertSalary">,注意useGeneratedKeys="true"keyProperty="salaryId",确保插入后能拿到自增主键;
  • SalaryController.java第52行:@RequestMapping("/admin/salary/generate"),这是管理员批量生成当月薪资的入口。

实操心得:第一次生成薪资前,务必先确认考勤数据。我试过直接点“生成薪资”,结果所有员工bonus都是0,排查半小时才发现,att_record里当月没有一条att_type=4(加班)的记录。记住:薪资是考勤的结果,不是凭空生成的。

5. 常见问题与排查技巧实录:那些让我凌晨三点还在改的Bug

5.1 经典问题速查表

问题现象可能原因排查步骤解决方案
启动Tomcat报错:java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServletWEB-INF/lib下的Spring MVC jar未被Eclipse识别① 检查Deployment Assembly是否添加了Maven Dependencies;② 查看WEB-INF/lib目录下是否有spring-webmvc-4.3.29.RELEASE.jar项目右键 → Properties → Deployment Assembly → Add → Java Build Path Entries → Maven Dependencies → Finish
登录页CSS样式丢失,页面纯文字web.xml<welcome-file-list>指向了index.jsp,但index.jsp里引用的CSS路径错误① 查看WebRoot/index.jsp第8行<link rel="stylesheet" href="css/style.css">;② 确认WebRoot/css/style.css文件存在CSS路径是相对index.jsp的,index.jspWebRoot根目录,所以href="css/style.css"正确。若误写成href="../css/style.css"则404
添加员工时,部门下拉框为空DeptController.list()返回空JSON,或dept_info表无数据① 直接浏览器访问http://localhost:8080/ems/admin/dept/list;② 查看返回JSON是否为空数组;③ 执行SELECT * FROM dept_info;检查ems.sql是否执行成功,dept_info表是否有数据。初始化数据里,dept_id=1是“总部”,必须存在
员工登录后,“考勤记录”菜单不显示AuthInterceptor未生效,或sessionuserRole值错误① 在AuthInterceptor.preHandle()第一行加System.out.println("Role in session: " + role);;② 登录后看控制台输出确认LoginController.login()方法里session.setAttribute("userRole", emp.getRole())执行了,且emp.getRole()返回"employee"(不是"EMPLOYEE""2"
导出考勤统计Excel时,中文乱码为??WebRoot/js/export.jscontentType未设字符集① 查看exportExcel()函数中xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");;② 缺少charset=utf-8修改为xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");

5.2 我踩过的三个深坑与独家技巧

坑一:MySQL 8.0的caching_sha2_password认证插件

现象:Eclipse控制台报java.sql.SQLException: Unknown system variable 'query_cache_size',或连接不上数据库。

原因:MySQL 8.0默认用caching_sha2_password插件,而项目用的mysql-connector-java 5.1.47驱动不支持。

解决方案(二选一):
-推荐:降级MySQL用户认证方式。登录MySQL,执行:
sql ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;
- 或升级驱动:替换WEB-INF/lib/mysql-connector-java-5.1.47.jar8.0.28.jar,并修改jdbc.properties里的driverClassName= com.mysql.cj.jdbc.Driver,URL加?serverTimezone=Asia/Shanghai&useSSL=false

坑二:Eclipse的Build Automatically导致热部署失败

现象:改了LoginController.java,重启Tomcat,但新代码不生效,还是旧逻辑。

原因:Eclipse的自动构建有时会卡住,class文件没更新。

独家技巧:关闭自动构建,手动触发。
- Project → UncheckBuild Automatically
- 改完代码后,Project →Clean...→ Clean all projects → OK;
- 此时bin目录下的class文件会被清空,Eclipse会强制重新编译所有Java文件;
- 再启动Tomcat,100%加载最新代码。

坑三:emp_info.password字段被截断,导致所有用户无法登录

现象:初始化数据插入后,SELECT password FROM emp_info WHERE emp_code='admin';返回21232f297a57a5a743894a0e4a801fc3(MD5),但系统用的是SHA256。

原因:ems.sqlINSERT INTO emp_infopassword值是MD5,但LoginServiceDigestUtils.sha256Hex()校验。两者算法不匹配。

真相:ems.sql的初始化密码确实是MD5,但LoginService.checkLogin()方法里,有一段兼容逻辑:

// 兼容旧版MD5密码 if (emp.getPassword().length() == 32 && !emp.getPassword().equals(DigestUtils.sha256Hex(password))) { // 尝试MD5校验 if (emp.getPassword().equals(DigestUtils.md5Hex(password))) { // MD5匹配,立即升级为SHA256 emp.setPassword(DigestUtils.sha256Hex(password)); empMapper.updatePassword(emp); return emp; } }

所以首次登录用admin123,系统会自动把密码升级为SHA256并存回数据库。这个细节,项目说明.txt里完全没提!这就是为什么你第一次登录成功后,再查数据库,password字段就变成64位了。

最后一个小技巧:想快速验证所有模块是否正常?不用一个个点。直接在浏览器地址栏依次输入以下URL(管理员登录后):
-http://localhost:8080/ems/admin/dept/list(部门列表)
-http://localhost:8080/ems/admin/emp/list(员工列表)
-http://localhost:8080/ems/admin/att/list(考勤列表)
-http://localhost:8080/ems/admin/salary/list(薪资列表)
-http://localhost:8080/ems/employee/att/list(员工考勤)

每个URL返回200且有数据(哪怕空数组),就说明对应模块的Controller、Service、Mapper、SQL全部通路。这是我给学生答辩前做的最后一道“健康检查”,比跑单元测试还快。

这个SSM考勤系统,它不追求高并发、不炫技微服务、不堆砌设计模式。它就像一把磨得锃亮的螺丝刀,专治Java Web开发入门阶段的拧巴劲儿。从ems.sql里一个DEFAULT CURRENT_TIMESTAMP的字符集选择,到AuthInterceptor里一行session.getAttribute("userRole")的类型判断,再到SalaryService里加班费计算的200元/小时这个硬编码——所有细节,都是真实项目里踩过坑、流过汗、熬过夜才定下来的。它不教你“应该怎么做”,它用代码告诉你“实际就是这样做的”。当你把这套系统从解压到跑通,再从跑通到改出一个属于自己的小功能(比如给考勤打卡加个GPS定位),你就已经跨过了那道叫“学完了但不会写”的门槛。剩下的,只是时间问题。

本文还有配套的精品资源,点击获取

简介:基于Spring+SpringMVC+MyBatis(SSM)搭建的企业级考勤管理项目,适配JDK 8、Eclipse开发环境和Tomcat 8/9,数据库使用MySQL。压缩包内含完整可运行源码(ems.zip)、一键执行的建表与初始数据脚本(ems.sql),以及详细部署步骤和运行说明(项目说明.txt)。系统采用双角色权限设计:管理员拥有全部模块操作权限,员工仅能查看和维护本人相关信息。功能模块包括部门管理(组织架构维护)、职位管理(岗位类型配置)、员工档案管理(基础信息增删改查)、考勤记录(打卡、请假、加班、出差等状态录入与统计)、薪资计算(自动关联岗位与考勤生成工资条)、意见提交(员工反馈通道+后台审核归档)、个人信息管理(头像、联系方式、密码等自主更新)。所有模块均通过本地实测,无缺失依赖、无编译报错,支持直接导入Eclipse运行,适合Java初学者练手、课程设计或本科毕业设计快速落地。


本文还有配套的精品资源,点击获取

http://www.jsqmd.com/news/954781/

相关文章:

  • 深入SAP金额转换:从BAPI_CURRENCY_CONV_TO_EXTERNAL函数看JPY、KWD的存储奥秘
  • 终极Markdown格式规范检测:Typora插件如何高效提升文档质量
  • 3步解锁网易云音乐加密格式:ncmdump让你的付费音乐真正属于你
  • ncmdump解密指南:3步破解网易云音乐NCM加密,实现跨平台播放自由
  • Agent Marketplace:智能体经济的开端
  • MATLAB一键跑出VIF数值,快速揪出回归里互相‘打架’的变量
  • 终极指南:3步实现Mac微信防撤回,零配置保护重要信息
  • 技术专题:BepInEx 6.0架构演进深度解析与IL2CPP签名耗尽解决方案
  • Claude Code Memory Skill:一个轻量级本地 Markdown 记忆库实践
  • 手把手教你用Vivado仿真SelectIO IP核:从testbench看懂数据对齐与bitslip机制
  • AI编程编辑器的诚实竞争:上下文真实性与执行确定性实战
  • AMD Ryzen硬件底层调试深度解析:SMUDebugTool高级应用实战
  • 四川酒店餐饮低成本运营的隐形冠军——酒店餐饮低耗品一站式采购指南 - 深度智识库
  • 终极指南:3分钟掌握Windows窗口置顶神器AlwaysOnTop
  • CentOS 7服务器上,用yum安装PHP 8.1后必做的5项安全与性能调优
  • 考研数学二多元函数微分学保姆级攻略:从偏导到梯度,手把手带你搞定同济高数下册第九章
  • 6.3万Star的反向代理Traefik,让你彻底告别Nginx手动配路由
  • MATLAB三路语音盲分离实操资源:含原始语音、混合音频、分离代码与效果可视化
  • 2026年四川省供应链行业含金量最高证书推荐-SCMP官方报考指南 - 众智商学院课程中心
  • AMD Ryzen调试工具SMUDebugTool:免费开源的处理器深度控制指南
  • TIA Portal ProDiag报警管理避坑指南:Get_Alarm指令的ProducerID到底怎么选?
  • 3种方法彻底解决Wand专业版限制:从基础解锁到远程控制的完整实战指南
  • 从内表到数据库:ABAP里`COUNT(*)`和`lines()`到底该用哪个?一次讲清选择逻辑
  • R语言gamlss扩展包1.7-0:内置30+非标准概率分布,含SICHEL、SHASH、GG等完整d/p/q/r函数
  • 终极指南:3个步骤掌握Logisim-Evolution数字电路仿真软件
  • 长沙市天加中央空调维修师傅电话|各区金牌师傅,靠谱选欧米到家 - 欧米到家
  • 2026年6月4日 | AI日报:Gemma 4 本地多模态、AI Agent 基础设施加速成型
  • N_m3u8DL-CLI-SimpleG:3分钟搞定M3U8视频下载的图形界面神器
  • AI事实与迷思:工程师必备的认知校准指南
  • 让串口调试更智能:利用快马AI辅助解析sscom捕获的复杂设备数据