尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
基于状态机与Spring Boot的校园请假管理信息系统设计与实现
简介一套面向校园场景的请假管理信息系统源码包由开发者黄娅于2016年创建适合管理信息系统方向的学生、开发者以及学校信息化管理者参考学习。压缩包共136个文件、约3.26MB涵盖C#源代码39个cs、窗体与资源文件resources/resx、界面图片jpg/png/gif、可执行与动态库exe/dll、数据库架构定义xsd以及解决方案/项目配置文件sln/csproj/config等完整呈现了该系统从UI界面到数据访问层的实现结构。系统围绕学生请假申请、教师审批、统计报表等核心功能展开将复杂的业务规则如请假期限、审批流程和多级通知固化到程序逻辑中同时通过数据库存储与权限控制保障数据安全。虽描述中提及功能尚未完全完善但作为实践案例可帮助读者梳理管理信息系统的需求分析、数据库设计、编码实现与测试部署全流程。已有1971人浏览学习适合用于课程设计、毕业设计或二次开发时的代码参考与结构借鉴。1. 校园请假管理信息系统到底在解决什么一张假条背后的审批链路校园请假管理信息系统真正的难点不在“提交请假”那个表单而在表单提交之后发生的一切。学生在手机端填完假条只是一条数据的诞生接下来这条数据要流经辅导员、学院、任课老师走审批、抄送、销假闭环。这些角色各自能看到什么、能操作什么、操作之后数据状态如何变化才是这个系统的灵魂。很多人第一次做这个项目把注意力全放在列表和表单上最后做出来的东西其实就是个信息登记簿审批还是靠线下吼一嗓子。这个系统适合两类人一是学校信息部门或外包团队需要把存量的请假流程数字化并接入教务数据二是想做完整业务闭环练手的开发者通过它理解状态机、权限、工作流和消息通知。我下面会按一套完整可落地的方案讲清楚从请假状态机设计、数据库建模到后端接口、前端对接再到部署后最容易踩坑的几个问题最后给两个从“能用”到“好用”的进阶功能。2. 先把数据模型立住请假状态机、审批链和三张核心表2.1 从提交到销假请假单的六个状态与转换规则第一步明确请假业务流程的关键路径。常见做法是学生提交请假的完整链路是草稿 → 待审批 → 审核通过/驳回 → 销假关闭。学生填写假条后可以先保存为 DRAFT草稿正式确认后变更为 PENDING待审批。审批一般至少两级辅导员先审核需要时再转学院或教务部门备案。审批不通过则变为 REJECTED已驳回学生可以修改重新提交也可以放弃。假条获批后学生返校需要销假销假完成则状态变为 CLOSED已销假整个流程闭环。假条在未审批前学生可以主动撤销状态变为 CANCELLED已撤销留档备查。围绕这个流程请假状态机包含六个状态状态值含义允许的下一步状态DRAFT草稿 / 未提交PENDING、CANCELLEDPENDING待审批正在审批链上APPROVED、REJECTED、CANCELLEDAPPROVED审批通过CLOSEDREJECTED审批驳回PENDING修改后重新提交CLOSED已销假无CANCELLED已撤销无这里有三个设计分歧值得说清楚。第一个是有没有必要保留 DRAFT很多初次实现者不做草稿箱学生点提交直接进入 PENDING返回去改只能靠驳回。如果目标是让学生端体验好一点DRAFT 是值得保留的。但要注意保存草稿不产生审批记录也不占用审批人的待办列表否则草稿满天飞审批人待办列表会被无关数据污染。第二个分歧是销假是否必须作为独立操作。不少简化版系统审批通过后就结束了学生返校不销假考勤数据对不上。为了让闭环闭合我一般会保留销假为独立操作而且只有 APPROVED 状态才允许销假销假必须记录实际返校时间。第三个分歧是多级审批是否做成可配置的工作流引擎。学生项目通常用 if-else 写死两级审批就能交差但实际校园场景里公假、调课、跨学院上课都会改变审批链。后面第 5 章会专门讲审批链配置化这里先保留状态机的规则。设计时要把状态转换逻辑放进代码而不是写成文档。状态转换拆成三步权限判断 → 状态比对 → 持久化更新。权限判断在前面拦截“谁能操作”状态比对保证“当前状态允许这个操作”持久化更新才真正改动数据。2.2 三张核心表结构请假单、审批记录、销假登记数据库选型上常见做法是直接用 MySQL 8如果现有环境是 5.7 也完全可以表结构以三张表为主线请假申请表leave_application、审批记录表leave_approval_record、销假登记表leave_cancel_record。以下是请假申请表的 DDLCREATE TABLE leave_application ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, student_id VARCHAR(32) NOT NULL COMMENT 学生学号关联学生基本表, student_name VARCHAR(64) NOT NULL COMMENT 学生姓名冗余字段减少联表, class_id VARCHAR(32) NOT NULL COMMENT 班级ID用于按班级维度统计, leave_type TINYINT NOT NULL COMMENT 请假类型1病假 2事假 3公假 4其他, start_time DATETIME NOT NULL COMMENT 请假开始时间, end_time DATETIME NOT NULL COMMENT 请假结束时间, leave_reason VARCHAR(500) DEFAULT NULL COMMENT 请假原因描述, attach_url VARCHAR(255) DEFAULT NULL COMMENT 附件路径病假单/证明材料, status VARCHAR(16) NOT NULL DEFAULT DRAFT COMMENT 状态DRAFT/PENDING/APPROVED/REJECTED/CLOSED/CANCELLED, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号用于防并发覆盖, create_by VARCHAR(32) NOT NULL COMMENT 创建人学号, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_status (status), KEY idx_student_start_time (student_id, start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT请假申请表;说明几个关键选型。student_id 与 student_name 同时存在是故意做冗余因为请假单的查询高频场景是“某学生的历史请假记录”和“某班级当天的请假汇总”冗余姓名可以避免每次 join 学生表。leave_type 用 TINYINT并落到代码枚举上不建议直接用字符串“病假”否则后期扩展请假类型时数据库和代码两边都要改。status 用 VARCHAR 存状态枚举值便于直接查看数据时阅读也有人用 TINYINT 映射枚举但我更推荐 VARCHAR因为运维排错时WHERE statusAPPROVED比查数字码表直观得多。审批记录表的 DDLCREATE TABLE leave_approval_record ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, leave_id BIGINT NOT NULL COMMENT 请假单ID关联 leave_application.id, approver_id VARCHAR(32) NOT NULL COMMENT 审批人工号, approver_name VARCHAR(64) NOT NULL COMMENT 审批人姓名, approver_role VARCHAR(32) NOT NULL COMMENT 审批人角色COUNSELOR辅导员COLLEGE学院教务ADMIN管理员, action VARCHAR(16) NOT NULL COMMENT 审批动作SUBMIT提交APPROVE通过REJECT驳回CANCEL撤销, comment VARCHAR(500) DEFAULT NULL COMMENT 审批意见, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 审批时间, PRIMARY KEY (id), KEY idx_leave_id (leave_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT请假审批记录表;这张表本质上是工作流日志表。我见过有人只存最新审批状态、不存过程记录结果出了问题无法追溯是谁批的、批了多久。审批历史完整落表是审计排查的基础。action 字段的 SUBMIT 动作也有用处提交时也写一条审批记录方便完整还原请假单的生命周期。销假登记表的 DDLCREATE TABLE leave_cancel_record ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, leave_id BIGINT NOT NULL COMMENT 请假单ID, student_id VARCHAR(32) NOT NULL COMMENT 学生学号, cancel_time DATETIME NOT NULL COMMENT 实际销假时间/返校时间, cancel_type TINYINT NOT NULL DEFAULT 1 COMMENT 销假类型1按时销假 2提前销假 3逾期销假, remark VARCHAR(255) DEFAULT NULL COMMENT 备注例如逾期原因, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 记录创建时间, PRIMARY KEY (id), UNIQUE KEY uk_leave_id (leave_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT销假登记表;uk_leave_id 唯一约束保证一张请假单只能销假登记一次销假记录与请假单是一对一关系。统计“学生是否按时销假”直接拿请假单的 end_time 与销假单的 cancel_time 对比即可。三张表完成闭环请假单管流程审批记录管链路销假登记管闭环。有人还想把请假单和课表绑定加一张课表快照表这个等第 6 章讲课时冲突检测时再展开。2.3 请假时长计算按自然日算还是按课节算请假时长是数据模型里最容易忽略的细节。学生请假一般表述为“周五下午到周一上午”但课程是按节次排的如果只存 start_time 和 end_time后续判断“是否与上课时间冲突”时就缺了课程维度语义。所以我会在请假单上额外留两个字段period_start和period_end分别表示请假起始课节与结束课节用 TINYINT 存课节序号。它与 start_time / end_time 配合使用start_time / end_time 用于日历展示和时长统计period 字段用于课表冲突检测。这一设计在后面的课时冲突自动检测里会体现价值。另一个实际约束是请假跨度需要被限定比如“单次请假最多不超过 14 天”超长请假必须走线下材料审批。在学校校验环节做这个限制比在数据库中做限制好因为跨天、跨月的计算逻辑放在代码里维护更方便。与此相关的法定节假日与调休问题直接影响时长计算是否正确我把它归到第 5 章的避坑清单里。3. 用 Spring Boot 把请假后端跑起来提交、审批、销假三个核心接口3.1 项目结构与核心依赖选型后端选型上Spring Boot MyBatis-Plus 是校园项目里最常见的组合。Spring Boot 生态成熟MyBatis-Plus 在 CRUD、分页、乐观锁插件上能省不少样板代码。如果你是 Python 背景用 FastAPI SQLAlchemy 也能实现同样的逻辑下面的代码重点是逻辑而不是框架语法。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-extension/artifactId version3.5.5/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency /dependenciesMyBatis-Plus 3.5.5 这个版本相对稳定支持逻辑删除和乐观锁插件后面审批接口会用到乐观锁。引入 Lombok 只是为了减少实体类 getter/setter 样板代码不引入也完全没问题看团队习惯。项目结构上按 controller → service → mapper 分层这里不赘述重点是三个接口的写法。3.2 请假提交接口状态初始化与时长校验Controller 层PostMapping(/api/leave/submit) public ResultLong submitLeave(RequestBody Valid LeaveSubmitDTO dto, RequestAttribute(userId) String userId) { // userId 由登录过滤器注入真正的身份以服务端为准不能信任前端传值 LeaveApplication application leaveService.createAndSubmit(dto, userId); return Result.success(application.getId()); }RequestAttribute(userId) 是从登录过滤器里取得当前用户身份。不少实现里把 studentId 放在前端传参这是巨大的越权漏洞第 5 章会细说。Valid 触发 DTO 参数校验比如 start_time 不能为空、请假类型必须在枚举范围内等。Service 实现Transactional public LeaveApplication createAndSubmit(LeaveSubmitDTO dto, String userId) { LeaveApplication entity new LeaveApplication(); BeanUtils.copyProperties(dto, entity); entity.setStudentId(userId); entity.setStatus(LeaveStatusEnum.PENDING.getCode()); entity.setVersion(0); // 校验请假开始时间不能晚于结束时间 if (entity.getStartTime().isAfter(entity.getEndTime())) { throw new BizException(请假开始时间不能晚于结束时间); } // 校验单次请假最大天数不超过 14 天 long days Duration.between(entity.getStartTime(), entity.getEndTime()).toDays(); if (days 14) { throw new BizException(单次请假不能超过14天超长请假请线下提交材料); } leaveApplicationMapper.insert(entity); // 写入提交动作的审批记录便于后续追溯完整生命周期 LeaveApprovalRecord record new LeaveApprovalRecord(); record.setLeaveId(entity.getId()); record.setApproverId(userId); record.setApproverRole(STUDENT); record.setAction(SUBMIT); approvalRecordMapper.insert(record); return entity; }这段代码三个关键点。第一Transactional 保证主单数据与审批记录写入的一致性第二时间校验用 Duration而不是直接把两个 Date 相减避免跨月计算错误第三entity.setStatus 直接设为 PENDING这里跳过了 DRAFT 状态是为了简化“一步提交”流程如果要做草稿箱应拆成 saveDraft 和 submit 两个方法草稿不写审批记录只有 submit 才写。3.3 审批接口状态比对、权限校验、审批留痕缺一不可审批接口是工作流系统的核心。很多入门项目只做一件事更新 leave_application 的 status。形式上流程能走通但权限和留痕都缺失。正确的做法是Transactional public void approve(Long leaveId, String approverId, String comment) { LeaveApplication leave leaveApplicationMapper.selectById(leaveId); if (leave null) { throw new BizException(请假单不存在); } // 1. 状态比对只有待审批状态可以继续流转 if (!LeaveStatusEnum.PENDING.getCode().equals(leave.getStatus())) { throw new BizException(当前状态不允许审批操作); } // 2. 权限校验审批人必须在该请假单的审批链上 if (!approvalChainService.canApprove(leave, approverId)) { throw new BizException(当前用户不在该请假单的审批链上); } // 3. 更新主单状态乐观锁版本号更新见 3.4 leave.setStatus(LeaveStatusEnum.APPROVED.getCode()); leave.setUpdateTime(LocalDateTime.now()); leaveApplicationMapper.updateById(leave); // 4. 写审批记录 LeaveApprovalRecord record new LeaveApprovalRecord(); record.setLeaveId(leaveId); record.setApproverId(approverId); record.setApproverRole(COUNSELOR); record.setAction(APPROVE); record.setComment(comment); approvalRecordMapper.insert(record); }把“审批人必须在审批链上”单独隔离到 approvalChainService核心原因是审批链可能会变辅导员调岗、学院秘书轮换解耦后改动时不影响审批主流程。审批记录这一步不可省略只更新状态不写记录事后没法排查。这里有一个进阶问题为什么不能只做“先查询再更新”因为如果两个审批人同时审批同一张单子两个线程都读到 statusPENDING先后执行 update状态虽然都是 APPROVED但审批记录会写入两条。乐观锁的做法在 3.4 展开。3.4 销假接口返校时间登记与逾期判断销假接口是闭环的最后一环容易被做成“把状态改成 CLOSED”就完事但返校时间和是否逾期都没记录。Transactional public void cancelLeave(Long leaveId, String userId) { LeaveApplication leave leaveApplicationMapper.selectById(leaveId); if (leave null || !leave.getStudentId().equals(userId)) { throw new BizException(请假单不存在或无权操作); } if (!LeaveStatusEnum.APPROVED.getCode().equals(leave.getStatus())) { throw new BizException(只有审批通过的假单才能销假); } LeaveCancelRecord cancel new LeaveCancelRecord(); cancel.setLeaveId(leaveId); cancel.setStudentId(userId); cancel.setCancelTime(LocalDateTime.now()); // 判断是否逾期销假实际销假时间晚于请假结束时间 if (cancel.getCancelTime().isAfter(leave.getEndTime())) { cancel.setCancelType(3); // 逾期销假 } else { cancel.setCancelType(1); } leaveCancelRecordMapper.insert(cancel); leave.setStatus(LeaveStatusEnum.CLOSED.getCode()); leaveApplicationMapper.updateById(leave); }销假操作先插入销假记录再更新主单状态这个顺序不是随意的。如果先更新主单再插销假记录第二步失败时主单已经是 CLOSED 但销假记录缺失后面统计“按时销假率”就会漏数据。虽然事务回滚能保证最终一致但把关键数据的写入顺序放在前面是运维上的好习惯。此外销假操作还得写一条审批记录action 为 CANCEL让审批链路也能看到销假这个动作。4. 前端与移动端对接Vue 学生端、审批工作台与消息触达4.1 学生端请假表单字段校验与最大可请天数控制前端选型用 Vue 3 Element Plus 比较普遍表单部分核心是时间范围选择器与校验规则。template el-form :modelleaveForm :rulesrules refformRef label-width100px el-form-item label请假类型 propleaveType el-select v-modelleaveForm.leaveType el-option label病假 value1 / el-option label事假 value2 / el-option label公假 value3 / el-option label其他 value4 / /el-select /el-form-item el-form-item label请假时间 proptimeRange el-date-picker v-modelleaveForm.timeRange typedatetimerange start-placeholder开始时间 end-placeholder结束时间 / /el-form-item el-form-item label请假事由 propreason el-input typetextarea v-modelleaveForm.reason maxlength500 / /el-form-item el-form-item el-button typeprimary clickhandleSubmit提交审批/el-button /el-form-item /el-form /template提交逻辑const handleSubmit async () { const [start, end] leaveForm.timeRange; const days Math.ceil((end.getTime() - start.getTime()) / 86400000); if (days 14) { ElMessage.error(单次请假不能超过14天); return; } await api.submitLeave({ leaveType: leaveForm.leaveType, startTime: formatDateTime(start), endTime: formatDateTime(end), reason: leaveForm.reason, }); ElMessage.success(已提交等待审批); router.push(/leave/list); };这里的经验是前端时间格式化统一用 dayjs 或工具函数不要手动拼字符串否则不同浏览器时间格式不一致后端解析时容易翻车。前端校验和后端校验必须一致而不是互相替代前端管体验后端管安全两层都要有。请假天数在前端算一遍后端还要再算一遍因为接口可以被绕过去。4.2 审批人工作台待办列表与批量审批审批人工作台按状态过滤是核心功能。后端提供待办查询接口前端做三个 tab待处理、已处理、全部。el-tabs v-modelactiveTab el-tab-pane label待审批 namepending el-table :datapendingList el-table-column propstudentName label学生 width90 / el-table-column propleaveTypeName label类型 width80 / el-table-column propstartTime label开始时间 width150 / el-table-column propendTime label结束时间 width150 / el-table-column label操作 width160 template #default{ row } el-button typesuccess clickapprove(row, true)通过/el-button el-button typedanger clickapprove(row, false)驳回/el-button /template /el-table-column /el-table /el-tab-pane el-tab-pane label已处理 namehandled !-- 已处理列表仅展示不允许操作 -- /el-tab-pane el-tab-pane label全部 nameall !-- 无条件分页列表通常带时间范围过滤 -- /el-tab-pane /el-tabs批量审批的接口设计建议是 POST /api/leave/batch-approve传入 leaveIds 数组和统一意见。批量场景在节假日前后出现高峰学生集中申请辅导员要逐个点会崩溃。批量接口内部仍是循环调用单条审批方法但把异常处置改为“单条失败不阻断其他条目”。实现时要注意事务边界不要在批量的外层再加一个大事务否则一条数据异常会导致整个批次回滚前端也没法知道哪几条成功了。通常的做法是单条审批方法内部带事务批量方法在循环外捕获异常并汇总结果。4.3 审批结果通知模板消息与失败重试审批通过或驳回后如何通知学生常见做法有三种站内信轮询、WebSocket 推送、微信服务号模板消息。校园场景里最可靠的是“微信模板消息 站内信兜底”。微信模板消息涉及 openid 绑定流程发送部分的伪代码public void sendTemplateMessage(String openid, String templateId, MapString, String data) { MapString, Object body new HashMap(); body.put(touser, openid); body.put(template_id, templateId); body.put(data, buildData(data)); // 调用微信接口发送 String result restTemplate.postForObject(apiUrl, body, String.class); if (result.contains(\errcode\:0)) { // 发送成功 } else { // 发送失败写入消息发送失败表由定时任务重试 messageFailMapper.insert(failRecord); } }核心注意点微信模板消息有频率限制不能作为唯一通知渠道模板接口偶尔超时发送失败写失败表比直接抛异常更合适定时任务每 10 分钟重试一次。站内信的实现思路更简单审批状态变更时往 message 表插入一条 UNREAD 记录学生端登录后拉取未读消息已读后更新状态。不要让学生端靠轮询请假单状态来判断那样既浪费接口资源又会在状态变更与消息展示之间出现时间差。5. 校园请假系统的五个高频坑越权、节假日、审批链断裂、销假超时、并发覆盖5.1 越权操作学生直接调接口把假单置为已通过现象某开发者在联调时用测试工具抓包发现审批接口没有任何身份校验把自己的假单编号直接调 approve 接口状态立刻变成 APPROVED。学生不需要经过辅导员自己就把假批了。原因前端把按钮隐藏了但接口层没有做校验。审批接口里approverId 直接从前端传参获取没有从登录会话中获取。解决服务端必须重新鉴权。审批接口从上下文取当前用户身份再匹配审批链关系不要信任前端传的 userId / approverId / studentId。// 错误写法示例前端可以传任意 approverId PostMapping(/api/leave/approve) public ResultVoid approve(RequestBody ApproveDTO dto) { leaveService.approve(dto.getLeaveId(), dto.getApproverId(), dto.getComment()); return Result.success(); } // 正确写法示例approverId 从会话上下文取 PostMapping(/api/leave/approve) public ResultVoid approve(RequestBody ApproveDTO dto, RequestAttribute(userId) String approverId) { leaveService.approve(dto.getLeaveId(), approverId, dto.getComment()); return Result.success(); }这个坑常被忽略因为前端体验一切正常接口被绕过时界面看不出来只有数据异常排查时才发现。安全建议是所有涉及“谁操作了哪条数据”的接口身份一律从会话上下文来而不是从请求体里取。5.2 请假时长按自然日算错法定节假日和调休怎么处理现象学生在国庆假期前请 9 月 30 日到 10 月 8 日的假10 月 1 日到 7 日是法定节假日学生在校时间实际只有 10 月 8 日但系统请假时长显示 9 天考勤数据完全对不上。原因直接用结束时间 - 开始时间除以一天计算没有排除周末、法定节假日和调休上班日。解决引入工作日历表而不是在代码里硬编码节假日。CREATE TABLE workday_calendar ( date DATE NOT NULL COMMENT 日期, day_type TINYINT NOT NULL COMMENT 1工作日 2周末 3法定节假日 4调休上班日, remark VARCHAR(100) DEFAULT NULL COMMENT 备注例如国庆调休, PRIMARY KEY (date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT工作日历表;请假时长计算只统计 day_type 为 1 和 4 的天数周末和法定节假日不计入请假时长调休上班日必须按工作日处理。每年年初教务系统会公布全年日历运维人员导入这张表。累计请假日数用一条 SQLSELECT COUNT(*) FROM workday_calendar WHERE date BETWEEN #{startDate} AND #{endDate} AND day_type IN (1, 4);这个方案比在代码里写死“排除周六周日”可靠因为节假日调休每年不同硬编码会积累成技术债。这里最容易被忽略的是“调休上班日”要算作工作日很多实现只识别了周六周日却把调休上班日也算作休息导致请假时长又被算少。5.3 辅导员调岗后审批链断裂假单卡死在待审批现象某辅导员调走后他名下学生的新请假单全部停在 PENDING系统没有任何提醒直到学生来问才发现线下审批已经耽误了好几天。原因审批人配置写死在单条数据里比如在 leave_application 表存了 approver_id人走之后没有自动重定向机制。解决审批链配置化。单独建一张班级与审批人的映射表把当前有效关系存起来。CREATE TABLE class_approver_config ( id BIGINT NOT NULL AUTO_INCREMENT, class_id VARCHAR(32) NOT NULL, approver_id VARCHAR(32) NOT NULL, effective_time DATETIME NOT NULL, invalid_time DATETIME DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT班级审批人配置表;查询请假单的当前审批人时只取 invalid_time 为 NULL 且 class_id 匹配的配置。辅导员调岗时把旧配置的 invalid_time 置为当前时间插入新配置已有请假单的审批记录不会变新请假单会自动流到新审批人。再加一个定时任务每小时扫描 PENDING 状态超过 24 小时的请假单向系统管理员推送预警防止再次出现“假单卡死无人管”的情况。5.4 销假超时没有提醒学生已返校但仍被记缺勤现象学生请假到期后没有手动销假系统默认按未返校处理考勤记录里出现“无故缺勤”。学生拿出已返校的凭证申诉引发纠纷。原因销假被设计成纯人工操作没有自动关单机制。很多学生返校后忘记线上销假。解决把“自动销假”作为兜底策略。到 end_time 后 24 小时系统自动生成销假记录cancel_type 标记为“自动销假”同时发送站内信提醒“若未实际返校请联系辅导员”。这样学生忘记销假数据也不会变成缺勤如果实际返校时间晚于 end_time再由人工改成逾期销假。实现自动销假时注意定时任务不要用“扫全表 更新”的粗暴方式要按 last_update_time 增量扫描或者维护一张待销假队列否则大表场景下会把数据库性能拖死。扫描间隔建议是每小时一次而不是每分钟一次避免无谓的压力。5.5 两个审批人同时点通过后写的把先写的覆盖现象一张请假单进入审批链第二环两个学院秘书同时打开审批页面都点了通过。结果两条审批记录都写入了主单状态虽然还是 APPROVED但一条假单被两个人批了流程上说不清楚。原因没有使用乐观锁审批接口里的“查询 → 更新”是两步操作两个事务同时读到旧的状态先后执行 update导致状态更新被后者覆盖审批记录却各写各的。解决在 update 语句中把 version 作为条件通过乐观锁控制并发。LeaveApplication updateEntity new LeaveApplication(); updateEntity.setId(leave.getId()); updateEntity.setStatus(LeaveStatusEnum.APPROVED.getCode()); updateEntity.setVersion(leave.getVersion() 1); int affected leaveApplicationMapper.update( updateEntity, new LambdaQueryWrapperLeaveApplication() .eq(LeaveApplication::getId, leave.getId()) .eq(LeaveApplication::getVersion, leave.getVersion()) ); if (affected 0) { throw new BizException(操作冲突该请假单已被其他人处理请刷新后重试); }受影响行数为 0说明当前版本已被其他事务修改事务回滚提示用户刷新后重试。注意 version 字段必须在每次 update 时递增且 update 条件必须包含 version 字段。如果 MyBatis-Plus 配置了乐观锁拦截器可以用 Version 注解自动完成但手写这个 SQL 更直观也方便排查问题。6. 从“能用”变“好用”课时冲突自动检测与请假统计报表6.1 课时冲突自动检测把课表接进请假流程很多请假系统的审批人真正头疼的不是“批不批”而是“批了之后学生错过的课怎么补”。如果系统能在提交阶段就提示“该假条覆盖了周三第 3-4 节的高数课”辅导员审批时心里就有数了。实现思路是引入课表快照表在请假提交时做一次课时冲突计算。核心逻辑是把请假时间段与课表时间段做区间重叠判断重叠超过一节课的时长就标记为冲突。筛选冲突课程的 SQL 示例SELECT course_name, teacher_name, weekday, period_start, period_end FROM course_schedule WHERE class_id #{classId} AND weekday BETWEEN DAYOFWEEK(#{startTime}) AND DAYOFWEEK(#{endTime}) AND period_start #{endPeriod} AND period_end #{startPeriod};这里用课节序号而不是时间点来判断可以避免上午最后一节和下午第一节被误判为连续冲突。比如周五下午第 5-6 节请假周一上午第 1-2 节上课时间上连续但课节序号没有重叠就不会误报。6.2 请假统计报表给辅导员和管理员的三个维度统计报表是请假系统最容易被忽略的价值点。给辅导员看班级维度汇总给学院教务看年级维度趋势给管理员看总体规则偏离预警。常用的三个报表按请假类型统计每个月的病假、事假、公假数量与占比用于分析学生请假的主要原因。按时销假率已销假数量 / 应销假数量低于某阈值的班级需要重点提醒。课时冲突统计本月冲突课程排前 10 的课程与老师帮助教务判断是否需要调整补课安排。报表的查询不建议在业务库直接跑大聚合可以从业务表定时汇总到一张统计中间表每天凌晨生成一次页面只查中间表避免影响请假主流程的性能。我做这类系统最后沉淀下来的习惯是把状态流转和权限校验全部放在服务端把体验优化放在前端不要做成“接口裸奔 前端保护”的半成品每次状态变更都留痕宁可多写一条记录也不要事后靠猜。这个思路在校园请假系统里适用换到其他审批类系统也一样。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

