SpringBoot高校学生请假管理系统:从流程设计到毕业设计实战全解析

发布时间:2026/10/11 8:37:51

SpringBoot高校学生请假管理系统:从流程设计到毕业设计实战全解析 每到学期末辅导员的办公桌上总有一沓叠得歪歪扭扭的请假条学生跑来跑去找人签字任课老师一头雾水不知道谁没来上课学委还得手动统计出勤情况。我在本科做课程设计时也经历过这个场景当时就想如果把这些流程搬到线上能省下多少精力。后来做毕业设计时我选了SpringBoot高校学生请假管理系统作为题目从技术选型到编码落地大约花了三周时间这里把我的完整思路、核心实现和踩过的坑都写出来希望能给正在准备毕业设计或者想练手SpringBoot实战项目的同学一些参考。这个项目本质上是一套面向高校场景的业务管理系统核心解决三件事学生请假申请的线上化提交与流转、辅导员的审批与考勤核验、以及全流程的数据留存与统计。它覆盖了三类角色涉及一条完整的业务链路非常契合SpringBoot的主流技术栈也足够撑起一篇结构完整的毕业设计论文。无论你是Java基础一般、想通过一个全栈项目巩固SpringBoot还是需要一套能直接运行、能讲清楚的毕设项目这篇文章的内容都适用。1. 为什么高校请假管理系统值得做成SpringBoot项目很多人一听请假管理系统就觉得太简单这不就是一个增删改查吗但实际上高校场景下的请假业务远比想象中复杂。请假不只是学生提交一个申请它涉及时间校验、审批层级、课程冲突、考勤联动等多个环节。而SpringBoot恰好是一个能把这些业务完整落地、又不会让初学者被复杂配置劝退的框架。1.1 业务复杂度决定了项目含金量和网上的博客系统商城系统这类通用项目不同请假管理系统的业务逻辑更贴近真实的管理场景。比如一个学生请假三天以内辅导员可以直接审批如果超过三天系统就要自动流转到系主任甚至学院教务办。再比如学生提交请假单之后如果这个时间段有课系统应该通知到对应的任课老师不然老师点名时会把请假的学生记为缺勤。这些规则不是单纯的前后端CRUD能覆盖的需要业务层面的状态机设计和流程控制而这些正是毕业设计答辩时老师最关注的系统设计部分。1.2 SpringBoot的技术特点与项目需求高度匹配SpringBoot的核心优势是自动配置和生态整合。这个项目用到的技术组件几乎都能在SpringBoot里找到现成的starter来整合MyBatis-Plus做数据持久化、Spring Security或JWT做权限认证、POI做Excel导出、Redis做缓存和登录Token管理。SpringBoot让这些技术整合变得非常流畅我只需要在pom文件里引入依赖写对应的配置类就能把整个项目串联起来。更关键的是SpringBoot的自动装配机制非常适合用来理解Java后端开发的现代工作方式。在开发这个系统的过程中我会频繁接触到SpringBootApplication、RestController、Service、Mapper这些注解也会接触到依赖注入IoC和面向切面AOP这些核心概念。可以说做完这个项目你会发现SpringBoot的很多基础问题都顺带弄明白了。1.3 就业和技术成长角度也值得做从求职面试的角度看SpringBoot是Java后端岗位的绝对核心技术栈。面试官很少关心你做过多少项目更关心你是否能讲清楚一个项目中为什么这样设计缓存怎么用的权限怎么控制的。请假管理系统虽然业务不算复杂但它麻雀虽小五脏俱全角色权限、流程审批、数据统计、消息通知这些点都可以作为面试中的谈资。我当年在面试中就被问到请假时间冲突你是怎么校验的多个层级审批的状态你是怎么设计的正是因为做过这个项目才能给出清晰的回答。2. 业务架构与功能模块拆解先理清楚角色和流程在动手写代码之前我花了不少时间画业务流程图和整理功能清单。这一步千万不要省很多同学拿到题目就开始建表结果做到一半发现审批流程串不起来返工成本非常高。请假系统的核心是流程想清楚流程数据表和接口其实就水到渠成了。2.1 角色与权限模型设计这个系统里我设计了四种角色每种角色的关注点完全不同角色核心诉求主要操作学生快速提交请假申请、查看审批进度提交申请、撤销申请、查看记录、上传佐证辅导员审核所带班级学生的请假请求审批待办、查看请假记录、登记考勤任课教师掌握所授课程学生的请假情况查看请假名单、确认出勤、反馈异常系统管理员维护系统数据、处理异常用户管理、学院班级管理、请假统计导出权限控制我采用的是基于角色的访问控制RBAC模型Spring Security框架天然支持这种设计。每个用户登录后通过角色决定他能访问哪些接口。比如学生不能调用审批接口辅导员不能跨班级查看学生信息这些都要在接口层面做拦截而不能只靠前端隐藏按钮。2.2 请假流程的完整闭环我设计的请假流程是申请-审批-销假三步闭环学生填写请假单选择请假类型病假、事假、公假、其他填写起止日期和请假事由上传证明材料如医院诊断书截图提交给辅导员。辅导员收到待办可以选择同意或驳回。如果请假天数超过设定阈值比如3天系统自动追加审批层级转给系主任处理。审批通过后学生的请假状态变为已准假系统将请假信息同步给该时间段有课的任课教师。学生返校后需销假确认请假时段实际结束系统将状态更新为已完成并归档。这里有一个很重要的设计点销假环节经常被忽略但实际业务中它很有价值。没有销假的话请了5天假但实际3天就回来上课了这类情况就无法追踪任课老师也无法确认学生是否真实返校。虽然很多毕设项目不要求做销假功能但把它加进去会让系统在答辩时明显更有说服力。2.3 考勤联动与数据闭环高校请假系统和考勤是天然关联的。我在实现请假功能的同时还做了一个考勤登记模块。任课教师可以选择某节课的日期系统展示该课程的学生名单教师可以逐个标记到课、迟到、请假或旷课。如果某个学生当天有审批通过的请假记录系统会自动在考勤表中将该学生的状态标记为请假教师无需手动处理。这个设计让学生请假数据从申请到认定形成了一个闭环。请假审批通过的记录直接进入考勤数据任课教师和辅导员查看考勤时能一目了然。这也是这个项目区别于普通CRUD系统的亮点所在。3. 技术选型与项目骨架搭建技术选型时我遵循一个原则优先选择面试中会问到的、生态成熟的主流技术。整个项目我采用的是当前非常经典的前后端分离模式前端使用Vue Element UI后端使用SpringBoot数据库使用MySQL缓存使用Redis。如果你的毕设要求不涉及前后端分离也可以直接用Thymeleaf做服务端渲染但前后端分离的方案在答辩时更能体现你对现代Web开发模式的理解。3.1 后端技术栈清单后端核心依赖如下!-- 核心框架 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 持久层MyBatis-Plus 简化CRUD开发 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 权限认证Spring Security JWT -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency !-- Redis缓存 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Excel导出Apache POI -- dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.3/version /dependency我特意选用了MyBatis-Plus而不是原生MyBatis因为MyBatis-Plus提供BaseMapper单表增删改查基本不用手写SQL开发效率提升非常明显。而且它自带的分页插件、逻辑删除、自动填充功能在这个项目里都能用到。3.2 项目分层与包结构项目包结构我按照常见的分层架构来组织这也是答辩时老师比较认可的标准写法com.example.leave/ ├── config/ # 配置类MyBatis-Plus分页、Redis、CORS跨域 ├── controller/ # 控制层接收请求返回统一结果 ├── service/ # 业务层核心业务逻辑实现 ├── mapper/ # 持久层数据库操作接口 ├── entity/ # 实体类与数据库表对应 ├── dto/ # 数据传输对象接口入参出参封装 ├── vo/ # 视图对象前端展示数据封装 ├── common/ # 通用类返回体、异常处理、常量定义 ├── security/ # 安全配置JWT过滤器、权限处理 ├── utils/ # 工具类日期、文件上传等 └── exception/ # 自定义异常与全局异常处理器分层的好处是各层职责清晰。Controller只负责参数校验和结果返回Service只做业务逻辑Mapper只做数据访问。如果答辩时老师问如果审批规则要改成所有请假都需系主任审批你改哪里你能直接回答改Service层的审批逻辑就可以了这种清晰的架构意识很加分。3.3 统一返回体与全局异常处理前后端分离项目里接口返回格式必须统一否则前端处理会非常难受。我定义了一个Result类所有接口都返回这个结构Data public class ResultT { private Integer code; // 状态码 200成功 400失败 401未认证 403无权限 private String message; // 提示信息 private T data; // 返回数据 public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(400); result.setMessage(message); return result; } }同时我还写了一个全局异常处理器使用RestControllerAdvice注解拦截所有Controller层抛出的异常。业务异常、参数校验异常、系统异常分别处理前端收到错误信息后直接弹出提示。这比每个Controller里try-catch要干净得多也是SpringBoot开发中很基础但必须掌握的技巧。4. 数据库表结构设计让状态流转不再混乱请假系统的核心在数据表的设计上。我第一次做的时候直接拍脑袋建了四五张表写到后面发现审批记录没法追溯、时间冲突没法判断、考勤和请假很难关联只好推倒重来。这里我把最终版的核心表结构以及设计逻辑讲清楚。4.1 核心表清单与关系系统共设计了8张表核心关系如下sys_user用户表包含学生和教师教师和辅导员都属于教师用户通过角色字段区分字段有id、username、password、real_name、role、college_id。sys_student学生扩展表关联sys_user记录学号、班级id、入学年份等。sys_college学院表记录学院名称、编号。sys_class班级表关联学院记录专业、年级、班级名称。course课程表包含课程名称、任课教师id、上课时间周几、第几节、上课周次。student_course选课表关联学生和课程。leave_request请假申请表核心主表。leave_approval审批记录表记录每一步审批的详情。4.2 请假申请表的关键字段设计leave_request表是整个系统的核心字段设计直接决定业务逻辑的复杂程度CREATE TABLE leave_request ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, student_id BIGINT 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 请假结束时间, reason VARCHAR(500) COMMENT 请假事由, proof_url VARCHAR(255) COMMENT 证明材料URL, status TINYINT DEFAULT 0 COMMENT 状态0待审批 1审批中 2已通过 3已驳回 4已撤销 5已完成, current_approver_id BIGINT COMMENT 当前待审批人id, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 提交时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间 );状态字段我用了Integer而不是String好处是后端比较方便、排序查找效率高。为了让代码可读性更强我定义了一个常量类LeaveStatus统一管理这些状态值避免魔法数字散落在各个方法里。4.3 审批记录表为什么必须单独建很多同类毕设项目会把审批结果直接写在leave_request表里比如直接加一个approver_id和approve_comment字段。这样做的确省事但业务上是残缺的。多级审批场景下辅导员批完可能还要系主任批如果只用一个字段存储审批人上一级的意见就会被覆盖。我这里单独建了leave_approval表CREATE TABLE leave_approval ( id BIGINT PRIMARY KEY AUTO_INCREMENT, leave_id BIGINT NOT NULL COMMENT 请假单id, approver_id BIGINT NOT NULL COMMENT 审批人id, approval_result TINYINT NOT NULL COMMENT 结果1同意 2驳回, approval_comment VARCHAR(500) COMMENT 审批意见, approval_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 审批时间 );每次审批都追加一条记录既能保留完整的审批轨迹又能支持任意多级的审批流程扩展。查询这个学生这条请假是谁批的、批了什么意见变成了一条SQL就能解决的事。我答辩时老师就问到了审批留痕的问题这张表就是最好的回答。4.4 逻辑删除与审计字段业务系统里的数据一般不做物理删除而是采用逻辑删除。MyBatis-Plus的TableLogic注解能非常方便地实现这个功能。我在所有实体类的id上配置逻辑删除字段deleted默认值为0删除时框架自动执行UPDATE语句置为1查询时自动过滤掉已删除的数据。另外每张表我都加上了create_time和update_time两个审计字段并使用MyBatis-Plus的自动填充功能统一维护不需要在代码里手动set时间Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }5. 核心代码实现审批流、冲突校验与权限控制接下来是项目中最核心的部分。我挑三个最有技术含量、也是答辩时最容易成为亮点的功能点来展开请假时间冲突校验、多级审批流程控制和基于JWT的权限拦截。5.1 请假时间冲突校验防止同一时段重复请假学生可能在已经提交了一个未完结的请假单后又提交一个新的请假单导致同一时间段出现两个请假申请。这在业务上是不允许的需要在Service层做校验。我的校验思路很简单查询当前学生的所有未完结申请状态为待审批、审批中、已通过判断新请假的起止时间是否与已有记录的重叠。Override public void validateTimeConflict(Long studentId, LocalDateTime startTime, LocalDateTime endTime, Long excludeLeaveId) { LambdaQueryWrapperLeaveRequest wrapper new LambdaQueryWrapper(); wrapper.eq(LeaveRequest::getStudentId, studentId) .in(LeaveRequest::getStatus, Arrays.asList(0, 1, 2)) .ne(excludeLeaveId ! null, LeaveRequest::getId, excludeLeaveId) .and(w - w .le(LeaveRequest::getStartTime, startTime).ge(LeaveRequest::getEndTime, startTime) .or() .le(LeaveRequest::getStartTime, endTime).ge(LeaveRequest::getEndTime, endTime) .or() .ge(LeaveRequest::getStartTime, startTime).le(LeaveRequest::getEndTime, endTime)); ListLeaveRequest conflicts leaveRequestMapper.selectList(wrapper); if (!conflicts.isEmpty()) { throw new BusinessException(该时间段内已有未完结的请假申请请勿重复提交); } }时间重叠判断有三种情况新请假开始时间落在已有区间内、新请假结束时间落在已有区间内、新请假区间完全包含已有区间。上面的SQL条件把三种情况都覆盖了。如果前端传了excludeLeaveId参数说明是编辑操作要排除当前这条记录自己。5.2 多级审批状态流转的实现审批流程的状态流转是整个系统最复杂的业务逻辑。我采用了一个相对容易理解又足够实用的策略提交申请时根据请假天数判断审批层级如果是短假3天当前审批人为辅导员如果超过3天当前审批人设置为辅导员但审批通过后系统会根据配置自动判断是否需要进入下一级。核心审批逻辑如下Transactional(rollbackFor Exception.class) public void approve(Long approvalId, Long approverId, Integer result, String comment) { LeaveRequest leaveRequest leaveRequestMapper.selectById(approvalId); if (leaveRequest null || !leaveRequest.getStatus().equals(LeaveStatus.PENDING_APPROVAL)) { throw new BusinessException(该请假单当前状态不可审批); } // 校验审批人权限必须是当前待审批人 if (!leaveRequest.getCurrentApproverId().equals(approverId)) { throw new BusinessException(您不是当前审批人无法操作); } // 保存审批记录 LeaveApproval approval new LeaveApproval(); approval.setLeaveId(leaveRequest.getId()); approval.setApproverId(approverId); approval.setApprovalResult(result); approval.setApprovalComment(comment); leaveApprovalMapper.insert(approval); if (result 2) { // 驳回直接结束流程 leaveRequest.setStatus(LeaveStatus.REJECTED); } else { // 同意判断是否需要进入下一级 Integer nextLevel determineNextLevel(leaveRequest.getLeaveDurationDays(), leaveRequest.getCurrentApproverLevel()); if (nextLevel ! null) { leaveRequest.setStatus(LeaveStatus.APPROVING); leaveRequest.setCurrentApproverId(getApproverByLevel(leaveRequest.getStudentId(), nextLevel)); leaveRequest.setCurrentApproverLevel(nextLevel); } else { // 所有层级审批完成 leaveRequest.setStatus(LeaveStatus.APPROVED); // 联动通知任课教师等操作 notifyTeachers(leaveRequest); } } leaveRequestMapper.updateById(leaveRequest); }我特别加了Transactional事务注解审批记录的插入和请假单状态的更新必须同时成功或同时失败。如果审批记录插入了但状态没更新成功整个操作就要回滚否则数据会出现严重的不一致。SpringBoot的声明式事务默认只在RuntimeException下回滚所以rollbackFor要显式指定Exception.class。5.3 JWT权限控制实现真正意义上的接口安全权限是这个项目的门面。我没有使用传统Spring Security的默认表单登录而是结合JWT做无状态登录认证。整个流程是用户登录后服务端生成一个带有效期的JWT返回给前端前端每次请求在请求头中携带这个Token后端通过自定义过滤器解析并验证用户身份。核心过滤器代码如下Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { token token.substring(7); try { Claims claims JwtUtil.parseToken(token); Long userId Long.valueOf(claims.get(userId).toString()); String role claims.get(role).toString(); // 将用户信息存入SecurityContext UsernamePasswordAuthenticationToken auth new UsernamePasswordAuthenticationToken( userId, null, Collections.singletonList(new SimpleGrantedAuthority(ROLE_ role))); auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(auth); } catch (Exception e) { response.setStatus(401); response.setContentType(application/json;charsetutf-8); response.getWriter().write({\code\:401,\message\:\登录已过期请重新登录\}); return; } } filterChain.doFilter(request, response); } }然后通过Spring Security的配置类指定接口的访问权限http.authorizeRequests() .antMatchers(/api/auth/login, /api/auth/register).permitAll() .antMatchers(/api/leave/student/**).hasRole(STUDENT) .antMatchers(/api/leave/approve/**).hasAnyRole(TEACHER, ADMIN) .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated();这里有个细节我在JWT里只存userId和role不存密码等敏感信息。每次请求时如果需要获取当前登录用户的详细信息再从Redis缓存里查。这样既保证了Token的无状态性又能在用户被禁用时通过Redis中的黑名单快速失效。6. 开发过程中踩过的坑与填坑方案做项目的过程中遇到的问题比预想的多这里挑几个典型的分享每一个都是我实际调了几小时才解决的希望能帮你避开。6.1 没配置Jackson时间格式前后端日期传参各种报错前端传2024-05-20 14:30给后端后端LocalDateTime字段根本接收不了直接报解析异常。原因是SpringBoot默认的Jackson反序列化不认识这种格式。我在application.yml里显式配置了全局时间格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8同时在后端接收参数时对RequestParam或RequestBody里的日期字段加上DateTimeFormat注解。否则你前端明明传了合法格式后端依然400。建议抽屉里备一套标准时间字符串联调时先测通的永远是2024-05-20 00:00:00这样的全格式。6.2 审批操作偶发数据错乱排查后是事务没生效有一次测试多级审批辅导员同意后系统的审批记录插入了但请假单状态一直没变。反复看代码逻辑没问题最后发现是方法内部的this调用导致事务失效。我写了两个方法在一个类里approve方法调用了同一个类的submit方法而Spring的事务是通过AOP代理实现的this调用不会经过代理对象所以事务注解根本没生效。解决办法是把需要事务的方法拆到另外一个Bean或者用编程式事务边界。排查了这个坑之后我才真正理解了Transactional的代理机制。6.3 MyBatis-Plus自动填充失效我在实体类createTime字段上加了TableField(fill FieldFill.INSERT)但插入数据时createTime还是没有值。检查配置类没有生效后来发现是MetaObjectHandler的Bean没被Spring扫描到。我把它放到了config包之外的目录里又没加ComponentScan注解指定扫描路径所以整个处理器压根没注册。移动位置并加上Component后问题解决。如果你的项目有自定义扫描路径一定要检查Bean是否真的被注册了。6.4 前端跨域问题本地点得通联调时却白屏后端默认是不允许跨域请求的。前端Vue项目跑在8080端口后端是8081接口请求直接被浏览器拦截。解决方法是写一个WebMvcConfigurer配置类统一处理跨域Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }allowCredentials(true)配合allowedOriginPatterns()时要注意某些SpringBoot版本如果用了allowedOrigins()会报错建议直接用allowedOriginPatterns。6.5 权限拦截器导致登录接口永远403JWT过滤器写好后我测试登录接口发现始终返回403。排查了一圈发现Security配置的antMatchers顺序问题——permitAll要写在authenticated之前且/login必须精确匹配。还有一次是自定义过滤器被Spring Security的过滤器链拦截之前就抛了异常导致返回的信息格式不是JSON而是默认错误页。后来把filter加入HttpSecurity的addFilterBefore位置并对异常做了try-catch处理后才正常。7. 系统优化与答辩亮点让项目从能跑到讲得出来很多人的毕设做完能运行就万事大吉但答辩时往往被老师问几句就答不上来。我建议在项目收尾阶段花时间做几个优化每一个都能成为答辩中的加分项。7.1 Redis缓存登录态与热点数据这个项目里Redis有两个用途一是存JWT的白名单二是缓存辅导员端的学生列表等热点数据。比如辅导员进入班级学生列表时每次都查一次数据库如果班级人数多、选课关系复杂性能并不好。我的做法是首次查询后将List缓存到Redis设置5分钟过期学生提交请假或考勤变动时主动删除对应缓存保证数据一致性。Cacheable(cacheNames studentList, key #classId) public ListStudentVO getStudentListByClass(Long classId) { // 首次查询数据库之后就命中缓存 }答辩时老师问数据修改了缓存怎么办就能顺势讲出CacheEvict的用法和主动失效策略。这比单纯说我用了缓存有说服力得多。7.2 请假数据的Excel导出使用Apache POI管理端的导出功能我用POI实现了请假记录表导出功能可以按学院、班级、请假类型、审批状态等多个维度筛选导出。这个功能虽然代码不复杂但是很实用。核心思路是构建Workbook对象创建Sheet填充表头和数据行XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(请假记录); // 创建表头 String[] headers {学号, 姓名, 请假类型, 开始时间, 结束时间, 状态, 事由}; // 填充数据行 for (LeaveRecordVO record : recordList) { XSSFRow row sheet.createRow(rowNum); row.createCell(0).setCellValue(record.getStudentNo()); // ... } // 设置响应头让浏览器下载 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenameleave_records.xlsx);网上有人问Java POI Word能生成图表吗其实POI的项目核心方向有两个Excel操作用的是HSSF/XSSFWord操作是XWPF如果想把Excel、Word方向的场景都串起来写你的毕设里还能增加一个打印审批单功能——生成Word版请假审批单留给线下盖章存档。这样一个功能同时体现文档处理能力和实际业务结合意识非常吸睛。7.3 引入日志与全局兜底我在每个关键Service方法里加入了Slf4j日志输出记录核心业务操作的用户、时间、参数和结果。比如审批通过时日志能清楚地看到谁的请假单被谁在什么时间审批通过了。线上如果出问题看日志就能定位逻辑上也规范。同时我写了一个最后的兜底校验横向比较请假单的当前状态是否被其他人并发改变了这种乐观锁的写法在面试和答辩中同样是很好的谈资。7.4 答辩时如何讲这个系统答辩时不要一开始就贴代码。我的建议是先用一张业务流程图讲清楚学生请假-辅导员审批-教师联动-销假归档的核心链路然后展示三四个核心界面的操作过程再点出三个技术亮点多级审批状态机、冲突校验、RBAC权限模型最后演示数据导出和统计功能。整个过程控制在10分钟以内重点突出系统解决问题的能力和设计思路而不是功能罗列。根据我自己的经验老师在答辩时问得最多的几个问题是请假状态为什么会这样设计、如何防止同一时间段重复请假、审批人权限是怎么控制的、缓存和数据一致性怎么处理。这篇文章里面写的每个模块其实都是在回答这些问题。如果你能清晰地把这几个环节讲明白毕设答辩基本就稳了。最后再分享一个开发效率小技巧写这个项目时建议学会用Lombok的Getter/Setter和Slf4j注解实体类的getter/setter方法不用手动敲代码量直接少三分之一。还有尽量用MyBatis-Plus的LambdaQueryWrapper来写查询条件不仅避免字符串硬编码代码也更优雅。每次写完一个功能先用POST请求把接口都测一遍再继续下一个模块不要攒到最后统一测否则Bug堆在一起非常难排查。
延伸阅读

