简介:面向高校毕业设计、课程设计场景的 SSM+JSP 公司员工考勤管理系统项目,基于 MySQL 存储,含可运行源码、SQL 脚本与配套文档。系统按角色划分权限:普通员工与部门经理均可维护个人资料、查看上班时间公告、提交请假/出差及差费报销,并完成日常出勤;管理员额外负责系统用户、部门、部门经理与员工等基础数据维护,并对出勤和请假进行统计。登录模块为不同角色提供独立入口,请假与出差设置、考勤管理形成完整业务闭环,适合学习 SSM 分层架构、JSP 页面交互与权限控制思路。资源为 zip 压缩包,约 29.96MB,内含工程源码、数据库脚本和说明文档,导入 SQL、配置好环境即可运行。已有 1847 人浏览学习,对正在完成考勤类管理系统的课设或毕设有直接参考价值。
1. SSM 员工考勤管理系统,能跑到答辩现场才是硬道理
课设季最怕的不是没选题,而是从网上下了一套 jsp 考勤源码,解压之后要么缺 SQL、要么依赖对不上,折腾两天连登录页都出不来。这套基于 SSM 的员工考勤管理系统属于典型的毕设/课设三层结构——Spring、SpringMVC、MyBatis 配 MySQL,前端走 JSP,包里带了可运行源码、SQL 脚本和说明文档,覆盖一般员工、部门经理、系统管理员三种角色,请假、出差、差费报销、考勤、日常出勤、公告、统计这些企业考勤场景里该有的模块都在。它的价值不在技术多新,而在于结构完整、角色分明、字段和页面一一对得上,适合做毕业设计和课程设计的人拿去跑通再二开,也适合刚学 SSM 的开发者拿它当第一个完整项目拆着看。能不能用、坑在哪,下面按我实际拆包的经验一步一步说。
2. 权限模型与功能拆解:十三个模块是怎么切给三种角色的
2.1 三角色功能树:员工、部门经理、系统管理员各管哪一摊
先看权限边界,这是答辩时老师几乎必问的一层。一般员工能碰的是个人资料管理、上班时间公告管理、请假管理、出差管理、差费报销管理、考勤管理、日常出勤管理这七项;部门经理在这套系统里拿到的功能列表和员工一模一样,没有额外的审批入口;系统管理员则是最全的一套:系统用户管理、部门管理、部门经理管理、员工管理、公告管理、请假管理、出差管理、差费报销管理、考勤管理、日常出勤管理,外加员工统计和请假统计两个报表。
这里有个很容易误解的地方:不少同学以为"部门经理"一定要有审批按钮,拿到包之后找不到审批入口,以为功能缺失。实际上这套系统的设计逻辑是"同功能、不同数据范围"——部门经理和一般员工共用功能入口,只是在列表展示和统计口径上按部门维度做了隔离。先把"角色决定入口,入口决定数据范围"这个前提记在脑子里,后面调权限、看代码都不会钻牛角尖。
| 功能模块 | 一般员工 | 部门经理 | 系统管理员 |
|---|---|---|---|
| 个人资料管理 | 查看/修改本人 | 查看/修改本人 | 维护全体员工 |
| 上班时间公告 | 查看 | 查看 | 发布、编辑、删除 |
| 请假管理 | 申请/查看本人 | 申请/查看(部门维度) | 全量管理 |
| 出差管理 | 申请/查看本人 | 申请/查看(部门维度) | 全量管理 |
| 差费报销 | 申请/查看本人 | 申请/查看(部门维度) | 全量管理 |
| 日常出勤 | 查看本人记录 | 查看本部门记录 | 查看全员 |
| 员工统计 | 无 | 无 | 部门/全公司统计 |
| 请假统计 | 无 | 无 | 按状态/部门聚合 |
| 用户/部门/经理管理 | 无 | 无 | 有 |
2.2 核心数据表:包里的 SQL 脚本通常会建哪几张表
这类 SSM 考勤包的 SQL 脚本结构高度相似,解压后先看 sql 文件里建了哪些表,基本就能预判整个系统长什么样。按功能反推,核心表应该有这么几张:用户表(含角色标识和所属部门)、部门表、公告表、请假申请表、出差申请表、差费报销表、日常出勤表。有些包会把考勤和日常出勤拆成两张表,一张存签到签退明细,一张存每日汇总状态,模板不同,但实体关系是同一个方向。
字段设计上,最值得关注的是用户表。常见做法是 username、password、real_name、role、dept_id、status 这几个字段打底。role 通常用整数区分,0 代表管理员、1 代表部门经理、2 代表一般员工;dept_id 关联部门表,是后面所有"按部门查"的基础。密码字段一般存的是 MD5 值,这一点在你手工改库造测试账号的时候尤其重要——直接在 SQL 里 insert 明文密码是登不进去的,得先算出 MD5 再入库。
请假表、出差表和报销表是一组"流程型"数据,字段基本都是申请人、开始时间、结束时间、事由/目的地、状态、备注。状态字段建议关注一下取值:1 待审、2 通过、3 驳回是常见的写法。日常出勤表则围绕日期和上下班时间展开:user_id、att_date、sign_in_time、sign_out_time、status。status 往往不是存"迟到/正常"这种汉字,而是存 0/1/2 之类的码值,页面再通过字典或 if 判断渲染成中文。
2.3 分层与请求链路:Controller 到 Service 再到 Mapper 的调用模板
SSM 项目翻开源码,包名一般是 controller、service、mapper(或者 dao)、entity/pojo,再加一个拦截器包。这套考勤系统的请求链路非常规整:JSP 页面发请求 → SpringMVC 的 Controller 接参数 → Service 处理业务 → MyBatis 的 Mapper 接口操作数据库 → 结果返回 Controller → 转发或重定向回 JSP。
以请假为例,前端表单提交到 LeaveController 的 apply 方法,Controller 把前端传来的参数封装进 Leave 实体,调用 service 层的 addLeave 方法,service 内部由 mapper 的 insert 语句落库,之后重定向回列表页。这套链路清晰还有一个好处:二开的时候,你要加一个"销假"功能,只需要沿着这条线复制一个方法改改业务判断就行,不需要动架构。
拦截器通常放在 interceptor 包里,实现的是 HandlerInterceptor 接口,preHandle 方法里判断 session 有没有 user 对象,没有就重定向到登录页。这套系统的登录是角色共用一个登录页,登进去之后根据 role 字段跳转到不同主页,而不是三个角色各写一套登录。这个设计在代码里表现为一次登录、三处跳转判断。
3. 环境搭建与源码部署:JDK、Tomcat、MySQL 的版本组合与三步跑通
3.1 版本选型:为什么 JDK 8 + Tomcat 8.5 + MySQL 5.7 最稳
能跑起来是这套源码的第一价值,而跑不起来的头号原因就是环境版本不匹配。这类 SSM 课设包大多在 2018 到 2021 年之间生成,当时的标配是 JDK 8、Tomcat 8.5、MySQL 5.7,配套的 Spring 版本通常是 4.x,MyBatis 3.4 左右。用这个组合去启动基本零障碍,而新手最容易踩的就是用 JDK 17 或 MySQL 8.0 去跑。
JDK 8 和 JDK 17 的差别不是不能编译,而是老项目的 cglib 代理、JSP 编译器和高版本 JDK 之间经常出兼容问题,报错信息还不直观。MySQL 8.0 的坑更典型:驱动类名从 com.mysql.jdbc.Driver 改成了 com.mysql.cj.jdbc.Driver,密码加密方式从 mysql_native_password 变成了 caching_sha2_password,老驱动连上去直接报认证失败。所以我的建议很直接:别跟版本较劲,装一个 JDK 8 和 MySQL 5.7,把这些不确定因素一次性排除掉。
3.2 SQL 导入与连接配置:四个必须替换的参数位
拿到包先做两件事:把 sql 文件导入 MySQL,再把数据库连接配置改成你本机的账号密码。导入用 Navicat 或命令行都行,这里给命令行操作,因为最不容易受图形化工具的版本干扰。
mysql -u root -p Enter password: ******** mysql> CREATE DATABASE attendance DEFAULT CHARACTER SET utf8; mysql> USE attendance; mysql> SOURCE D:/ssm_attendance/attendance.sql;导入成功后会看到一堆建表和 insert 语句执行成功的提示。要注意 sql 文件里可能已经带了 CREATE DATABASE 语句,如果带了,第一步可以跳过,但务必确认库名和你接下来配置文件的 jdbc 连接串一致。
# jdbc.properties 常见的四个替换点 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/attendance?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456driver 用老驱动类名对应 MySQL 5.x;url 里的 3306 端口要和你本机 MySQL 一致;username 和 password 换成你自己的。这里最容易翻车的是密码里有特殊字符,比如 @ 或 &,在 properties 文件里会被当成分隔符解析,导致连不上,常见做法是改成一个纯数字字母的组合密码,省得踩这个坑。characterEncoding=utf8 这参数别删,删了中文写入数据库大概率变乱码。
3.3 部署启动:IDEA 里从添加 Tomcat 到看到登录页
这个包如果带 pom.xml,就是 Maven 工程,导入到 IDEA 后先等依赖下载完;如果不带 pom 而是 lib 目录里有 jar 包,说明是普通 Web 工程,直接把项目添加到 Tomcat 就行。Maven 工程要注意一个细节:检查 pom 里 spring、mybatis、mysql-connector-java 这几个依赖的版本,如果 mysql 驱动写的是 5.1.47 这类老版本,连 MySQL 8 必挂,配合 5.7 就没事。
<!-- pom.xml 中与数据库相关的关键依赖示意 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>IDEA 里配置 Tomcat 的操作链是:Run → Edit Configurations → 点 + 选 Tomcat Server Local → 指定本地 Tomcat 8.5 路径 → Deployment 选项卡里点 + 选 Artifact → 选 war exploded。这里有个常见误操作:新手选了 war 格式,启动时会反复重新打包一次,慢而且容易和 Tomcat 的部署目录冲突,用 war exploded 做开发调试会快很多。
启动之后浏览器访问http://localhost:8080/项目名/,看到登录页就说明环境通了。如果你改过 Tomcat 端口,记得访问地址里的端口也要同步改。看到登录页只是第一步,真正验证系统能不能用,还得用包里的默认账号登录三个角色各走一遍流程,这个验证方法放到最后一章细说。
4. 登录模块与权限控制:Session 写入、拦截器放行与三角色跳转
4.1 登录流程拆解:表单提交、MD5 校验、角色进 Session
登录是这套系统的入口,也是理解权限控制的钥匙。前端是一个 login.jsp 页面,表单提交到 Controller 的 login 方法,参数是 username 和 password,注意 password 在 Controller 里通常不做明文比对,而是把用户输入的值先做一次 MD5,再拿 MD5 结果去查库。这样设计的目的是让数据库里不落明文密码,属于课设项目里安全做法的及格线。
// LoginController 登录方法核心逻辑示意 @RequestMapping("/login") public String login(String username, String password, HttpSession session, Model model) { // password 先做 MD5,再传给 service 层 String md5Pwd = DigestUtils.md5DigestAsHex(password.getBytes()); User user = userService.login(username, md5Pwd); if (user == null) { // 用户名或密码错误,回到登录页并给出提示 model.addAttribute("error", "用户名或密码错误"); return "login"; } // 登录成功:把完整用户对象放进 session,角色信息跟着走 session.setAttribute("loginUser", user); // 按角色跳转到不同主页 if (user.getRole() == 0) { return "redirect:/admin/index"; } else if (user.getRole() == 1) { return "redirect:/manager/index"; } else { return "redirect:/employee/index"; } }这一段的核心逻辑在最后三行:角色决定跳转方向。0 是管理员,跳 admin 首页;1 是部门经理,跳 manager 首页;2 是一般员工,跳 employee 首页。三个首页其实是三套菜单模板,里面的功能入口由角色控制,但因为功能模块本身是复用的,所以员工首页和经理首页的菜单长得几乎一样,区别在跳转时带的 userId 或 deptId 不同。
Session 里存的是整个 user 对象而不仅仅是 userId,这有个好处:页面头部要显示当前登录人姓名、部门,直接从 session 里取,不用每次查库。但这也是一个易错点——如果你在二开时给 user 表加了字段,必须同步更新 entity 类,否则 MyBatis 查出来封装不进对象,页面上就显示 null。
4.2 拦截器配置:登录放行与角色验权的路径规则
登录后的所有请求都要验证 Session 里有没有用户,这件事由拦截器统一做,而不是每个 Controller 里重复判断。拦截器实现 HandlerInterceptor 接口,在 preHandle 里检查 session,没有 loginUser 就重定向到登录页。
// LoginInterceptor 的 preHandle 方法核心逻辑 @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) { // 未登录,重定向到登录页,return false 中断后续请求 response.sendRedirect(request.getContextPath() + "/login.jsp"); return false; } return true; }拦截器注册在 spring-mvc.xml 里,用 mvc:interceptors 配置路径规则。这里最容易踩的坑是路径放行范围没写对:静态资源 css、js、images 以及 login 请求本身必须放行,否则登录页都加载不出来,或者登录请求被拦导致死循环跳转。
<!-- spring-mvc.xml 拦截器注册示意 --> <mvc:interceptors> <mvc:interceptor> <!-- 拦截所有 /admin/** 和 /manager/** 和 /employee/** 请求 --> <mvc:mapping path="/admin/**" /> <mvc:mapping path="/manager/**" /> <mvc:mapping path="/employee/**" /> <!-- 放行登录页、登录动作和静态资源 --> <mvc:exclude-mapping path="/login" /> <mvc:exclude-mapping path="/login.jsp" /> <mvc:exclude-mapping path="/css/**" /> <mvc:exclude-mapping path="/js/**" /> <mvc:exclude-mapping path="/images/**" /> <bean class="com.example.interceptor.LoginInterceptor" /> </mvc:interceptor> </mvc:interceptors>这里有个经验:如果你把拦截范围写成/**而 exclude 写漏了静态资源,页面会变成只有文字没有样式,很多人以为是 CSS 路径问题,排查半天结果发现是拦截器把 css 请求也拦了。另外,角色验权到这个程度就是"登录即可访问",并没有细到"员工不能访问 admin 路径",这个包多数是不做二次角色校验的。二开时如果老师追问越权问题,可以在拦截器里加一步 role 判断。
4.3 用户管理子模块:管理员对三类账号的统一增删改查
用户管理是管理员独有的功能,后台对应 UserController 的一组增删改查方法。这里说的"系统用户管理"和"员工管理"在实现上往往是同一张用户表的不同视图——系统用户管理侧重账号层面,员工管理侧重资料层面,底部都是增删改查,只是表单字段和跳转页面略有差异。
// UserController 新增用户方法核心逻辑 @RequestMapping("/add") public String add(User user, HttpServletRequest request) { // 密码加密后再入库,避免明文落库 String md5Pwd = DigestUtils.md5DigestAsHex(user.getPassword().getBytes()); user.setPassword(md5Pwd); int count = userService.addUser(user); if (count > 0) { return "redirect:/admin/user/list"; } request.setAttribute("error", "添加失败,请检查用户名是否重复"); return "user/add"; }新增用户时,角色和部门是下拉框,部门数据从部门表查出来填充;用户名一般要查重,这是少数在 service 层写判断的地方。编辑用户的密码逻辑有一个特定的坑:如果编辑页面里密码框为空,就表示不修改密码,这是个非常常见的约定。很多人在二开时直接update set password = ?,把空值写进数据库,导致用户再也登不进去,正确写法是密码为空时不更新 password 字段。
删除用户则要注意外键关联问题:一个员工已经有请假记录、出差记录和考勤记录,直接把用户表记录删掉,轻则列表出现关联缺失,重则报外键约束错误。常见的兜底做法是改 status 状态字段做到期禁用,而不是物理删除,这个点可以在答辩时主动提,属于加分项。
5. 考勤与统计模块排查指南:出勤数据流、报表 SQL 与五个高频坑
5.1 日常出勤的数据流:签到记录、状态判断与月度汇总
日常出勤是这个系统里逻辑最重的一块。员工登录后进入出勤页面,点"上班签到",后台就往出勤表里 insert 一条记录,带上当天日期、当前时间和默认状态;下班时点"下班签退",更新这条记录的签退时间,并根据上班时间和公司规定的时间点做迟到早退判断。
这里的判断逻辑通常是写死在 service 方法里:拿签到时间跟一个固定的时间字符串比较,比如09:00:00之后算迟到。这个写死的问题我放在最后一章讲参数化改造。出勤状态的几种取值一般是:0 正常、1 迟到、2 早退、3 缺卡,其中缺卡是指只签了到没签退,或者只签退没签到,页面要在列表里用状态码渲染成对应的中文标签。
-- 出勤明细查询:按员工和日期范围查,带状态过滤 SELECT u.real_name AS userName, d.dept_name AS deptName, a.att_date AS attDate, a.sign_in_time AS signInTime, a.sign_out_time AS signOutTime, CASE a.status WHEN 0 THEN '正常' WHEN 1 THEN '迟到' WHEN 2 THEN '早退' WHEN 3 THEN '缺卡' END AS statusText FROM t_attendance a LEFT JOIN t_user u ON a.user_id = u.id LEFT JOIN t_dept d ON u.dept_id = d.id WHERE a.att_date BETWEEN #{startDate} AND #{endDate} AND u.dept_id = #{deptId} ORDER BY a.att_date DESC这条 SQL 其实是出勤管理列表页的核心查询。注意两个 LEFT JOIN 的写法:出勤表只存 user_id,用户名和部门必须联表查。很多新手在改这个列表时把 JOIN 写成了 INNER JOIN,导致有出勤记录但用户被删掉的数据查不出来,列表行数凭空变少,看起来像"数据丢了",实际上是连接方式的问题。
5.2 员工统计与请假统计:报表查询背后的聚合 SQL
管理员的统计功能本质是分组聚合。员工统计一般是按部门分组数人头,再叠加每个部门的出勤异常数;请假统计则按请假状态分组,看待审、已通过、已驳回各占多少,也可以按部门维度统计请假天数。
-- 请假统计:按部门分组统计请假次数与总天数 SELECT d.dept_name AS deptName, COUNT(l.id) AS leaveCount, SUM(DATEDIFF(l.end_date, l.start_date) + 1) AS totalDays, SUM(CASE WHEN l.status = 1 THEN 1 ELSE 0 END) AS pendingCount FROM t_leave l LEFT JOIN t_user u ON l.user_id = u.id LEFT JOIN t_dept d ON u.dept_id = d.id GROUP BY d.dept_name ORDER BY leaveCount DESCDATEDIFF(end_date, start_date) + 1是算请假天数的常见写法,加 1 是因为当天算一天,直接相减会少一天。这是个特别容易错的细节,也是答辩时老师喜欢追问的点之一。如果你在这个包的基础上做统计报表,先在 SQL 里跑一遍这条语句,对照页面上的数字,确认天数和条目数对得上,再考虑要不要加图表。图表方面,这个包大概率是 JSP 页面里用 JSTL 表格展示,没有集成 ECharts,二开时可以加一个 ECharts 的 CDN 引入,把统计结果渲染成柱状图,属于低成本高视觉效果的改造。
5.3 运行期五个高频坑:现象、原因、解决办法
这里整理我拆这类包时反复遇到的五个问题,每条都是"现象 → 原因 → 解决"的排查结构。
第一个坑:Tomcat 启动报端口被占用。现象是启动日志里出现Port 8080 was already in use,页面访问不了。原因是上一次启动没有正常停止,或者本机有其他程序占了 8080。解决办法是关掉多余的 Java 进程,或者把 Tomcat 端口改掉,IDEA 里在 Server 选项卡的 HTTP port 改 8081,同时确认项目里没有写死端口的地方。顺手在配置文件里把 Tomcat 的 shutdown 端口也看一眼。
第二个坑:MySQL 8 连不上报认证错误。现象是启动时报Unable to connect to database或Public Key Retrieval is not allowed。原因是用 MySQL 8 的默认加密插件配了老驱动。解决方法是换回 MySQL 5.7,或者把驱动升级到 8.x、url 里加allowPublicKeyRetrieval=true。我的建议还是直接用 5.7,课设环境里省事永远是第一位的。
第三个坑:页面中文乱码。现象是 JSP 页面显示中文正常,但从数据库查出来的中文是问号。原因是数据库连接串没有 characterEncoding=utf8,或者数据表本身是 latin1 字符集。解决方法是先改 jdbc.url 加参数,再检查建表语句里的DEFAULT CHARSET=utf8。如果数据已经乱码了,改完连接串后把表删了重新导入 SQL,别想着修复脏数据,课设阶段重建库最快。
第四个坑:ClassNotFoundException 或 NoClassDefFoundError。现象是启动时缺类报错。原因通常是 Maven 依赖没下全,或者普通 Web 工程少拷贝了 jar 到 WEB-INF/lib。解决方法是 Maven 工程先执行mvn clean再重新导入依赖;非 Maven 工程检查 lib 目录下是否缺少 mybatis 和 mysql 驱动的 jar,缺什么从本机 maven 仓库或官网补什么。这类包还有一个常见情况:源码是 Maven 结构,但被人手动删了 pom.xml 发布成压缩包,这种需要自己补一个 pom 或者转成 lib 工程。
第五个坑:登录成功后跳转回登录页。现象是输入正确账号密码,页面闪一下又回到登录页,浏览器地址栏能看到 redirect。原因是拦截器把登录后的跳转请求也拦了,而 session 写入的时机在跳转之前确实执行了,但拦截器读的 key 和 Controller 写入的 key 不一致,比如 Controller 里写loginUser,拦截器里读user,永远拿不到。解决方法是统一 session 的 key 命名,全局搜setAttribute和getAttribute对比一遍。这个坑最隐蔽,报错日志里看不出任何异常,只能靠断点或加日志排查。
6. 二次开发进阶:把课设考勤系统改造成能写进简历的项目
6.1 考勤规则参数化:把迟到阈值从代码里抠出来
原包把上班时间、迟到判定写死在 Service 里,这能跑,但答辩和面试时经不起问。一次低成本高回报的改造是把这些规则挪到配置里。常见做法是建一张规则表,或者退一步直接在 properties 文件里配。
# attendance.properties 新增考勤规则配置 attendance.start_time=09:00:00 attendance.end_time=18:00:00 attendance.late_minutes=0 attendance.early_minutes=0Service 里用@Value("${attendance.start_time}")注入,迟到判断就变成拿签到时间字符串和配置值比较。改动量不大,但把"写死的业务规则"变成了"可配置的业务参数",这一条放在简历的项目描述里比"实现了迟到判断"有说服力得多。更进一步可以做成管理员在后台维护规则的表单,那就从课设级别往上迈了一大步了。
6.2 全流程验证:一组测试数据覆盖三角色主链路
改造完最怕改出回归问题。我习惯维护一套测试账号:一个管理员、一个部门经理、两个普通员工,分属两个部门。每次改完代码,强制走一遍完整链路——管理员登录建部门、建经理、建员工,员工登录提交请假和出差申请,管理员复核记录,再点签到签退验证出勤状态,最后看统计页数字是否对得上。
这套验证方法不复杂,但能覆盖这个包 80% 以上的功能点,而且几个关键数据可以提前算好:两个员工同一天签到,一个 08:50 一个 09:05,统计页应该出现一个正常一个迟到,这就同时验证了签到写入、状态判断和统计聚合三段逻辑。从那以后我每次拿到课设源码包,都强制走一遍环境验证加全流程数据测试,宁可多花二十分钟,也不在答辩现场翻车。希望帮到你。
本文还有配套的精品资源,点击获取