SAP MM模块常用数据库表清单:从主数据到物料凭证的实战解析

SAP MM模块常用数据库表清单:从主数据到物料凭证的实战解析

1. 为什么需要单独梳理一套“常用表清单”做SAP MM模块时间长了,你会发现一个现实:系统里几千张表,但日常碰来碰去的其实就那几十张。刚接手一个物料管理相关的报表、接口或者增强项目时,最花时间的往往不是写代码,而是…

📅 2026/10/20 7:24:36
Linux驱动开发中的进程管理:上下文、等待队列与并发保护

Linux驱动开发中的进程管理:上下文、等待队列与并发保护

写Linux驱动,写到后面你会发现一个很有意思的现象:明明学的是“设备驱动”,但真正决定你代码稳不稳定的,往往不是设备本身的寄存器操作,而是你对待进程的方式。驱动代码从不主动运行,它要么在某个进程的系统…

📅 2026/10/20 7:24:36
FLUX.2、即梦、Z-image、Qwen-Image、GLM-Image:五大文生图模型选型指南

FLUX.2、即梦、Z-image、Qwen-Image、GLM-Image:五大文生图模型选型指南

1. 五个模型摆在面前,到底该怎么选先把结论摆在前面:FLUX.2、Seedream(即梦)、Z-image、Qwen-Image、GLM-Image 这五个名字,本质上代表的是当前文生图领域五条不同的技术路线和产品思路。它们不是简单的“谁比谁强”&a…

