简介:校园请假管理信息系统是一套面向学校场景的管理信息系统实践案例,适合学习管理信息系统的学生、课程设计开发者及需要搭建类似平台的管理人员参考。系统围绕学生请假申请、教师审批、数据统计等核心流程展开,涵盖用户界面、数据库管理、业务逻辑、多级审批、报表统计与安全隐私等关键模块,可帮助读者理解信息系统如何整合教育管理领域的数据并实现流程自动化。压缩包共136个文件,以39个cs源代码、14个resources资源文件、14个resx资源定义、12个jpg界面截图及dll、config、sln解决方案等为主,整体约3.26MB,包含源代码、数据库结构、配置文件与帮助文档,便于二次开发与实现细节研究。目前已有1973人学习下载。读者可从中获取需求分析、系统设计、编码实现到测试部署的完整参考,并借鉴其审批流程与统计报表设计思路,发现可优化改进之处。
1. 校园请假管理信息系统:从纸质假条到全流程数字化的方案起点
每到学期中段,辅导员桌上总会堆起一摞假条:病假、事假、公假混在一起,签字靠手写,统计靠翻本子,学生请假三天以上还得跑教务处盖章。这套流程在班级规模小的时候勉强能转,一旦涉及跨院系、多校区,审批链条一长,假条丢了、状态查不到、销假没人管的问题就全冒出来了。校园请假管理信息系统要解决的正是这件事——把申请、审批、销假、统计四个环节收进一个系统,让学生少跑腿、让审批留痕、让数据可查。这套方案适合正在做管理信息系统类毕设或课设的学生,也适合需要给内部流程做数字化改造的从业者参考。下面按需求、设计、实现、验收的顺序,把一份能直接照着搭的方案拆开讲。
2. 需求分析与技术选型:先把边界划清楚再动手
2.1 三类角色与核心业务闭环
做管理信息系统最容易翻车的地方,是一上来就画页面,结果做到一半发现角色权限没想清楚。请假系统的角色其实不复杂,但每个角色的动作边界必须提前定死。
学生端负责发起和跟踪:提交请假申请(填写起止时间、请假类型、事由、附件证明)、查看审批进度、发起销假、查询历史记录。辅导员端负责初审:看到的是自己班级的待办列表,可以同意、驳回、要求补充材料。院系管理员端负责终审和统计:处理超过一定天数的请假、导出班级或院系的请假汇总、维护请假类型字典。
业务闭环的关键在于状态流转。一份请假单从提交到归档,通常经历「待初审 → 待终审 → 已通过 / 已驳回 → 已销假」几个状态,其中「已通过」到「已销假」之间还有一个隐含的「请假中」。状态机设计得清楚,后面所有查询和统计才有依据。我一般会在需求阶段就把这张状态流转表画出来,和导师或需求方逐条确认,避免开发到后期才发现「驳回后能不能重新提交」这种细节没定义。
2.2 技术选型:为什么选这套组合
管理信息系统的技术选型不需要追新,稳定、可维护、资料多才是第一原则。这套方案采用前后端分离的 B/S 架构,后端用 Spring Boot + MyBatis-Plus,前端用 Vue 3 + Element Plus,数据库用 MySQL 8.0,缓存和会话管理用 Redis。
选 Spring Boot 的理由很实际:自动配置省掉了大量 XML,内置 Tomcat 让部署变成一个 jar 包的事,而且生态成熟,遇到问题检索资料多。MyBatis-Plus 在 MyBatis 基础上提供了通用 CRUD 和条件构造器,请假单这种以单表操作为主的业务,用它能少写不少重复 SQL。前端选 Vue 3 的组合式 API,配合 Element Plus 的表格、表单、日期选择器组件,审批列表和申请表单基本可以快速搭出来。
数据库选 MySQL 是因为请假数据的关联关系清晰、事务要求明确,关系型数据库比文档型更合适。Redis 在这里主要做两件事:缓存请假类型字典这类低频变更数据,以及存储登录令牌,避免每次请求都查库。
| 层次 | 技术选型 | 选型理由 |
|---|---|---|
| 前端 | Vue 3 + Element Plus | 组件齐全,表单和表格开发效率高 |
| 后端 | Spring Boot 3 + MyBatis-Plus | 生态成熟,CRUD 开发量小 |
| 数据库 | MySQL 8.0 | 关系清晰,事务支持完善 |
| 缓存 | Redis | 字典缓存与令牌存储 |
| 部署 | Nginx + Jar 包 | 前后端分离部署,配置简单 |
提示:技术栈不必和这套完全一致,但角色权限、状态流转、审批留痕这三件事的设计思路是通用的,换语言也照搬得过去。
3. 总体架构与数据库设计:四层结构与五张核心表
3.1 分层架构与模块职责
系统按经典的四层结构组织:表现层、接口层、业务层、数据层。表现层是 Vue 页面,负责渲染和交互;接口层是 Controller,只做参数校验和结果封装,不写业务逻辑;业务层是 Service,承载状态流转、权限判断、审批规则;数据层是 Mapper,负责和 MySQL 打交道。
模块划分上,我一般拆成五个:用户与权限模块、请假申请模块、审批流转模块、销假管理模块、统计报表模块。用户与权限模块管登录、角色、班级归属;请假申请模块管表单提交和附件上传;审批流转模块管待办列表和状态变更;销假管理模块管请假结束后的核销;统计报表模块管按班级、院系、时间段的汇总导出。模块之间通过 Service 接口调用,不跨层直接访问 Mapper,这样后期加一个「请假超时提醒」功能时,改动范围可控。
3.2 核心数据表设计
数据库设计是这套系统的地基。五张核心表分别是用户表、请假单表、审批记录表、请假类型表和销假记录表。这里重点说请假单表和审批记录表的设计取舍。
请假单表存的是「当前状态」,审批记录表存的是「状态变更历史」。很多人图省事只在请假单表里放一个审批人字段,结果一旦出现驳回后重新提交,历史就丢了。分开存的好处是每一次审批动作都有独立记录,答辩时被问到「怎么证明审批留痕」可以直接拿表结构说话。
-- 请假单表:一条记录对应一次请假申请 CREATE TABLE leave_application ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL COMMENT '申请学生ID', leave_type_id BIGINT NOT NULL COMMENT '请假类型ID', start_time DATETIME NOT NULL COMMENT '请假开始时间', end_time DATETIME NOT NULL COMMENT '请假结束时间', reason VARCHAR(500) NOT NULL COMMENT '请假事由', attachment VARCHAR(255) DEFAULT NULL COMMENT '证明材料路径', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待初审 1待终审 2已通过 3已驳回 4已销假', current_approver_id BIGINT DEFAULT NULL COMMENT '当前待办审批人', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_student (student_id), INDEX idx_status (status) ) COMMENT '请假单表'; -- 审批记录表:每次审批动作一条,用于留痕和追溯 CREATE TABLE approval_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, application_id BIGINT NOT NULL COMMENT '关联请假单ID', approver_id BIGINT NOT NULL COMMENT '审批人ID', action TINYINT NOT NULL COMMENT '1同意 2驳回 3要求补充', comment VARCHAR(300) DEFAULT NULL COMMENT '审批意见', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_application (application_id) ) COMMENT '审批记录表';请假单表的status字段用数字编码而不是字符串,是为了索引效率和状态机判断方便;current_approver_id冗余存当前审批人,让「我的待办」查询不用联表也能快速命中。审批记录表只增不改,action字段区分同意、驳回、要求补充三种动作,comment存审批意见。两张表通过application_id关联,查一份请假单的完整轨迹就是按时间排序查审批记录。
注意:
status的编码含义要写进注释并和前端枚举保持一致,我见过前后端对「2」的理解不一致导致状态显示错乱的案例,这种问题排查起来很费时间。
4. 核心模块实现:状态流转、权限拦截与统计导出
4.1 请假申请与状态流转的实现
状态流转是这套系统最核心的逻辑,也是最容易写乱的地方。我的做法是把状态变更收敛到一个 Service 方法里,所有审批动作都走它,避免在多个 Controller 里各写一套判断。
@Service public class LeaveFlowService { @Resource private LeaveApplicationMapper applicationMapper; @Resource private ApprovalRecordMapper recordMapper; /** * 审批动作统一入口 * @param applicationId 请假单ID * @param approverId 审批人ID * @param action 1同意 2驳回 3要求补充 * @param comment 审批意见 */ @Transactional(rollbackFor = Exception.class) public void approve(Long applicationId, Long approverId, Integer action, String comment) { LeaveApplication app = applicationMapper.selectById(applicationId); if (app == null) { throw new BizException("请假单不存在"); } // 只有当前待办审批人才能操作,防止越权 if (!approverId.equals(app.getCurrentApproverId())) { throw new BizException("无权审批该请假单"); } // 根据当前状态和动作计算下一状态 int nextStatus = calcNextStatus(app.getStatus(), action); app.setStatus(nextStatus); // 同意且进入终审时,切换审批人;终审通过则清空待办人 if (action == 1 && app.getStatus() == 0) { app.setCurrentApproverId(findDeptAdmin(app.getStudentId())); } else if (action == 1 && app.getStatus() == 1) { app.setCurrentApproverId(null); } applicationMapper.updateById(app); // 写入审批留痕 ApprovalRecord record = new ApprovalRecord(); record.setApplicationId(applicationId); record.setApproverId(approverId); record.setAction(action); record.setComment(comment); recordMapper.insert(record); } private int calcNextStatus(int current, int action) { if (action == 2) return 3; // 驳回 if (action == 3) return current; // 要求补充,状态不变 return current == 0 ? 1 : 2; // 同意:初审过→待终审,终审过→已通过 } }这段代码的关键点有三个。第一,@Transactional保证状态更新和留痕写入要么都成功要么都回滚,不会出现「状态改了但记录没写」的黑匣子情况。第二,越权判断放在最前面,只有current_approver_id匹配的人才能操作,这是权限控制的第一道闸。第三,calcNextStatus把状态计算抽成独立方法,后续如果要加「销假」状态,只改这一处即可。参数上,action用 1/2/3 编码而不是字符串,和数据库设计保持一致,前端传参时做一次映射。
4.2 基于拦截器的权限控制
权限控制分两层:接口层用拦截器校验登录态和角色,业务层用上面的越权判断兜底。拦截器方案比在每个方法里写注解更集中,适合这种角色固定的系统。
@Component public class AuthInterceptor implements HandlerInterceptor { @Resource private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains("/auth/login")) { return true; } String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { response.setStatus(401); return false; } // 从 Redis 取登录用户信息,取不到说明令牌过期 Object user = redisTemplate.opsForValue().get("login:" + token); if (user == null) { response.setStatus(401); return false; } // 将用户信息放入请求上下文,供后续业务读取 request.setAttribute("loginUser", user); return true; } }拦截器只做「有没有登录」的判断,角色级别的细粒度控制放在业务层,因为同一个接口不同角色能做的事不一样,比如审批接口学生调不了、统计接口辅导员只能看自己班级。令牌存 Redis 并设置过期时间,用户重新登录时刷新,这样既避免了 JWT 无法主动失效的问题,也让会话管理变得简单。参数上,Authorization头传令牌,login:前缀是 Redis key 的命名约定,方便后续按前缀清理。
4.3 统计报表的查询与导出
统计模块的需求通常集中在「按班级统计请假人次」「按类型统计占比」「按时间段导出明细」三类。实现上不追求复杂,用 MyBatis-Plus 的条件构造器拼查询,结果映射到 VO 返回前端,导出用 EasyExcel 写流。
public List<LeaveStatVO> statByClass(Long classId, LocalDate start, LocalDate end) { QueryWrapper<LeaveApplication> wrapper = new QueryWrapper<>(); wrapper.select("leave_type_id", "COUNT(*) AS cnt") .eq("status", 2) // 只统计已通过的 .ge("start_time", start.atStartOfDay()) .le("end_time", end.atTime(23, 59, 59)) .inSql("student_id", "SELECT id FROM sys_user WHERE class_id = " + classId) .groupBy("leave_type_id"); return applicationMapper.selectMaps(wrapper) .stream() .map(m -> new LeaveStatVO((Long) m.get("leave_type_id"), ((Number) m.get("cnt")).longValue())) .collect(Collectors.toList()); }这里用inSql子查询按班级过滤学生,避免了先查学生列表再拼IN条件的两次查询。status = 2只统计已通过的请假,驳回和待审的不计入,这是统计口径的问题,需求阶段就要和需求方确认清楚。导出时用 EasyExcel 的@ExcelProperty注解定义列名,前端拿到文件流直接下载,不需要后端生成临时文件。
提示:统计口径(算不算驳回、算不算跨天)一定要在需求文档里写死,否则验收时双方各执一词,返工成本很高。
5. 避坑与常见问题排查:五个最容易翻车的点
5.1 状态显示错乱:前后端枚举不一致
现象是学生看到「已通过」,辅导员后台却显示「待终审」。原因通常是前端自己维护了一套状态映射,后端改了编码没同步。解决办法是把状态枚举定义在后端,通过字典接口下发给前端,前端只做展示不做判断,所有状态判断逻辑收归后端。
5.2 审批越权:只校验登录不校验归属
现象是 A 辅导员能审批 B 班级的请假单。原因是拦截器只判断了「有没有登录」,没判断「这个人能不能审这张单」。解决是在业务层加归属校验,审批前比对current_approver_id和当前用户,统计查询时按角色拼接班级或院系过滤条件,不能只靠前端隐藏按钮。
5.3 时间边界问题:跨天请假统计漏算
现象是按天统计时,跨天请假只算了一天。原因是查询条件用start_time和end_time直接比较,没有考虑请假区间与统计区间的重叠。解决是把判断改成区间重叠逻辑:start_time <= 统计结束 AND end_time >= 统计开始,而不是要求请假完全落在统计区间内。
5.4 附件上传失败:路径与大小限制
现象是学生上传病假证明时提示失败,但小文件能传。原因是 Spring Boot 默认上传大小限制较小,且附件存储路径没做目录隔离。解决是在配置文件里调大spring.servlet.multipart.max-file-size,存储路径按「年份/月份/学号」分目录,文件名用 UUID 重命名避免中文和重名问题。
5.5 销假遗漏:通过后没有闭环
现象是请假单一直停在「已通过」,没人管销假,统计时把早已返校的学生还算作请假中。原因是销假没有触发机制。解决是在请假结束时间到达后,通过定时任务把状态标记为「待销假」并提醒学生,学生确认返校后置为「已销假」,超过一定时间未销假的在统计报表里单独列出。
6. 验收与答辩:用一条完整链路证明系统能跑通
验收这件事,我一般不建议按页面逐个点,而是按「一条请假单的完整生命周期」走一遍,这样既覆盖了所有模块,又能暴露状态流转和权限的问题。具体做法是准备三个账号:一个学生、一个辅导员、一个院系管理员,然后按下面的链路操作并记录每一步的预期结果。
| 步骤 | 操作账号 | 操作内容 | 预期结果 |
|---|---|---|---|
| 1 | 学生 | 提交三天病假,上传证明 | 状态为待初审,辅导员待办出现该单 |
| 2 | 辅导员 | 驳回并填写意见 | 状态为已驳回,学生端可见驳回原因 |
| 3 | 学生 | 修改后重新提交 | 生成新单,历史记录保留 |
| 4 | 辅导员 | 同意 | 状态转为待终审,管理员待办出现 |
| 5 | 管理员 | 同意 | 状态为已通过,待办清空 |
| 6 | 学生 | 请假结束后销假 | 状态为已销假,统计口径正确 |
这条链路走完,答辩时被问到「状态怎么流转」「权限怎么控制」「留痕在哪看」,都可以直接打开页面和数据库表回答。答辩还有一个容易被忽略的点:提前准备好统计报表的截图和导出文件,因为评委往往对「数据能不能汇总」比「页面好不好看」更感兴趣。
血泪经验是,我第一次做这类系统时只测了正常流程,没测驳回后重新提交,结果答辩现场被要求演示驳回场景,状态直接卡住。从那以后我每次交付前都强制走一遍「驳回—重提—再审批」的异常链路,并把数据库里的审批记录截图留档。希望这套结构和验收方法能帮到你,照着搭一遍,比看十篇概述都管用。
本文还有配套的精品资源,点击获取