更多相关文章

2026/10/11 8:37:51

悬疑题材漫剧怎么快速生成?知漫剧场景画面生成技巧

不懂技术的人也能理解:悬疑剧吃的是氛围——光线暗一点、场景空一点、镜头偏一点,紧张感就出来了;但画面不对,故事再好也不吓人。知漫剧(zz.jiaxunai.cn)——一站式 AI 漫剧创作平台,专为新手小…

2026/10/11 8:37:51

开工第一周代码手生?用飞算JavaAI找回编码手感

开工第一周,我最真实的体感不是“效率低”,是“手生”。坐在工位上,面前是一套春节前还挺熟悉的Java项目,光标却悬停半天,脑子像被格式化过一样:这个Service是干什么的?那段Mapper查询为什么当初…

2026/10/11 8:32:50

LeetCode 1545详解:不构造完整字符串,递归二分定位第K位

LeetCode 1545 大概是“递归构造类”题目里最值得手推一遍的代表作了。题目本身不复杂:二进制字符串 S1 等于"0",从 S2 开始,每个字符串都由三块拼成——上一轮的字符串、一个固定的1、以及上一轮字符串取反之后反转的结果。最后需…

2026/10/11 9:47:55

Spring Boot 3.3.4升级:Logback旧版回滚策略失效的解决与迁移

