简介:这份考勤登记管理系统资源包将源码、原型与数据库完整打包,面向需要完成课程设计、毕业设计或快速搭建考勤管理模块的开发者与学习者。包内包含系统源码、可交互原型、数据库脚本及其备份文件,覆盖从界面设计到数据表结构的核心环节,适合用于前端原型演示、后端功能改造及数据库初始化练习。资源共4个文件,含sql脚本及bak备份便于数据恢复,pdf文档说明系统设计,zip为工程文件压缩包,整体仅963KB,轻量易下载。已有717人学习浏览,适合计算机相关专业学生或初级开发人员参考借鉴。通过对照原型、源码与数据库可快速理清考勤登记流程,减少重复建表与页面搭建的工作量,便于在此基础上扩展请假统计、异常打卡等功能。
1. 考勤登记管理系统:一个交付包里的三件套到底解决了什么问题
不管是毕业设计、课程设计,还是小团队内部要一套“能用的”考勤工具,市面上的交付物通常都长成一个样子:源码 + 原型 + 数据库。很多同学拿到手第一反应是把代码跑起来,然后发现数据库脚本导入报错、原型图跟页面对不上、代码里还写了几个根本不存在的数据表。这类系统的技术门槛并不高,难点在于“登记”这个动作背后的业务规则——迟到怎么算、跨天班次怎么记、请假和打卡怎么互相影响。这篇文章不绕弯子,直接把“考勤登记管理系统”拆成三层:数据库怎么设计、原型图怎么画、源码怎么落地,每一层都有可以直接抄的脚本和踩坑记录。适合正在做课设的在校生,也想给公司搭一套轻量考勤后台的开发者。
2. 考勤系统的数据库设计:从登记这一个动作开始拆表
2.1 员工表和考勤登记表是最低配置,但还不够
考勤系统的核心动作是“登记”,也就是记录某个员工在某一个时间点上的状态。围绕这个动作,最少需要两张表:员工表存放基础信息,考勤登记表存储每一次的打卡或签退记录。但实际做下来你会发现,只有这两张表撑不起真实业务。举个例子:员工请了一天事假,这天的考勤记录是缺失的,月末统计时你是按“旷工”算还是按“请假”算?所以必须再有一张请假表。同理,加班、外勤、补卡,每一种“非正常打卡”都需要一张对应的业务表,否则统计口径完全对不上。
我一般会在一开始就把表分成两类:主数据表和流水表。员工表、部门表是主数据,考勤登记表是流水。每次登记动作都只往流水表里插一行,不修改历史记录。这样做的好处是月末汇总时直接对流水表做聚合,排查异常也很直观。很多同学喜欢在员工表里加一个“最后打卡时间”字段来偷懒,短期看方便,后期出问题你根本不知道是哪一次写坏了。
2.2 请假、加班和排班的表结构:三条常见的扩展路径
继续往业务方向扩展,最先要考虑的是请假。请假表最少需要四个字段:员工ID、开始时间、结束时间、请假类型。这里的类型不要用字符串裸存,建议用数字字典,0表示事假、1表示病假、2表示年假。后端在统计出勤率时就拿这个字段做过滤,别在SQL里写type = '事假'这种比较,后面改个显示名称就会引发连锁报错。
加班表的结构和请假表对称,但多一个“加班时长”计算字段。我见过不少项目把这个字段设计成用户手填,结果员工填 8、填 10 的都有,月末对不上账。正确的做法是只记录开始和结束时间,时长交给后端按规则计算。排班表则看你的系统需不需要固定班次,如果只是记录“上午打卡、下午打卡”,可以用一个简单的班次表存上班时间和下班时间,再给员工表加一个班次ID外键。这样考勤登记时就能自动判断迟到早退,而不用人工去对比时间。
2.3 建表 SQL:用最小字段先跑通核心流程
先把三张核心表建出来,后面加字段都在这个基础上改。下面这个 SQL 是常见做法,字段不追求齐全,但把主键、外键和时间类型都定清楚。
CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID', emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT '工号', name VARCHAR(50) NOT NULL COMMENT '姓名', department_id INT COMMENT '部门ID', schedule_id INT COMMENT '班次ID', status TINYINT DEFAULT 1 COMMENT '1在职 0离职', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT '员工表'; CREATE TABLE attendance_record ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL COMMENT '员工ID', record_date DATE NOT NULL COMMENT '打卡日期', check_in DATETIME COMMENT '上班打卡时间', check_out DATETIME COMMENT '下班打卡时间', status TINYINT DEFAULT 0 COMMENT '0正常 1迟到 2早退 3缺卡', UNIQUE KEY uk_emp_date (emp_id, record_date) ) COMMENT '考勤登记表'; CREATE TABLE leave_request ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, leave_type TINYINT NOT NULL COMMENT '0事假 1病假 2年假', start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, reason VARCHAR(255), status TINYINT DEFAULT 0 COMMENT '0待审批 1通过 2驳回' ) COMMENT '请假表';attendance_record 表里的唯一索引uk_emp_date是这套系统的关键。它的作用是保证同一个员工同一天最多只能有一条汇总记录。打卡动作不是每次插入新数据,而是当天第一次打卡时创建记录,之后都走更新逻辑。这个设计可以规避后面要讲的并发重复登记问题。leave_request 的状态字段也建议保留审批流,哪怕课程设计阶段不写审批页面,这个字段也让后续扩展有话可说。
3. 用原型图把考勤登记的交互流程定下来:两个必画页面与常见误区
3.1 打卡登记页:为什么推荐“拍照+时间戳”而不是一个按钮
原型图的核心价值不是画得好看,而是把流程里每个分支想清楚。打开 Axure 或墨刀,先画打卡登记页。这一页通常有一个大按钮让员工点击完成打卡,但我建议你再加一个“拍照”插口。这不是必须的功能,但原型阶段把它画进去,评审时就能发现一个关键问题:你这个考勤系统是否需要证明“人真的在公司”?按钮打卡太容易被代打,加拍照字段就意味着数据库里要预留图片存储,也意味着源码里要多一个文件上传逻辑。原型图里多这一个框,开发阶段就能提前想到这些。
页面上的时间显示也要坚持用后端时间回传,不要在浏览器端取时间。原型里你可以先画一个“时间由服务器返回”的占位文本,实际开发时就知道这不是写死的。很多画原型的人习惯直接在页面上放一个当前时间,开发照着做也写了本地时间,结果客户端时间不准,打卡记录全是错的。这个坑在原型阶段就能用一行注释避开。
3.2 管理员审批页:列表批量操作比单条审核更符合真实场景
第二个必画页面是考勤审批列表。别画那种一条条点开详情再点通过的流程,真实管理员一天要处理几十条异常记录,批量勾选 + 批量通过才是常态。原型图里画一个带复选框的表格,顶部放“批量通过”“批量驳回”按钮,底部放分页。这一页把列表查询条件和统计口径都画出来,开发时后端接口的参数也就定了。
画完这一页把流程图跑一遍,你会发现一个隐藏的联动逻辑:管理员通过了某条请假申请,系统自动更新员工当天的考勤状态,不再显示旷工。这个联动在原型的“交互说明”里标注清楚,后端实现时就知道审批通过不能只改一张表的状态字段,还要顺手处理考勤汇总记录。
3.3 用一张线框把月末统计页的字段定下来
月末统计页往往在原型阶段被跳过,但这是整个系统最能体现工作量的一页。页面里要有四个统计数字:应出勤天数、实际出勤天数、迟到次数、请假时长。下面接一个明细列表,按员工分组展示每个月的每一天是什么状态。原型阶段把这个页面的字段定死了,源码里那个复杂的汇总 SQL 就有明确的目标。没有这页原型,写代码的人会自由发挥,经常出现统计数字和明细对不上的情况。线框图不用精致,字段对齐就够。
4. 从原型到源码:用 SSM 结构把一个考勤登记接口跑通
4.1 项目骨架:三层结构怎么对应数据库的几张表
源码部分常见做法是用 SSM(Spring + Spring MVC + MyBatis)搭一个小单体应用,数据源指向上一章建好的三张表。工程目录按 controller、service、mapper 三层拆开。Controller 层只做参数接收和返回,Service 层写业务判断,Mapper 层写 SQL。这里最容易犯的错误是在 controller 里写业务判断。比如判断迟到这个逻辑,放 controller 里也能跑,但到时候写单元测试会发现根本没法测。把这个逻辑放进 service,你就能直接用 Mock 数据测试不同时间的登记结果。
工程搭建不贴全量代码,但目录结构必须对齐数据库的表。一个员工对应一个 EmployeeMapper,一张考勤记录对应一个 AttendanceRecordMapper。映射关系清晰之后,新增一张表要动的地方就是四个文件:实体类、Mapper 接口、XML 映射文件、Service。骨架搭对了,后面开发就是填肉。
4.2 登记打卡的后端接口:时间判定和重复打卡校验
打卡登记是整个系统里最核心的接口。它要做的事情有:查员工是否存在、判断当前时间在哪个班次范围内、确认当天是否已有记录、插入或更新 attendance_record。下面这段代码是 Service 层的关键逻辑,我标注了每一步的作用。
public void checkIn(int empId) { // 查询员工,不存在直接抛业务异常 Employee emp = employeeMapper.findById(empId); if (emp == null) { throw new BizException("员工不存在"); } // 查出当天的登记记录 AttendanceRecord record = attendanceMapper.findByEmpIdAndDate(empId, LocalDate.now()); if (record == null) { // 第一次打卡,插入新记录,状态先按正常 record = new AttendanceRecord(); record.setEmpId(empId); record.setRecordDate(LocalDate.now()); record.setCheckIn(LocalTime.now()); record.setStatus(calculateStatus(emp.getScheduleId(), LocalTime.now())); attendanceMapper.insert(record); } else { // 已打过卡,说明是下班打卡,更新时间 record.setCheckOut(LocalTime.now()); // 如果之前缺卡状态,也要一并修正 if (record.getStatus() == 3) { record.setStatus(calculateStatus(emp.getScheduleId(), LocalTime.now())); } attendanceMapper.update(record); } } private int calculateStatus(int scheduleId, LocalTime time) { Schedule sched = scheduleMapper.findById(scheduleId); if (time.isAfter(sched.getStartTime())) { return 1; // 迟到 } return 0; }calculateStatus 方法里只判断了上班时间,没处理跨天班次,后面避坑章节会细讲。现在技术的重点要放在核心逻辑上。第一次打卡走 insert,第二次打卡走 update。这种“一插一更”的处理方式,可以让数据库里每天每个员工始终保持一条记录,月末统计时就不会出现一个人一天打了三次卡、每次计算都重复的情况。你在面试或者答辩时把这个设计逻辑讲清楚,会比贴一堆 CRUD 代码有价值得多。
4.3 前端页面的最小实现:表单提交与状态回显
前端不追求复杂框架,一个 HTML 页面加 jQuery 就够了。打卡页面上放一个按钮,点击后把员工 ID 通过 POST 请求发给后端。接口返回结果直接显示在按钮下方。关键点是页面上不要显示“打卡成功”这种写死文案,要拿后端返回的状态字段来渲染。迟到、正常、早退,每种状态给不同颜色。下面这段前端代码的逻辑注释里写了怎么做。
$('#checkInBtn').click(function () { var empId = $('#empId').val(); $.ajax({ url: '/attendance/checkIn', type: 'POST', data: { empId: empId }, success: function (resp) { if (resp.code === 0) { // 用后端返回的状态字段渲染文案,而不是写死 var statusText = { 0: '正常', 1: '迟到', 2: '早退' }[resp.data.status]; $('#result').text('打卡成功,状态:' + statusText); } else { $('#result').text(resp.msg); } } }); });这段代码里我最想强调的就是statusText这个映射。它要求后端返回结构化数据,不能只返回一个“成功”字符串。很多初学同学习惯后端拼好文案传回来,前端直接显示。这种做法第一轮能跑通,但后续如果要改成显示“迟到 2 分钟”,后端接口就要跟着改。前后端约定好状态码,前端自己拼文案,扩展成本低很多。
5. 考勤系统避坑指南:时间时区、跨天班次与并发登记的三个坑
5.1 服务器时区和数据库时区不一致,打卡时间差 8 小时
现象是员工上午 9 点打卡,数据库里存的却是凌晨 1 点。原因几乎都是应用服务器和 MySQL 的时区没对齐。Java 的 JVM 默认时区、MySQL 的 time_zone 参数、连接串里的 serverTimezone 三个地方只要有一个不一致,时间就会出现偏移。解决办法是在数据库连接串里强制指定时区,代码层面也在启动时固定时区。最简单的一套配置是在 JDBC 连接串加上serverTimezone=Asia/Shanghai,同时在 MySQL 执行SET GLOBAL time_zone = '+08:00'。这两个配置一起用,基本不会再出现偏移。注意前面代码用的是LocalTime.now(),它取的是 JVM 本地时区,所以 JVM 参数也要加-Duser.timezone=Asia/Shanghai,否则只改数据库侧不彻底。
5.2 跨天班次的“昨天 22:00 打卡算今天”判定
很多工厂或服务行业的班次是晚上 10 点到第二天早上 6 点。如果员工晚上 10 点打卡,按日期看是当天,但按班次看这应该算“下一个工作日”的上班卡。最容易翻车的地方在周六日:周五晚上 10 点的卡,到底算周五的加班还是周六的正常班?我个人的处理习惯是:先把打卡时间换算成“偏移时间”。班次表加一个 offset 字段,晚班设为负数,打卡时间减去 offset 后再判断属于哪一天。这样周五晚上 10 点的打卡,减掉偏移量后落到周六,系统就按周六班次处理。不建议用复杂的规则引擎,一个字段的事不要搞成大工程。
5.3 两个人同时在考勤机上登记,为什么丢了一条记录
当两个员工在同一秒提交打卡,后端同时执行了 select 判断有没有记录,两个请求都查不到,然后都去 insert,结果唯一索引生效时第二条插入失败。这种并发问题在课设阶段不容易遇到,但真实场景迟早会触发。解决的办法有两个层面。第一是依赖数据库的唯一索引,insert 前不查,直接尝试插入,捕获 DuplicateKeyException 再转成 update。SQL 里还有一种更优雅的写法,用INSERT ... ON DUPLICATE KEY UPDATE一句搞定,单条语句是原子的。第二种办法是对打卡接口加分布式锁,但为了一个打卡接口引入 Redis 显得头重脚轻。先把唯一索引和更新语句配合好,日常场景完全够用。
5.4 请假和打卡记录互相矛盾,月底对不上账
员工上午请了假,下午来公司打了个卡,月底统计时这天的状态既不是请假也不是正常出勤。根源在于请假表里存的是时间段,考勤表里存的是当天的一条记录,两个数据源没有交叉验证。我的做法是统计时不直接查 attendance_record,而是先查请假表,把请假时间段内的记录先标记掉,剩余时间再算出勤。在 SQL 里可以先把请假单 JOIN 到日历上,生成“哪些人哪天请假”的临时表,再用它去过滤考勤表。这个逻辑在写原型时就要画进审批流程图,否则代码里没预留这个开关,后面查数据全靠手动对账。
6. 把审批流加进去:让考勤登记系统从 Demo 变成能用的内部工具
整个系统如果只用打卡功能,本质上是一个带界面的数据库,难称管理系统。把它变成“工具”的转折点在于审批流。补卡申请、请假申请、加班确认这三类动作必须走审批。具体做法是给每张业务表都加上一个 status 字段,从攻坚到开发者,每个人看到的列表按状态过滤。申请提交后状态为 0,审批通过为 1,驳回为 2。这个订单对管理员相当友好,待办列表里一眼就能看到几条需要处理。
审批通过后的联动逻辑不能省。比如请假审批通过,得顺手把当天 attendance_record 的状态改成“请假”。这个联动我建议放在事务里做,保证状态修改和考勤更新要么一起成功,要么一起失败。不是不能拆成两步走,但半夜审批时一旦第二步异常没人会知道,第二天考勤数据就是脏的。事务加上后,代码里要处理一个问题:审批通过后重新计算考勤状态,如果员工当天既请了假又打了卡,优先算请假还是算出勤?业务上一般优先算请假,因为请假是主动申请并经过审批的。
最后把一个细节补上:系统里所有涉及状态变更的操作都写一行操作日志。用新建一张 log 表,记录操作人、操作时间、变更前状态、变更后状态。这个习惯我是在第一次上线考勤系统踩坑后养成的,当时有个人把一条已审批的请假单改成驳回,月末统计全乱,但没人知道是谁动的。现在只要发现数据不对,翻 log 表一眼就能定位。希望帮到你。
本文还有配套的精品资源,点击获取