学生请假系统源码:从本地跑通到多级审批改造实战指南

发布时间:2026/10/6 2:56:04
学生请假系统源码:从本地跑通到多级审批改造实战指南 简介面向需要快速搭建或二次开发学生请假管理系统的学习者与开发者此源码包以 Python 为主要实现语言配合 SQL 数据库脚本覆盖请假申请、审批流程、通知提醒、统计分析、权限管理等核心模块可作为课程设计、毕业设计或小型管理项目的直接参考。压缩包共 14 个文件其中 5 个 .py 源码文件承担业务逻辑与交互8 个 .pyc 为编译生成的字节码文件1 个 .sql 提供数据表结构整体仅 21KB轻量易部署便于快速阅读代码结构和本地运行调试。目前已有 1117 人学习浏览具备一定参考价值。借助源码中各模块的职责划分可理解学生信息维护、请假数据写入与统计、审批状态更新等具体实现SQL 初始化脚本则方便重建数据库适合用于掌握请假系统从数据模型到业务接口的完整技术链路。1. 学生请假系统源码怎么选先看这套单体项目为什么撑得住课设学校里的请假系统源码常被当成课程设计和毕业设计素材但大多数人下载后第一感觉是「跑不起来」——缺 SQL、缺 jar、缺配置。这个标题挂在「源码」下说明它属于那种拿到手要自己动手的项目。我的建议是先别纠结功能炫不炫先保证它能在你电脑上启动、提交一次请假能看到待审批、审批完能回到销假闭环。这篇我按一套常见的 Spring Boot 单体项目路径来讲——适合学生做的请假管理系统方案选择上不堆微服务不拆分前后端把精力留在业务逻辑上。人群定位是正在做课设/毕设、想快速落地的同学。2. 请假系统源码本地跑通的最小路径解压、建库、启动三步走一个能称为「源码」的请假系统项目通常长这样前端页面、Controller、Service、Mapper、SQL 脚本打包在一个压缩包里。拿到手第一件事不是读代码而是先把它跑起来。跑不起来后面全是纸上谈兵。2.1 环境匹配JDK、MySQL、Maven 三件套的版本对齐这套源码最常见的底座是 Spring Boot MyBatis或 MyBatis Plus MySQL。版本对齐是第一个坑JDK 版本差太多Maven 依赖可能直接解析失败MySQL 5.7 和 8.0 的驱动行为和连接串写法有差异。我一般按下面这个组合对齐兼容性最稳组件推荐版本说明JDK8 或 11Spring Boot 2.x 在 JDK 8 上最省心JDK 17 需要额外调整MySQL5.7 或 8.08.0 必须显式写时区连接参数5.7 相对宽松Maven3.6.x3.8 对 mirror 配置更敏感容易从中央仓库拉包失败IDEIntelliJ IDEA社区版够用导入时选 Maven 工程项目解压后先看根目录下有没有pom.xml。有pom.xml就是 Maven 工程用 IDEA 的Open直接选这个文件所在目录等右下角依赖索引转完。如果pom.xml里 spring-boot-starter-parent 版本是 2.xJDK 8 肯定没问题如果是 3.xJDK 17 起步别用 8 硬跑。还有一类源码不是 Maven 工程而是直接把整个 IDEA 工程目录打了包里面能看到.idea或.iml文件。这种更简单IDEA 直接 Open 目录但要等它自动识别 SDK。识别后立刻检查File - Project Structure - Project SDK选本地装的 JDK 8别选No SDK。2.2 初始化数据库建库建表脚本和三类核心表请假系统的数据模型翻来覆去就是三类核心表用户与角色、请假单、审批记录。源码包里通常带一个leave_system.sql直接导入即可。如果没有现成脚本按下面的精简结构也能把一套最小系统撑起来CREATE DATABASE leave_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE leave_system; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), role TINYINT NOT NULL DEFAULT 2 COMMENT 1管理员 2学生 3辅导员 4系主任 ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, class_name VARCHAR(50), grade_year VARCHAR(10), phone VARCHAR(20) ); CREATE TABLE leave_request ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 申请人, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, leave_type TINYINT COMMENT 1事假 2病假 3其他, reason VARCHAR(500), status TINYINT NOT NULL DEFAULT 1 COMMENT 1待审批 2已通过 3已驳回 4已销假 5已撤销, current_approver_role TINYINT DEFAULT 3 COMMENT 当前审批角色, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE leave_approval ( id INT PRIMARY KEY AUTO_INCREMENT, leave_id INT NOT NULL, approver_id INT NOT NULL, approve_role TINYINT, action TINYINT COMMENT 1通过 2驳回, comment VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );导入命令是mysql -u root -p leave_system.sql如果源码提供的 SQL 文件里有DROP TABLE IF EXISTS直接执行没问题没有的话先把旧库手动删掉避免字段冲突。注意角色字段我建成了TINYINT而不是VARCHAR。后面写权限注解时hasRole(TEACHER)这种写法在数据层会转成数字比较。排序、索引、脏数据控制都比字符串好这也是为什么很多源码会用role数字当唯一标识、再用一张sys_role表做映射。导入成功后用SHOW TABLES;确认核心表都在再顺手执行SELECT COUNT(*) FROM sys_user;有数据说明初始账号脚本也带进来了。默认账号一般在README.md或源码包的doc目录里常见组合是admin/admin123和student/123456。找不到就自己往sys_user插两条测试数据别卡在这一步。2.3 改配置启动application.yml 里的四个必调项项目能不能连上数据库全看src/main/resources/application.yml或application.properties。这里我见过太多翻车案例有人只改了密码没改库名有人走了 localhost 但 MySQL 跑在 3307还有人因漏了时区参数直接报 SQL 异常。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/leave_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这个文件里四个必调项第一server.port别用 80Mac 上 8080 也可能被 AirPlay 占用直接换成 8081 更省事第二url里的serverTimezoneAsia/ShanghaiMySQL 8.0 的驱动不写这一项会报 CST 时区错误第三password改成你自己本地 MySQL 的密码第四mapper-locations如果你的源码把 XML 放在别的目录这里路径不对会报Invalid bound statement。启动命令两种方式任选。IDEA 里直接右键Application主类运行控制台看到Tomcat started on port(s): 8080就算成功。命令行方式是mvn spring-boot:run第一次执行会下一大堆依赖需要几分钟。启动后打开浏览器访问http://localhost:8080/login能看到登录页就说明前后端资源都在。如果没有登录页而是 404多半是spring.thymeleaf.prefix配置不对或者templates目录被 IDE 标记成了资源文件却没被识别。验证登录接口时的 curl 命令curl -X POST http://localhost:8080/user/login \ -H Content-Type: application/x-www-form-urlencoded \ -d usernameadminpasswordadmin123返回 JSON 里带 token 或跳转地址说明数据库连接、账号校验、会话管理这条链路全部通。到这一步项目已经在本地活了后面才是真正理解它和改造它。3. 请假流程的状态机与时间冲突检测把审批做成闭环跑通之后很多同学会开始改页面、改样式但请假系统的核心不在页面上在状态流转。学生提交请假单辅导员审批通过了要能销假销假后整个流程才算回收。这块逻辑写不好表面是功能缺失实则是状态机的边界没定义清楚。3.1 status 用 int 不用 varchar 的取舍与四类状态码我见过不少源码把状态直接存成字符串pending、approved、rejected。新手觉得可读性好实际索引效率低、容易写岔。比如有人在代码里写if (APPROVED.equals(status))而数据库里存的是approved大小写对不上永远匹配不到。推荐做法是用int状态码加一个枚举类管理public enum LeaveStatus { PENDING(1, 待审批), APPROVED(2, 已通过), REJECTED(3, 已驳回), COMPLETED(4, 已销假), CANCELED(5, 已撤销); private final int code; private final String desc; LeaveStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } }这个枚举在 Service 层直接当判断条件用比如更新状态时不再写setStatus(2)而是setStatus(LeaveStatus.APPROVED.getCode())。好处是代码里所有的魔法数字都有名字findByStatus的 SQL 参数也不会传错。如果源码里没有枚举只有一个Integer status那你至少把状态码注释写在实体类字段旁边维护的时候不至于靠猜。请假系统的状态机还有一个容易被忽略的细节销假不是终态。学生提前回来要销假status4到了截止时间没销假系统可能要自动标记逾期。这要在设计状态码时预留扩展位置。3.2 请假申请接口的时间校验重叠区间与提前天数学生请假最容易出错的时间场景是两个一是请假的起止时间重叠二是已经提交的申请还没审批完又提交新的。一个学生在同一时间段不可能既在教室上课又躺在医院打点滴所以提交接口必须做重叠检测。Mapper public interface LeaveRequestMapper { Select( SELECT COUNT(*) FROM leave_request WHERE user_id #{userId} AND status IN (1, 2) AND start_time #{endTime} AND end_time #{startTime} ) int countOverlapping(Param(userId) Long userId, Param(startTime) LocalDateTime startTime, Param(endTime) LocalDateTime endTime); }这段 SQL 用的是区间重叠判断的经典公式新开始时间 旧结束时间 AND 新结束时间 旧开始时间。两个区间只要存在交集这个条件必然成立。注意边界情况如果学生刚好前一天 23:00 请假第二天 01:00 结束而新请假单从第二天 01:00 开始由于用的是严格小于和大于endTime startTime时不会判重这是合理的——前一段刚结束后一段无缝开始。除了重叠检测还要校验请假开始时间不能在历史时间。常见做法是if (startTime.isBefore(LocalDateTime.now().plusHours(1))) { throw new BizException(请假开始时间需早于当前时间至少1小时); }这里加plusHours(1)是给审批留缓冲防止学生把昨天的事补成请假。具体提前多少小时看学校制度有的辅导员要求当天 18:00 前提交那就写成LocalDate.now().atTime(18, 0)来判断。3.3 销假与超期自动归档定时任务怎么写审批通过不代表流程结束。学生返校后要销假辅导员可能还会批复一个「缺课学时」的登记。这个动作在源码实现里通常是一个新的POST /leave/cancel接口加上一个定时任务去处理所有「到期未销假」的单子。Service public class LeaveTimeoutJob { Scheduled(cron 0 0 2 * * ?) public void autoArchiveExpiredLeaves() { ListLeaveRequest expiredList leaveRequestMapper .findApprovedAndExpired(LocalDateTime.now()); for (LeaveRequest leave : expiredList) { leave.setStatus(LeaveStatus.COMPLETED.getCode()); leave.setRemark(超过请假截止时间未销假系统自动归档); leaveRequestMapper.updateById(leave); } } }定时任务这一段先看启动类上有没有EnableScheduling。没有这个注解Scheduled完全不执行这是新手最容易漏的。cron 0 0 2 * * ?表示每天凌晨 2 点跑一次选这个时间是因为学生基本不在操作、数据库压力小。如果你在测试时不想等到凌晨可以临时改成Scheduled(fixedDelay 60000)每 60 秒跑一次验证完再改回去。4. 请假系统的统计与联动模块报表口径、课程表与通知开关跑通基本流程后系统的价值开始体现在统计和联动上。一个只能提交和审批的请假系统撑死了算课设及格但如果你把「请假时长统计」「课表冲突检测」「通知触达」做出来答辩时老师问的东西就都在射程内。4.1 请假天数的三种统计口径自然日、工作日与课时「请假多少天」这个统计看似简单实际有三种口径不同场景要选用不同规则。按自然日算最简单DATEDIFF(end_time, start_time)就是天数但学生周五到周日请假按自然日是 3 天按工作日只有 1 天如果涉及考勤扣分辅导员要的是课时数。-- 按自然日统计 SELECT u.real_name, SUM(DATEDIFF(lr.end_time, lr.start_time)) AS total_days FROM leave_request lr JOIN sys_user u ON lr.user_id u.id WHERE lr.status IN (2, 4) AND lr.create_time BETWEEN #{startDate} AND #{endDate} GROUP BY u.real_name ORDER BY total_days DESC;工作日口径没法一条 SQL 搞定因为要排除周六周日甚至法定节假日。我的做法是在 Java 里循环日期判断星期几节假日数据另建一张sys_holiday表不写死规则。课时数则要 join 课程表取请假时间区间内落在course_schedule里的课程条数。源码里如果这三个统计都有注意看它是否处理了「请假结束时间小于开始时间」这种脏数据没处理的话统计结果会是负数这种 bug 在答辩时很容易被老师抓。4.2 课程表与销假自动解除冲突课程提示怎么实现学生请假系统如果接了课程表模块那新增请假单时要做的就不是简单的重叠检测而是要查这个时间区间内哪些课被影响。SELECT cs.course_name, cs.course_time FROM course_schedule cs JOIN student s ON cs.class_name s.class_name WHERE s.user_id #{userId} AND cs.course_time BETWEEN #{startTime} AND #{endTime} ORDER BY cs.course_time;这段查询的作用是在学生提交请假单时提前把冲突课程列出来前端弹一个确认框「以下课程将受影响是否继续提交」。注意BETWEEN的两个边界值在 MySQL 中是闭区间如果课程恰好从请假结束时间开始也会被查出来需要在业务层判断course_time endTime才提示。这种边界问题用前面第 3 章的重叠公式更稳。销假自动解除是另一个联动点。学生提交销假时系统要检查实际销假时间是否晚于end_time。晚于的话扣平时分、发消息给辅导员不晚于直接更新status4。这个逻辑不复杂但很多源码只在cancel_time字段里存一个时间就结束没有对比预设的end_time导致「超期归来」没有任何记录。4.3 站内信与邮件通知没有消息队列也能做得轻很多学生的源码一做到「通知」就想上 RabbitMQ、Kafka在课设项目里属于杀鸡用牛刀。请假系统的通知量级是每天几十条用一张消息表加一个定时扫描就够了。app: notify: enabled: true mail: false配置里给通知加开关mailfalse时只写站内信不发邮件。这样本地开发不用配 SMTP部署到服务器后想开邮箱提醒才配spring.mail.host。消息表的结构也很简单id, user_id, title, content, read_flag, create_time。审批通过、驳回、销假超期这三个事件各插一条记录。站内信的好处是能让「学生—导员—系主任」之间留痕。我有一次改这类系统发现源码的通知逻辑写在 Controller 里一个审批接口里插了三次消息SQL 和业务混在一起。后来我把通知抽成一个NotifyService事件触发处只调一个方法改动量小了很多。这个经验放在答辩里也说得出口单一职责降低耦合。5. 请假系统部署与二次开发避坑五条血泪经验这部分是从「能跑」到「扛得住改」的分水岭。下面五条都是我在类似源码上实际踩过的坑每条都按「现象 → 原因 → 解决」写清楚。5.1 数据库连接串删了 serverTimezone时间差 8 小时且报 CST 错误现象启动时报The server time zone value CST is unrecognized或者所有时间字段读出来比实际时间早 8 小时。原因MySQL 8.0 驱动要求连接串里显式指定时区而编写器的默认时区是 GMT跟中国时区正好差 8 小时。解决在jdbc:mysql://连接串末尾加上?serverTimezoneAsia/Shanghai同时把useSSLfalse也带上避免本地开发时 SSL 握手慢。如果源码里用的是application.properties写法是spring.datasource.urljdbc:mysql://localhost:3306/leave_system?serverTimezoneAsia/Shanghai注意符号在.properties里不需要转义但在application.yml里最好把整个 URL 用引号包起来。5.2 中文乱码出现在三个环节不是只改一处现象页面显示学生姓名是??或者插入数据库后SELECT查出来全是问号。原因字符集问题至少有三个作用点——数据库表默认字符集、JDBC 连接串里的characterEncoding、以及 HTTP 请求的编码过滤器。解决建库时用CREATE DATABASE ... CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci连接串加useUnicodetruecharacterEncodingutf8若项目是 Spring MVC在web.xml里配CharacterEncodingFilter。Spring Boot 项目不用配过滤器server.servlet.encoding.forcetrue可以兜底。改完这三处还乱码检查 MySQL 的my.cnf里[mysqld]段有没有character-set-serverutf8mb4。5.3 端口被占与 MySQL 连接数不足现象Tomcat started on port(s): 8080没出现控制台报Port 8080 was already in use。原因本地有其他进程占了端口。解决lsof -i :8080 kill -9 PIDMac 和 Linux 用lsofWindows 用netstat -ano | findstr 8080再用taskkill /PID PID /F。项目跑起来后如果偶尔报Too many connections是连接池参数太小在application.yml里给spring.datasource.hikari.maximum-pool-size调到 20默认 10 在课设多人同时测试时确实容易打满。5.4 MyBatis 返回 Map 时的时间格式化问题现象SQL 查出来create_time在 Java 中间层变成一串数字或者 JSON 返回给前端时变成2025-01-01T00:00:00这种不友好的格式。原因MyBatis 把DATETIME映射成java.time.LocalDateTime后序列化规则取决于jackson配置如果查询返回的是MapString, ObjectMyBatis 不管类型转换直接给你java.sql.Timestamp对象。解决实体类字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)用 Map 接收时SQL 里写DATE_FORMAT(lr.create_time, %Y-%m-%d %H:%i:%s) AS createTime。前一种方案保持对象型转换更干净后一种对临时报表字段更省事。5.5 从开发机拷到服务器只拷 jar 不够现象本地跑得好好的把target里的 jar 拷到服务器java -jar启动后访问页面报 500 或者数据库连不上。原因Spring Boot 的 jar 自带依赖但数据库连接配置默认读application.yml里面写的是localhost:3306服务器上 MySQL 密码也可能不同加上没有初始化表结构。解决用外部配置覆盖内置配置启动时执行java -jar leave-system.jar \ --spring.datasource.urljdbc:mysql://127.0.0.1:3306/leave_system?serverTimezoneAsia/Shanghai \ --spring.datasource.usernameroot \ --spring.datasource.passwordyour_pwd首次部署前先在服务器上把 SQL 脚本灌进去mysql -u root -p leave_system.sql。如果服务器上没装 MySQL先用docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD... mysql:8.0起一个容器再来连后端。上传时除了 jar顺手把leave_system.sql和一份application-prod.yml也传上去省得后面手动传参。6. 把请假系统源码改造成自己的毕设多级审批与权限细化的验证套路如果这套源码答辩时只能演示「学生提交→辅导员审批」深度不够。多数学校现在要求请假系统支持多级审批学生→辅导员→系主任→教务处每一级的意见都不一样。改造这件事比重新写一遍容易但比想象中容易翻车。6.1 单级审批改成多级审批在状态机里加一条审批链路单级审批的leave_request表里status是唯一判断依据。多级审批需要再加两个字段current_approver_role和approval_level。每次审批通过时判断当前层级是不是最后一环。public void approve(LeaveApproval approval) { LeaveRequest leave leaveRequestMapper.selectById(approval.getLeaveId()); if (leave.getStatus() ! LeaveStatus.PENDING.getCode()) { throw new BizException(该请假单当前状态不可审批); } int nextLevel leave.getApprovalLevel() 1; int nextRole findNextApproverRole(leave.getLeaveType(), nextLevel); if (nextRole 0) { leave.setStatus(LeaveStatus.APPROVED.getCode()); leave.setApprovalLevel(nextLevel - 1); } else { leave.setCurrentApproverRole(nextRole); leave.setApprovalLevel(nextLevel); } leaveRequestMapper.updateById(leave); }findNextApproverRole返回的是配置表里定义的下一级角色。没有下一级时返回 0整个请假单置为已通过。这里的核心边界是「学生撤销」和「层级审批」的互斥只要当前approval_level 1学生端就不能再撤销否则辅导员批完了学生一个撤销把整条链路作废数据就乱了。6.2 权限验证技巧用注解替代手写 if (role 1)很多源码的权限控制是下面这样写的Controller 里User user (User) session.getAttribute(user); if (user.getRole() 1) { ... }。这种写法在单用户角色下没问题但多级审批一上每个接口都要判断角色代码臃肿且容易漏判。建议引入 Spring Security 的方法级注解。在WebSecurityConfig里开启EnableGlobalMethodSecurity(prePostEnabled true)然后在接口上声明PreAuthorize(hasRole(COUNSELOR)) PostMapping(/leave/approve) public Result approve(RequestBody ApprovalDTO dto) { return leaveService.approve(dto); }测试时用四个人物账号分别验证管理员、学生、辅导员、系主任。下面这个用例表可以直接当答辩测试记录用例操作预期结果学生提交请假学生账号 POST /leave/apply生成待审批单待办通知到辅导员辅导员通过辅导员账号 POST /leave/approve状态流转到系主任待审批系主任驳回系主任账号 POST /leave/approve/reject状态变为已驳回通知学生学生重复提交同一时间段再次申请提示时间冲突提交被拦截已审批单据重复审批辅导员再次审批同一单提示当前状态不可审批做权限改造时我第一版曾图省事在 Controller 里继续写 if结果三个账号交替测试时发现漏了两处判断存在接口可以越权查别人的请假记录。后来老老实实画了一张角色-接口矩阵把「谁能查、谁能批、谁能销假」列清楚再动手。这种项目最忌讳上来就改代码状态机画不明白改一处崩三处。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询