1. 升级踩坑:Spring Boot 3.3.4 一换,Logback 回滚策略先崩了先说结论:这并不是你写的那段 logback-spring.xml 语法有问题,而是 Spring Boot 3.3.4 默认引入的 Logback 版本出现了一次不大不小的“破坏性升级”。原本在 1.2.x 里…

2026/10/11 9:47:55

DukeMTMC-VideoReID数据集全解析:从数据加载到评估协议

简介:DukeMTMC-VideoReID 是一套面向行人再识别(Video Re-ID)任务的 Python 数据集与代码库,适用于监控场景下跨摄像头行人追踪与身份识别的研究与开发。该数据集源自大型多目标、多摄像头跟踪项目 DukeMTMC,包含 8 个…

2026/10/11 9:47:55

YOLOv8道路病害检测实战:从数据集准备到10ms推理优化

简介:本资源面向计算机视觉学习者与智能交通方向开发者,提供一套基于Yolov8的道路病害目标检测完整项目,覆盖横向裂缝、纵向裂缝、块状裂缝、龟裂、坑槽及多种修补类病害的识别任务,适合课程大作业、毕业设计或工程原型验证。压缩…

2026/10/11 9:47:55

YOLO犬类情绪识别实战:从目标检测到行为特征分类的完整方案

简介:基于YOLO的犬类情绪识别设计是一份面向深度学习教育场景的完整项目资源包,适合毕业设计、课程设计或期末大作业使用。它围绕犬类情绪分类这一具体任务,展示了从数据准备、模型训练到测试部署的完整链路,帮助学习者掌握目标检…

2026/10/11 9:42:54

Spring Boot+Vue多用户B2B2C商城源码解析与部署实践

买过或者评估过不少商城源码之后,再看到“Spring Boot Vue JavaShop 7.1.15 多用户 B2B2C 商城源码”这个标题,我第一反应不是“又来一套后台加前台的 CRUD”,而是想认真看看这套系统的单体架构是否扛得住中小规模电商业务的真实场景。如果…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