ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot高校请假管理系统毕业设计:从需求分析到状态机审批完整实战

SpringBoot高校请假管理系统毕业设计:从需求分析到状态机审批完整实战 1. 项目概述与核心需求解析1.1 这个项目到底解决什么问题每年到了毕业季选“高校学生请假管理系统”这类题目的同学都不少。原因很简单一是业务场景足够清晰评委和导师一听就懂二是技术栈主流SpringBoot MyBatis-Plus MySQL这几样往简历上一放大家都认识三是功能边界明确不大会做着做着失控。但说句实在话很多同学做完的版本就是学生提交、老师通过、数据入库三个页面一拼完了。这类系统在答辩时最容易被追问的一个问题就是“你这系统跟一份Excel表有什么区别”所以要做出真正能拿得出手的毕业设计必须想清楚一件事——请假管理系统不只是“填表单、点按钮”它本质上是一套高校学生事务流程审批引擎。核心业务逻辑是这样的学生发起请假申请辅导员进行第一级审批涉及课程出勤的还要抄送给任课老师院系教务做最终备案。每一步都有时间记录、状态流转、条件判断比如请假超过3天必须由院系分管领导审批销假时必须关联实际返校时间并自动更新考勤统计。这套逻辑拆开来看涉及用户角色权限、工作流状态机、考勤数据聚合、消息通知触发、数据报表导出五个核心模块。任何一个拿出来都足够撑起一篇质量不错的毕业设计论文的第二章。1.2 为什么主流方案都选SpringBoot我接触过的毕设题目里只要涉及Web管理系统十有八九是SpringBoot。这个选择不完全是跟风背后有几个很实际的原因。第一SpringBoot把Spring MVC、事务管理、自动配置全部做了封装开发效率确实高。一个请假系统涉及的增删改查、状态流转、分页查询、文件上传比如病假证明材料在SpringBoot下写起来非常顺手两三天就能把骨架搭完剩下大量时间去处理业务细节和论文。第二SpringBoot 2.7.x在高校教学和企业项目里兼容性极好。这里必须提醒一下现在不少同学一上来就下载SpringBoot 3.x结果JDK版本不匹配3.x要求JDK17、Spring Security 6的写法跟网上大量旧教程对不上号、javax.包变成jakarta.各种踩坑进度直接卡住。如果你是用SpringBoot做毕设我个人的建议是使用SpringBoot 2.7.18 JDK 8 MySQL 8.0这三个版本组合在网上能找到几乎所有问题的现成答案踩坑成本最低。第三SpringBoot的项目结构规整适合写论文。controller、service、mapper、entity、config分层清晰画系统架构图、功能结构图的素材现成就有答辩的时候讲起来也顺。1.3 这篇分享适合谁如果你正在做或准备做SpringBoot相关的毕业设计这篇分享值得完整看一遍。我会把请假管理系统的需求分析、表结构设计、核心代码实现、部署排错整个链路讲清楚同时把代码实现之外的东西——比如怎么把简单的请假功能扩展成能写进论文的完整业务闭环——一并讲透。即使你的题目不是请假系统而是“疫情防控管理系统”“社团活动管理系统”“实验室预约系统”这套分析和实现思路也基本可以平移复用。2. 技术选型与架构设计思路2.1 单体优先别一上来就微服务很多同学在开题答辩时被老师问了一句“为什么不用微服务”回来就心虚非要往项目里塞Nacos、Feign、分布式事务最后项目跑都跑不起来。这里我把话说得直白一点高校请假管理系统这种业务规模单体应用是技术上完全正确的选择而且微服务引入的复杂度是论文根本无法自圆其说的。你论文里分析“为什么选择单体架构”写到“业务规模小、团队人力有限、部署运维成本低”这几点老师是认可的。单体架构在这个项目里的具体表现是一个SpringBoot应用包含所有业务模块前端可以使用Thymeleaf服务端渲染或Vue前后端分离两种形态。两种我都做过分别说说。Thymeleaf方案适合时间紧、Java基础相对薄弱的同学所有页面由后端Controller渲染不需要额外启动前端工程部署时打包一个Jar就能跑。前后端分离方案适合打定主意要把Vue写上简历的同学前端工程需要node_modules安装依赖、Vue Router、Axios封装开发阶段要处理跨域CORS问题时间成本多出30%左右。我个人的推荐是如果你的项目周期只有两三个月选Thymeleaf方案把精力花在业务逻辑的完整性和系统的美观度上性价比最高。下文代码示例也统一按Thymeleaf单体方案来讲解。2.2 技术栈清单与版本对应关系技术组件推荐版本用途与说明JDK1.8稳定性第一兼容性无敌SpringBoot2.7.18自动配置、Starter生态成熟MyBatis-Plus3.5.x内置分页插件、代码生成器MySQL8.0主数据库注意驱动配置差异Druid1.2.x数据库连接池自带监控页面Lombok最新版简化实体类但要注意IDE插件安装Thymeleaf3.0.x服务端模板渲染Bootstrap5.x页面样式直接CDN引入即可ECharts5.x考勤统计图表展示这里面最值得说的一句是为什么用MyBatis-Plus而不是原生MyBatis。对于这种大量CRUD加条件查询的管理系统MyBatis-Plus提供了BaseMapper的通用方法、LambdaQueryWrapper链式查询、物理分页插件能把代码量压缩60%以上。比如查询某个学生本学期所有已通过的请假记录原生MyBatis需要写XML、写resultMap、写动态SQLMyBatis-Plus一行LambdaQueryWrapper就解决了。减少的代码量意味着更多时间花在业务场景打磨和论文文字润色上。2.3 项目目录结构与职责边界src/main/java ├── com.example.leave │ ├── config │ │ ├── MybatisPlusConfig.java // 分页插件配置 │ │ ├── WebMvcConfig.java // 拦截器注册 │ │ └── DruidConfig.java // 数据源监控配置 │ ├── controller │ │ ├── AuthController.java // 登录、退出 │ │ ├── StudentLeaveController.java // 学生请假接口 │ │ ├── ApprovalController.java // 审批接口 │ │ ├── AttendanceController.java // 考勤登记接口 │ │ ├── StatsController.java // 统计报表接口 │ │ └── UserController.java // 用户管理接口 │ ├── service │ │ ├── LeaveApplicationService.java // 请假核心业务 │ │ ├── ApprovalFlowService.java // 审批流状态机 │ │ └── AttendanceService.java // 考勤服务 │ ├── mapper │ ├── entity │ ├── dto │ ├── vo │ ├── common │ │ ├── Result.java // 统一返回结果 │ │ ├── JwtUtil.java // JWT工具类 │ │ └── GlobalExceptionHandler.java // 全局异常处理 │ └── interceptor │ └── AuthInterceptor.java // 登录鉴权拦截器 src/main/resources ├── application.yml ├── mapperXML文件目录 └── templatesThymeleaf页面这套结构有几个设计上的讲究。common包里的Result统一了前后端交互的数据格式避免每个人各写各的返回类型代码风格难看而且功能容易跑偏。interceptor包负责登录状态校验所有需要登录的接口统一在WebMvcConfig里注册而不是每个Controller里重复写判断逻辑。entity和dto分开是为了避免数据库表字段直接暴露给前端页面特别是审批意见、请假理由这类字段必须收口到服务层验证。3. 数据库设计与权限模型3.1 表结构设计从业务反推需求设计表结构之前先把业务角色理清楚。一个典型的高校请假流程涉及四类角色学生提交请假申请、查看审批进度、申请销假辅导员班主任审批本班学生的请假、登记学生出勤异常任课教师查看所教课程的请假学生名单、确认出勤院系教务管理员全局查看、最终备案、数据导出所以数据库里至少要有用户表、角色表、班级表、学生信息表、请假申请表、审批记录表、考勤表、通知表。要注意不要傻乎乎地给user表直接加一个role字段然后用0、1、2区分角色因为一个用户可能同时具备多种角色——虽然这种事在实际系统里少但建表时把用户表与角色表分开用关联表连接这是评委会问到的“数据库规范化”考点。3.2 核心表DDL与字段说明下面是请假申请主表这张表是整个系统的心脏CREATE TABLE leave_application ( id bigint(20) NOT NULL AUTO_INCREMENT, student_id bigint(20) NOT NULL COMMENT 学生用户ID, class_id bigint(20) DEFAULT NULL COMMENT 班级ID冗余存储便于查询, leave_type tinyint(4) NOT NULL COMMENT 请假类型:1-事假 2-病假 3-公假 4-其他, start_time datetime NOT NULL COMMENT 请假开始时间, end_time datetime NOT NULL COMMENT 请假结束时间, total_days decimal(5,1) DEFAULT NULL COMMENT 请假天数, reason varchar(500) NOT NULL COMMENT 请假事由, attachment_url varchar(255) DEFAULT NULL COMMENT 证明材料附件路径, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态:0-待辅导员审批 1-待院系备案 2-已通过 3-已驳回 4-已销假 5-已撤销, current_approver bigint(20) DEFAULT NULL COMMENT 当前待审批人ID, apply_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个表的status字段就是请假流程的状态机后续所有业务逻辑都是围绕这个状态值展开的。审批记录表单独建一张这一点非常重要。很多同学图省事在leave_application表里加一个approve_comment字段结果多级审批时记录直接被覆盖论文里写“完整记录审批过程”就是一句空话。审批记录表的正确设计是每条审批意见一行CREATE TABLE approval_record ( id bigint(20) NOT NULL AUTO_INCREMENT, application_id bigint(20) NOT NULL COMMENT 请假申请ID, approver_id bigint(20) NOT NULL COMMENT 审批人ID, approval_role tinyint(4) NOT NULL COMMENT 审批角色:1-辅导员 2-院系教务, action tinyint(4) NOT NULL COMMENT 动作:1-通过 2-驳回, comment varchar(500) DEFAULT NULL COMMENT 审批意见, create_time datetime NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个设计既方便在请假详情页里按时间顺序展示审批流转时间线也方便论文里画审批流程图时直接取数。3.3 权限模型RBAC还是要做的我见过不少毕设的登录逻辑是查一下user表里的role字段如果是1就跳管理员页面是2就跳学生页面。这种做法在答辩时很容易被攻击“如果有学生知道接口地址直接把请求里的参数改了是不是就能调用管理员接口”正确的做法是至少做到接口级RBAC。我的实现思路是利用SpringBoot的拦截器加注解自定义一个RequireRole注解标注在Controller方法上用户在登录时后端生成JWT令牌令牌中携带userId和role列表每一次请求拦截器解析JWT校验接口所需的角色是否包含在用户角色列表中这样即使用户尝试直接访问某个接口没有对应角色权限会被拦截器直接拦截。虽然不能跟Spring Security的精细权限模型比但应对毕设和大多数校园系统的安全需求已经足够了而且实现代码不过几十行答辩时还能讲清楚原理。4. 核心功能实现与关键代码解析4.1 登录认证与JWT工具类先说登录。密码存储不能用明文这是底线至少要用MD5加盐或者BCrypt加密。毕设项目用BCrypt更稳Spring Security的crypto包可以直接引入不需要引入整个Spring Security框架。Component public class JwtUtil { // 密钥生产环境必须配置在环境变量中 private static final String SECRET_KEY your-secret-key-change-me; private static final long EXPIRATION 24 * 60 * 60 * 1000; // 24小时 public String generateToken(Long userId, String username, Integer role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }这里使用的jjwt依赖版本要注意网上很多教程用的是0.9.x对应JDK8没问题如果你用SpringBoot 3.x JDK17jjwt 0.9.x会报错需要升级到jjwt-api 0.11.5及以上。这也是我坚持推荐SpringBoot 2.7.x的原因之一——配套生态完全一致跟着教程走不会翻车。JWT生成之后放在前端localStorage里每次请求通过Axios或原生ajax的Authorization头携带后端拦截器统一解析。这种做法跨域时不会像Cookie那样出现各种session丢失问题。4.2 请假申请提交与状态机流转学生提交请假申请服务层的核心方法大致如下Override Transactional(rollbackFor Exception.class) public Long submitLeave(LeaveSubmitDTO dto, Long studentId) { // 1. 校验时间合法性 if (dto.getEndTime().isBefore(dto.getStartTime())) { throw new ServiceException(请假结束时间不能早于开始时间); } // 2. 计算请假天数注意去重同一天 long days ChronoUnit.DAYS.between( dto.getStartTime().toLocalDate(), dto.getEndTime().toLocalDate()) 1; // 3. 保存请假主表数据 LeaveApplication application new LeaveApplication(); application.setStudentId(studentId); application.setLeaveType(dto.getLeaveType()); application.setStartTime(dto.getStartTime()); application.setEndTime(dto.getEndTime()); application.setTotalDays(BigDecimal.valueOf(days)); application.setReason(dto.getReason()); application.setStatus(LeaveStatus.PENDING_ADVISOR.getCode()); application.setCurrentApprover(getAdvisorIdByStudentId(studentId)); leaveApplicationMapper.insert(application); // 4. 写入通知 noticeService.sendNotification(application.getCurrentApprover(), 你有新的请假审批待处理); return application.getId(); }这里有几个细节值得注意。第一Transactional必须加因为请假申请表和通知表要同时写入成功任何一步失败都不能留下半条数据。第二请假天数的计算用ChronoUnit.DAYS.between加1是为了把起止当天都算进请假天数比如周一请到周三天数应该是3天而不是2天。第三状态字段用枚举编码存int不要在数据库里存字符串状态名避免大小写不一致导致查询不到。状态机流转是请假系统的核心难点我用一个Map来定义合法状态转换路径private static final MapInteger, ListInteger STATE_TRANSITIONS new HashMap(); static { // 从“待辅导员审批”可以流转到以下状态 STATE_TRANSITIONS.put(LeaveStatus.PENDING_ADVISOR.getCode(), Arrays.asList(LeaveStatus.PENDING_DEPARTMENT.getCode(), // 辅导员通过进入院系备案 LeaveStatus.REJECTED.getCode(), // 辅导员驳回 LeaveStatus.CANCELED.getCode())); // 学生自行撤销 // 从“待院系备案”可以流转到以下状态 STATE_TRANSITIONS.put(LeaveStatus.PENDING_DEPARTMENT.getCode(), Arrays.asList(LeaveStatus.APPROVED.getCode(), // 备案通过请假生效 LeaveStatus.REJECTED.getCode())); // 备案不通过 // 从“已通过”可以流转到以下状态 STATE_TRANSITIONS.put(LeaveStatus.APPROVED.getCode(), Arrays.asList(LeaveStatus.COMPLETED.getCode())); // 销假完成 } public void transition(Long applicationId, int fromStatus, int toStatus, Long operatorId) { LeaveApplication app leaveApplicationMapper.selectById(applicationId); ListInteger allowed STATE_TRANSITIONS.get(app.getStatus()); if (allowed null || !allowed.contains(toStatus)) { throw new ServiceException(非法状态流转); } // 执行状态更新、记录审批历史、发送通知... }这样做的好处非常明显首先非法操作在所有入口都被拦截前端按钮和后端接口是双保险其次论文里可以画一张清晰的状态转换图答辩时照着讲评委一眼就看出你对业务逻辑的理解是到位的最后后续扩展其他流程比如调课申请、宿舍外宿申请时只需要在Map里增加状态节点不用大改业务逻辑。4.3 审批操作与多级审批实现辅导员登录系统看到待审批列表点击通过后端对应的方法是approve()。这个方法的实现除了状态流转之外还需要做两件关键的事判断当前登录用户是否有权限审批这个申请以及判断请假天数是否超过设定阈值需要转给更高层级。Override Transactional(rollbackFor Exception.class) public void approve(Long applicationId, String comment, Long approverId, Integer role) { LeaveApplication app leaveApplicationMapper.selectById(applicationId); if (app null) { throw new ServiceException(申请不存在); } // 校验当前审批人是否就是指定的审批人 if (!app.getCurrentApprover().equals(approverId)) { throw new ServiceException(当前您无权审批该申请); } LeaveApprovalConfig config approvalConfigMapper.selectByLeaveType(app.getLeaveType()); ApprovalRecord record new ApprovalRecord(); record.setApplicationId(applicationId); record.setApproverId(approverId); record.setApprovalRole(role); record.setAction(1); record.setComment(comment); approvalRecordMapper.insert(record); // 判断是否需要进入下一个审批环节 if (app.getTotalDays().compareTo(config.getMaxDaysForAdvisor()) 0) { // 超过辅导员最大审批天数转入院系教务审批 app.setStatus(LeaveStatus.PENDING_DEPARTMENT.getCode()); app.setCurrentApprover(getDepartmentAdminIdByClassId(app.getClassId())); } else { app.setStatus(LeaveStatus.APPROVED.getCode()); } leaveApplicationMapper.updateById(app); noticeService.sendNotification(app.getStudentId(), 你的请假申请已通过); }这里引入了一个请假配置表用来管理不同请假类型对应的审批阈值比如事假辅导员最大可批3天病假可批7天超过阈值需要院系审批。把业务规则抽到配置表而不是写死在代码里是评审老师非常看重的一个设计点——说明你考虑到了业务变化对系统的影响。4.4 考勤登记与统计报表请假通过的记录需要自动同步到考勤模块。学生销假后系统需要根据实际返校时间更新考勤状态。考勤表结构相对简单CREATE TABLE attendance_record ( id bigint(20) NOT NULL AUTO_INCREMENT, student_id bigint(20) NOT NULL, course_id bigint(20) NOT NULL COMMENT 课程ID, attendance_date date NOT NULL COMMENT 考勤日期, status tinyint(4) NOT NULL COMMENT 状态:0-正常 1-请假 2-迟到 3-缺勤, source_type tinyint(4) NOT NULL COMMENT 来源:1-登记 2-请假关联 3-导入, create_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_student_course_date (student_id,course_id,attendance_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;唯一索引是关键保证同一个学生同一门课同一天只能有一条考勤记录避免数据重复。统计报表我用ECharts做图表展示按班级维度展示月度请假人数趋势、按请假类型展示占比饼图、按年级展示平均请假次数。数据查询直接用MyBatis-Plus的聚合查询不需要额外引入报表引擎。4.5 通知模块站内信与邮件双通道审批结果、请假提醒这类消息不能只靠在列表里刷新查看增加一个简单的通知模块能显著提升系统的完成度。我实现的是一个站内信表加可选邮件通知站内信的表设计是notice_id、receiver_id、title、content、is_read、create_time。学生登录后右上角显示未读数量点进去查看详情已读状态实时更新。邮件通知用SpringBoot的spring-boot-starter-mail配置好SMTP后调用JavaMailSender发送。注意几个实际经验QQ邮箱和163邮箱都要在邮箱设置里开通SMTP服务密码是授权码而不是邮箱登录密码发送邮件是IO操作耗时较长不能放在请求主线程里一定要用Async放到线程池否则前端审批按钮转圈半天没反应。spring: mail: host: smtp.qq.com port: 587 username: your-emailqq.com password: your-auth-code properties: mail: smtp: auth: true starttls: enable: true主类上记得加EnableAsync然后在通知Service的方法上标Async这样邮件发送就在独立线程执行了。5. 实操过程与部署要点5.1 IDEA中创建SpringBoot项目的正确姿势无数同学在新建项目这个步骤就卡住了这里把流程理顺。打开IDEA选择Spring InitializrServer URL我一般直接用默认的start.spring.io如果你访问慢可以用阿里云的https://start.aliyun.com注意它生成的SpringBoot版本可能偏旧需要在pom里手动调整Group填com.exampleArtifact填leave-system。依赖勾选时别一上来把所有Starter都勾了。我的建议是最少勾选Web、Thymeleaf、MySQL Driver、MyBatis-Plus Framework如果在依赖列表里找不到就创建完项目后手动在pom.xml加坐标因为国内IDEA版本的Spring Initializr有时不显示这个选项。创建完成后第一步是改pom.xml把SpringBoot版本改为2.7.18parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent然后添加MyBatis-Plus和Druid依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependency5.2 application.yml配置文件详解SpringBoot项目的配置集中在application.yml里这个文件必须了然于胸答辩时老师十有八九会问。server: port: 8080 servlet: context-path: /leave-system spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/leave_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root thymeleaf: cache: false mode: HTML jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0url里的serverTimezoneAsia/Shanghai非常重要不加这个Java和MySQL的时间差8小时导致日期查询全部错位排查一个下午是常有的事。useSSLfalse是为了避免连接时SSL握手警告刷屏。thymeleaf.cachefalse开发时必须关闭模板缓存否则改了页面不刷新重启项目才能看到效果。5.3 MyBatis-Plus分页插件与代码生成器分页插件配置属于必考内容。MyBatis-Plus 3.5.x的分页插件配置方式和旧版略有不同Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配好之后Service里写分页查询非常轻松Override public PageLeaveApplicationVO pageQuery(int pageNum, int pageSize, Long studentId) { PageLeaveApplication page new Page(pageNum, pageSize); LambdaQueryWrapperLeaveApplication wrapper new LambdaQueryWrapper(); wrapper.eq(LeaveApplication::getStudentId, studentId) .orderByDesc(LeaveApplication::getApplyTime); PageLeaveApplication result leaveApplicationMapper.selectPage(page, wrapper); // 把实体转换为VO并补充学生姓名、班级名等信息 return convertToVO(result); }页面上用一个PageHelper对象接收参数然后前端Bootstrap的分页组件根据总记录数渲染页码。注意一个经常踩的坑不要在原实体里混入非表字段比如分页查询需要返回“学生姓名”“班级名称”这些关联查询结果正确做法是创建一个VO类去继承或组合实体而不是在实体上多加字段。否则MyBatis-Plus的insert和update方法会把那些非表字段也拼到SQL语句里报“Unknown column”错误。5.4 页面设计不追求华丽追求合理管理系统的前端页面说真的绝大多数同学的美工水平都不足以支撑炫酷设计所以最稳妥的方案是使用现成的Bootstrap模板做一个干净、清爽、功能直观的后台管理界面。页面结构采用经典的左侧菜单顶栏内容区布局。左侧菜单根据用户角色动态生成学生角色只显示“请假申请”“我的假期”“通知消息”辅导员显示“审批管理”“班级学生”“课程考勤”管理员显示“用户管理”“数据统计”“系统设置”。菜单用Thymeleaf的sec:authorize或者简单的th:if判断角色来控制显示。表单页面的字段校验同时做前后端两层。前端用Bootstrap Validate做必填校验和日期范围校验改善交互体验后端在DTO上加NotNull、Future等JSR-303注解并在Controller入口统一做参数校验确保绕过前端直接调接口时系统依然安全。5.5 打包部署与演示环境准备毕业设计最终要提交一个能跑起来的系统最好还能打包成可执行文件给评委演示。用Maven打包命令mvn clean package -DskipTests打包完成后target目录下会生成一个leave-system-0.0.1-SNAPSHOT.jar。如果配置正确这个Jar包可以直接放到任何一台有JDK8和MySQL环境的机器上运行java -jar leave-system-0.0.1-SNAPSHOT.jar这样一个较完整的SpringBoot单体应用就交付了。部署演示环境时建议预置几组测试账号学生账号一个有3-5条不同状态的请假记录、辅导员账号一个有待审批和已审批的混合列表、管理员账号一个。演示时按“学生发起请假 → 辅导员审批 → 院系备案 → 数据统计图表变化”这个顺序走整个过程一气呵成给评委的观感会非常好。我在实际部署中还发现一个细节如果你要把项目交到实验室或导师机器上预览他们机器上大概率装了360杀毒软件或各种电脑管家第一次运行Jar包可能被杀毒软件拦截需要在白名单里放行或者直接用命令行启动这是一个不起眼但影响演示效果的坑提前提醒一下。6. 常见问题与排查技巧实录6.1 启动报错的九类高频问题下面这张表是我汇总了历年同学操作中最常踩的坑每条都是实测过的。报错现象根本原因解决方案Failed to configure a DataSource数据源配置缺失或yml缩进错误检查spring.datasource.url配置项是否存在缩进不要用TabAccess denied for user rootlocalhost数据库密码错误用Navicat或命令行验证root密码确认无误后修改ymlTable xxx doesnt exist表名或数据库名不一致检查MySQL中表名前缀是否与实体类的TableName一致Unknown column xxx in field list实体类存在非表字段用TableField(exist false)注解排除Port 8080 was already in use端口被占用用netstat -ano查找占用进程并结束或改server.port页面中文显示乱码数据库编码问题数据库和表都必须使用utf8mb4连接的encoding参数不要删日期显示00:00:00时区不一致url加serverTimezoneAsia/ShanghaiJackson设置GMT8前端JS不生效Thymeleaf缓存配spring.thymeleaf.cachefalse后重启MyBatis-Plus分页查询失效缺少分页拦截器检查MybatisPlusInterceptor是否注册为Bean6.2 耗时最久的“幽灵”问题时间差8小时与事务不回滚时间差8小时这个问题我单独拿出来说因为它在毕业设计里出现的频率实在太高了。现象是数据库里存的时间正确Java里查出来的时间少了或多了8小时页面显示不对。根因大概率出在连接串的时区参数和JDBC驱动的默认行为不一致。我的标准配置是URL里明确指定serverTimezoneAsia/Shanghai同时在Jackson配置里设置time-zoneGMT8这两个缺一不可缺哪个都会在特定场景翻车。事务不回滚是另一个隐蔽问题。很多同学在Service方法上加了Transactional但发现报错时数据还是部分写入了。排查思路是看方法是不是通过this.xxx()在同类内部调用的如果是Spring AOP的代理对象不会拦截内部自调用事务注解不生效。解决办法是把内部调用改为注入自身代理或者拆分成两个Service类互相调用。还有就是确认异常有没有捕获后吞掉如果try-catch之后不重新抛出事务同样不知道要回滚。6.3 数据统计口径的一个大坑统计模块经常被问到一个问题“请假天数和实际缺勤节数怎么对应”比如学生周一到周五请假5天但每天有4节课出勤数据里应该记录为缺勤20节还是按请假状态标记这个口径如果不一致论文里统计图表的数据就对不上账。我的处理方式是考勤表统一按天记录状态为“请假”另外在统计查询时通过课程排课表关联计算出请假涉及的实际学时。这样两种口径按天请假、按节统计都能从系统里查出来答辩时无论老师按哪个角度提问都有数可讲。6.4 让系统更“耐看”的锦上添花功能如果时间允许有三个扩展方向值得投入精力都能显著提升项目档次的第一个是Excel导入导出。用EasyExcel做一个学生信息批量导入和请假记录导出。别看就是一个工具类写进论文“系统功能”章节能占一整页答辩演示时用几百条数据导入只花几秒钟的视觉效果很好。第二个是审批流程可视化。在请假详情页面里用时间线组件展示每一步审批的记录包括谁在什么时间做了哪个操作、审批意见是什么。整洁的时间线UI会让整个系统增色不少。第三个是站内统计看板。登录后的首页不放空白欢迎语或过度动画而是放三个数字卡片——当前待办数、本月请假人次、今日出勤率下面放一个折线图展示近七天的请假趋势。这样系统一打开就给人“数据实时”的感觉。6.5 论文撰写与答辩准备的配合最后说一个很多同学忽视的角度代码写完了论文里不能只是罗列功能清单。要把实现过程提炼成几个有深度的结论。我这里举几个可以直接用的思路在需求分析章节重点写“多级审批的流程构造”解释为什么需要状态机而不是简单的if-else判断在系统设计章节画一张清晰的E-R图同时说明每张表的设计理由在核心实现章节挑“状态下-事件驱动流转”和“JWT无状态认证”两个点展开把代码一步一步贴出来并解释关键行在测试章节不要只写“测试全部通过”而是设计一个完整的用例表覆盖正常流程、异常分支、权限绕过、并发提交四类场景准备答辩时把项目启动后预先做好一份数据展示脚本限定自己在5分钟内走完登录三种角色、提交一个请假、审批通过、查看通知、打开统计图表。这个脚本会帮助你在面试时解决“低头翻代码半天找不到重点”的尴尬。我个人在实际操作中的体会是做这种管理系统真正考验的不是哪个框架用得多炫而是能不能把流程设计得严谨、把数据设计得规整、把异常情况都考虑到。这些能力恰恰是未来的开发工作中每天都要用的。如果你正在做这个题目不妨按照上面这个思路把系统重构一遍跑通全流程后你会发现之前那些“感觉哪里不对”的模糊问题全都变得清晰了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进