📅 2026/10/20 7:24:36
MORE NEWS

更多资讯

📰

在银河麒麟V10 SP2上从零部署OpenClaw开源语音助手

1. 这项目到底是干嘛的,为什么值得装进一台银河麒麟V10 SP2"养虾之路"这四个字纯粹是我这个折腾系列的网络外号,跟水族养殖没有半点关系。每次在折腾群里发银河麒麟上的新玩法,总有人回一句"又在瞎折腾",一来…

📰

RocketMQ百万消息堆积实战复盘:从告警到根因定位与根治方案

晚上快十点,监控群里突然弹出一条告警:消费组 fulfillment-consumer 的积压消息数已经越过 50 万阈值,而且直线还在往上蹿。我打开控制台刷新了一下,累计堆积已经奔着 120 万去了。说实话,看到 RocketMQ 百万消息堆积这…

📰

MySQL字符集与校对规则全解析:从乱码根源到实战排查

搞数据库的人,十有八九都被“乱码”两个字折腾过。好好的中文,入库之后变成了“???”,或者读出来一团乱码;再或者emoji表情存进去直接报错,索引建出来跑了半个多小时结果还超长。这些问题绕来绕去,最后都…

📰

Token级奖励:提升LLM强化学习细粒度反馈的关键实践

训练大模型这半年,我最深的一个感受是:给模型发奖励和给学生打分一样,光给个总分,学生根本不知道错在哪。如今LLM强化学习(RL)里最值得玩的方向之一,就是token级奖励(token-level re…

📰

d3dx10_41.dll丢失的完整修复指南:DirectX运行库安装与排查

1. 先搞懂报错本质:d3dx10_41.dll在系统里到底扮演什么角色1.1 名字里藏着答案:d3dx10_41.dll的“出身”先别急着找下载,搞清楚这个文件是干什么的,能少走很多弯路。游戏启动时弹出的这个框,措辞一般是“无法启动此程序…

📰

GitHub模拟登录的七层验证与Playwright工业级实践

1. “GitHub模拟登录”不是个技术动作,而是一道认知分水岭很多人第一次听说“GitHub模拟登录”,脑子里立刻浮现出一个画面:写几行Python代码,填上账号密码,点一下就登进自己的GitHub主页——好像在复刻浏览器操作。但实…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