先直接说结论SpringBoot Vue3 前后端分离的高校请假管理系统是目前 Java Web 毕设和中小型校园管理系统实战里非常典型的一类选题。它解决的并不是一个“请假审批”的小功能而是学生、辅导员、学院领导三类角色之间如何把请假申请、多级审批、记录追溯、统计导出串成一条完整业务链的问题。适合准备做毕设的学生、刚学完 SpringBoot 想练前后端分离的开发者以及需要在校内快速落地一套基础管理系统的技术团队。这类系统最值得关注的不是功能多不多而是权限模型、审批流程和前后端数据交互这三块能不能稳定跑通。很多项目功能页面做得不少最后卡在跨域、状态管理、接口鉴权或打包部署上。这篇文章不按视频教程的节奏讲而是按实际开发顺序把从环境准备、表结构设计、后端接口、前端页面到联调部署的关键环节拆开给你一条可以照着复现的路线。1. 先弄清楚这套系统在解决什么问题1.1 角色和流程决定系统边界高校请假管理系统听上去只是一个课务管理工具但落到真实场景里它至少要处理三种完全不同的角色诉求。学生端要能做请假申请、查看审批进度、撤销未审批的申请、查看历史记录。辅导员端要能审核自己所带学生的请假申请能按班级或年级查看请假汇总。学院领导端要能进行二级审批或终审能按时间、班级、请假类型统计请假数据。如果不提前把角色链路理清很容易把系统做成“同一张表三个入口”。功能看起来都有但实际上审批顺序、数据可见范围、状态流转都是乱的。所以第一步不是建工程而是先把谁提交、谁审核、谁能看什么数据写清楚。常见的审批流程是学生提交请假申请填写请假类型、开始时间、结束时间、请假事由、证明材料。辅导员先审批可以选择通过或驳回。如果需要学院层面审核辅导员通过后进入学院领导审批环节。学生可以随时查看当前状态待辅导员审批、待学院审批、已通过、已驳回、已撤销。这套流程意味着后端不能只提供“增删改查”接口还要处理状态流转合法性。比如辅导员只能审核自己负责的学生学生不能多次提交已经处于审批中的相同时间段申请驳回后学生应该能修改并重新提交。这些都算业务规则写死了之后系统才像一个管理系统而不是一张可编辑的 Excel。1.2 为什么选前后端分离而不是传统单体模板如果你以前写过 SSM 或 SpringBoot 配合 Thymeleaf 的项目会发现页面嵌套在后端模板里虽然开发链路短但有一个很实际的问题前端的菜单权限、路由跳转、数据局部刷新都要和后端模板语法耦合在一起。做毕设或小团队项目时页面一旦想在打包后独立部署到 Nginx就会很别扭。SpringBoot Vue3 的前后端分离方案最大的优势是后端只负责提供 RESTful API前端只负责页面渲染和数据交互。后端可以独立测试接口前端可以用 Mock 数据先开发页面。部署时前端构建成静态资源目录后端打成 Jar 包两侧可以分开扩容也可以统一由 Nginx 托管。选择这套组合还有一层考虑Vue3 目前已经成为 Vue 生态的主流版本配合 Vite 做开发服务器热更新速度比 Vue2 Webpack 时代快很多。Element Plus 这类 UI 库也以 Vue3 为基础重新实现做后台管理页面时可以省去大量样式和组件开发时间。SpringBoot 则还是 Java 后端最主流的企业级框架教程、社区资料、问题解决方案都足够多遇到报错基本都能搜到对应解法。所以这个选题对学习和实际交付都比较友好技术栈有市场认可度资料量充足开发效率也能满足单人完成完整系统的时间成本。2. 后端核心设计从表结构到审批状态机2.1 数据表先设计好写代码才不会反复返工后端开发里最怕的不是接口复杂而是表结构没考虑清楚就开始写实体类和 Mapper。到后面接口联调了发现字段不够用、状态位表达不了业务含义再改表就是动全局。请假管理系统的表结构不需要特别多但每一张表的职责要边界明确。最基础的用户相关表至少要包含学生、教师/辅导员、学院这样的核心信息。如果用 Spring Security 或 Sa-Token 做权限还要考虑用户角色表或直接用角色字段区分。常见做法是维护一张用户表通过 role 字段区分 student、teacher 或 dean再通过学院ID、班级ID等字段建立数据归属关系。请假相关的表至少要覆盖这几类信息请假单主表、审核记录表。请假单主表保存每个学生的一次请假申请包含学生ID、请假类型、开始时间、结束时间、时长、事由、证明材料路径、当前状态、提交时间等。审核记录表保存每一次审批动作包含审核人ID、审核意见、审核结果、审核时间、审核层级。审核记录表的价值在于系统里需要能看到“谁在什么时间把这个请假单从待辅导员审批改成了待学院审批”而不是只能看到最终状态。为了统计方便可以再单独维护班级和学院的基础表或者直接从学生表的关联字段里查。多数课程设计级别的项目不需要把维度拆得特别复杂保持“学生属于班级、班级属于学院”的简单模型就足够。真正要注意的是不要把班级、学院的信息全部打到请假单里否则修改班级名称或学院名称时就要连带改历史请假记录后续容易出数据一致性问题。2.2 核心表结构示例下面给一个适合毕设和中小型管理系统起步的 MySQL 表设计示例。实际落地时可以根据需求增减字段但核心字段思路可以参考。CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(64) NOT NULL COMMENT 登录账号, password VARCHAR(128) NOT NULL COMMENT 加密后的密码, real_name VARCHAR(64) COMMENT 姓名, role VARCHAR(20) COMMENT 角色: student/teacher/dean, student_no VARCHAR(32) COMMENT 学号,学生角色使用, college_id BIGINT COMMENT 学院ID, class_id BIGINT COMMENT 班级ID,学生角色使用, phone VARCHAR(20) COMMENT 联系电话, status TINYINT DEFAULT 1 COMMENT 账号状态: 1启用 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE leave_application ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 请假单ID, student_id BIGINT NOT NULL COMMENT 学生ID, apply_no VARCHAR(32) COMMENT 申请编号, leave_type VARCHAR(20) COMMENT 请假类型: sick/personal/other, start_time DATETIME NOT NULL COMMENT 开始时间, end_time DATETIME NOT NULL COMMENT 结束时间, total_days DECIMAL(5,1) COMMENT 请假时长(按天), reason TEXT COMMENT 请假事由, attachment_path VARCHAR(255) COMMENT 证明材料路径, status VARCHAR(20) DEFAULT PENDING_TEACHER COMMENT 状态: PENDING_TEACHER/PENDING_DEAN/APPROVED/REJECTED/WITHDRAWN, current_approver_id BIGINT COMMENT 当前待审核人ID, reject_reason VARCHAR(255) COMMENT 驳回原因, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE leave_approval_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 记录ID, leave_id BIGINT NOT NULL COMMENT 请假单ID, approver_id BIGINT COMMENT 审核人ID, approver_name VARCHAR(64) COMMENT 审核人姓名, action VARCHAR(20) COMMENT 动作: SUBMIT/APPROVE/REJECT/WITHDRAW, from_status VARCHAR(20) COMMENT 审核前状态, to_status VARCHAR(20) COMMENT 审核后状态, comment VARCHAR(255) COMMENT 审核意见, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );表设计里最容易忽略的是current_approver_id和from_status这种过渡字段。如果不记录“当前该谁审核”学生端查看进度时就只能靠前端根据状态翻译下一步人是谁一旦流程调整前端也要跟着改。如果把当前审核人字段持久化下来审核列表就可以直接按“当前待我审核”查询不需要写复杂的跨表判断。2.3 请假状态流转要集中控制不要散落在业务代码里请假状态是系统里最容易失控的部分。如果每个接口里都写 if status 等于什么再执行什么等接口写多了很容易出现两个入口都能修改状态的情况。比如学生发起申请后能撤销教师审核通过后又能撤销最后就不知道哪个状态是最终状态。更稳妥的做法是把状态流转收敛到一个独立的服务里。例如在 Service 层中通过一个统一方法处理请假单状态变更同时插入一条审核记录。这样每次变更都有据可查后续想加邮件通知、站内消息只需要在状态流转方法里加逻辑即可。这里建议把状态和可执行动作之间的关系理清楚当前状态可执行操作下一状态PENDING_TEACHER辅导员通过PENDING_DEAN 或 APPROVEDPENDING_TEACHER辅导员驳回REJECTEDPENDING_TEACHER学生撤销WITHDRAWNPENDING_DEAN学院领导通过APPROVEDPENDING_DEAN学院领导驳回REJECTEDAPPROVED不允许修改无REJECTED学生重新提交PENDING_TEACHERWITHDRAWN无无如果不需要学院审批辅导员通过后可以直接置为 APPROVED。但建议不要把层级写死。更合理的做法是维护一个系统参数例如“是否需要二级审批”辅导员通过时判断这个参数再决定下一状态。这样不同学院的流程可以不同不用改代码。3. 权限认证JWT 与路由权限的配合3.1 后端接口不能只靠“前端不显示按钮”来控制权限前后端分离项目最常遇到的权限问题是前端把某个按钮隐藏了但接口还是能被直接调用。实际上只要有人用 Postman 或浏览器控制台模拟请求就能绕过按钮直接访问接口。所以权限控制必须以后端为准前端隐藏按钮只是体验优化不是安全边界。请假系统建议使用 JWT 做登录态认证。用户登录成功后后端签发一个携带用户ID、角色、有效期等信息的 Token前端把 Token 存在本地每次请求在请求头中带上。后端通过拦截器或网关统一解析 Token把用户信息放进去Controller 里只关注业务逻辑。实际开发时可以定义一个CurrentUser之类的注解或直接通过 ThreadLocal 保存当前登录用户信息。这样在每个接口里就能很方便地拿到当前用户ID和角色然后做数据范围控制。例如辅导员查询请假列表时不能查全校数据只能查student_id属于自己管理范围的请假单。这个过滤条件不应该依靠前端传班级ID而应该由后端根据当前登录人自动推导。3.2 管理端和移动端的设计差异高校请假管理系统通常有两种前端形态一种是管理后台供辅导员和学院领导使用页面大、菜单多、信息密度高另一种是学生使用的 H5 或 Web 端页面核心操作路径很短提交申请、看状态、撤销申请。如果你用 Vue3 开发建议把这两种形态拆成两个前端入口而不是硬塞在同一个后台模板里。因为学生的“我的请假”页面和辅导员的“待办审批”页面信息结构差别很大共用一套布局会带来很多不必要的样式和权限判断。当然如果不是移动端项目而是纯 Web 管理系统也可以只做一套系统通过不同的菜单权限来区分。学生登录后只显示“提交请假”“我的申请”“消息通知”等菜单教师登录后显示“待办审批”“请假记录”等菜单。判断依据是用户角色属于典型的 RBAC 权限模型。4. 前端实现重点Vue3 项目怎么搭才稳4.1 项目初始化与目录结构的工程化Vue3 项目建议直接使用 Vite 创建。Vite 开发服务器启动快构建速度也快更适合前后端分离的场景。执行下面命令就能创建npm create vitelatest leave-admin -- --template vue cd leave-admin npm install基础依赖至少需要这些Vue Router处理页面路由Pinia替代 Vuex 的全局状态管理Axios请求后端接口Element Plus后台管理组件库一个稳妥的目录结构可以这样组织src/ api/ # 按业务模块封装的接口请求 assets/ components/ # 通用组件 layout/ # 后台主布局 router/ # 路由定义 stores/ # Pinia 状态 utils/ # axios 实例、工具函数 views/ student/ # 学生端页面 teacher/ # 教师端页面 dean/ # 学院领导端页面 login/ dashboard/前端最容易乱的地方是 api 请求散落在页面组件里。每个页面直接写axios.get(/leave/list)到最后后端接口改名时到处都要改。应该先把所有请求方法集中到 api 目录页面组件只调用对应的方法。axios 实例也要单独封装统一设置 baseURL、请求头、超时时间、响应拦截器。4.2 登录状态和路由守卫生效登录页拿到 Token 后不能只存起来就跳转。还要获取用户信息包括用户角色、姓名、学院班级等然后根据角色生成可访问的菜单和路由。这时的关键点在于路由守卫。Vue Router 提供了全局前置守卫每次跳转前判断是否有 Token。没有 Token 就跳登录页有 Token 再去请求用户信息或从 Pinia 里读取已有信息。// router/index.js 示例 router.beforeEach(async (to, from, next) { const token localStorage.getItem(token) if (!token) { if (to.path /login) { next() } else { next(/login) } return } if (to.path /login) { next(/) return } const userStore useUserStore() if (!userStore.userInfo) { try { await userStore.fetchUserInfo() } catch (e) { localStorage.removeItem(token) next(/login) return } } next() })路由权限有两种做法。一种是前端把全部路由静态定义好跳转时动态判断当前用户角色是否有访问权限。另一种是后端登录后返回该用户可访问的菜单列表前端动态注册路由。课程设计阶段用前一种更简单把路由元信息里加上 roles 字段就行正式项目或想体现技术深度时用后一种能更接近若依这类企业级框架的思路。需要注意的是动态添加路由在 Vue Router 4 里用router.addRoute实现。刷新页面后动态路由会丢失所以在刷新时要重新拉取用户信息和路由表。这个坑几乎所有 Vue3 后台项目都会遇到一定要处理。4.3 学生端核心页面的交互设计学生端最主要的页面是“发起请假申请”和“我的申请列表”。请假申请表单必须做前后端双重校验。开始时间不能晚于结束时间不能选择过去时间请假时长要能自动计算。证明文件如果要上传建议先传到后端指定的文件存储目录后端只保存文件路径而不是把文件以 Base64 字符串形式提交到业务接口里。我的申请列表建议用表格分页展示每一行显示状态标签、申请时间和操作按钮。操作按钮根据状态显示待辅导员审核显示“撤销申请”被驳回显示“修改并重新提交”已通过只读无操作这里要处理一个细节本地时间和后端时间可能有时区差异。建议前后端时间统一使用时间戳或标准字符串传递后端按统一时区处理。否则会出现前端显示“2025-06-01 10:00”提交后后端校验发现早于当前时间的情况。4.4 教师端审批列表怎么高效看数据教师端最关心的不是单个申请而是“有多少待办、哪些学生的申请快到期、哪些申请占用了上课时间”。所以列表页的设计要比学生端更注重信息筛选。审批列表至少要支持按状态筛选、按请假类型筛选、按班级筛选。默认加载“待我审核”数据用红色圆点或数字角标提示待办数量。列表里的关键字段是学生姓名、班级、请假时间、请假时长、请假原因、提交时间、当前状态。因为辅导员审核时经常要判断学生是不是有课程冲突所以如果系统里能接入课程表数据可以在审批弹窗里展示该时间段的课时会更实用。课程表数据会明显增加设计和开发量。如果时间有限可以在请假申请时由学生填写“是否影响上课”和“已向任课老师说明情况”用文本记录代替课表联动。这个替代方案不算最优但优点是能快速上线不会因为课表导入问题拖慢整体进度。5. 联调、部署与排查顺序5.1 接口联调最容易出错的几个点前后端分离项目的联调阶段很多时间都耗在不是业务逻辑本身的问题上。下面几类问题我刚做这种项目时也都遇到过。第一是跨域问题。前端开发服务器默认跑在 5173 端口后端接口跑在 8080 端口从前端地址向后端发请求属于跨域。解决方式可以是后端配置 CORS也可以在前端 Vite 配置 proxy 代理。个人建议在开发环境用 Vite proxy在后端不要直接放开所有来源的跨域。因为生产部署时通常是 Nginx 托管前端再把/api路径反向代理到后端服务跨域问题根本不存在。Vite 代理配置示例// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求/api/leave/list开发服务器会把请求转发到http://localhost:8080/api/leave/list。开发阶段后端接口路径如果都带/api前缀生产部署时也方便做 Nginx 路径转发。第二是请求体格式不一致。前端 Axios 默认 POST 请求体是 JSON但如果有人手动设置了application/x-www-form-urlencoded后端用 RequestBody 接收就会报错或拿到空对象。建议前端在封装 Axios 时统一设置请求头后端统一用 RequestBody 接收 JSON 对象不要一套系统里混用表单和 JSON。第三是 Long 类型精度问题。后端表主键如果使用雪花算法或自定义生成的 Long ID超过 JavaScript 安全整数范围时前端拿到的 ID 会出现精度丢失。请假系统如果只是 MySQL 自增主键ID 不大可以忽略。但如果你用的 ID 生成策略会生成 19 位长整数就要在后端序列化时处理成字符串避免前端编辑、删除时 ID 对不上。5.2 数据库连接和启动参数要注意什么SpringBoot 的版本选择会直接影响配置方式。SpringBoot 2.x 和 3.x 在部分依赖上有差异尤其是 javax 到 jakarta 的命名空间变更。如果只是做毕设或学习建议使用相对成熟的 SpringBoot 2.7.x 配合 JDK 8 或 JDK 11网上能找到最多的资料。如果要用 SpringBoot 3.x就必须用 JDK 17 及以上MyBatis-Plus 等依赖也要切换到支持 jakarta 的版本。配置文件里至少要把 MySQL 连接参数、MyBatis 的 mapper 扫描路径、JWT 密钥和过期时间、文件上传目录设置好。不要用默认的 root 空密码连接数据库也不要把密码写在代码里。课程设计项目虽然简单但养成用配置项管理外部参数的习惯没有坏处。启动后如果要验证接口是否能直接访问可以在后端配置类里临时放行 Swagger 或 Knife4j 的路径但部署时要记得关闭接口文档的访问权限避免接口结构暴露。5.3 Nginx 部署流程前端打包后生成 dist 目录里面是静态资源。可以把 dist 里的内容复制到 Nginx 的 html 目录然后配置反向代理。server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }Vue3 使用 history 路由时刷新子页面会出现 404原因是 Nginx 找不到对应的真实文件。解决方式就是try_files $uri $uri/ /index.html这个配置没有实际文件时统一返回 index.html让前端路由接管。前后端分离部署中几乎必然遇到这个问题第一次用 Nginx 部署时可以先拿这条配置做基准。后端 Jar 包可以使用java -jar leave-system.jar直接启动。长期运行建议配置成 systemd 服务这样服务器重启后能自动拉起进程。日志输出也要单独指定到文件目录方便排查问题时查看堆栈。5.4 出现问题时按什么顺序排查开发这套系统时如果你遇到页面打不开、接口返回异常或流程状态不对的情况不要急着改代码按下面的顺序排查会更快。先看浏览器控制台和 Network 面板确认请求是否发出、请求地址是否正确、响应状态码是多少。如果请求都没发出大概率是前端路由守卫或 Axios 封装的问题。再登录服务器或本地控制台看后端日志。SpringBoot 自带日志输出到控制台如果你配置了 logback 输出文件就直接查看日志文件。数据库异常、空指针、参数绑定错误在日志里都会有完整堆栈。很多新手不看堆栈只拿着页面上的“系统错误”到处问效率很低。如果接口返回 401 或 403优先检查 Token 是否过期、请求头中是否带了 Token、后端的拦截器是否把该接口放行了。如果返回 500再看异常信息里指向的是 Mapper SQL、Service 逻辑还是参数转换。如果是流程状态不对比如辅导员通过后学生端状态没变化先查后端数据库里的leave_application.status到底是什么值。有可能是状态字段更新成功但前端缓存了旧数据也可能是状态更新的 SQL 条件写错导致根本没有执行更新。直接看数据库可以快速区分是接口问题还是前端展示问题。部署到 Linux 后还要注意端口是否被占用、防火墙是否放行、MySQL 是否允许远程连接。这三个问题在本地开发时不会出现但一部署就暴露。6. 从毕设到真实项目的几个优化方向如果这个系统只是课程设计或毕设做到这里已经可以交付了。但如果想把它写进简历或者以后还要继续迭代有四个方向值得继续深入。第一个方向是完善消息通知能力。请假申请提交、审核通过、被驳回这些关键节点都可以通过站内信、邮件或企业微信机器人通知到相关人员。实现方式并不复杂在状态流转服务里发送异步消息即可但能明显提升系统的完整度也能在面试或答辩时讲出“业务闭环”的感觉。第二个方向是引入工作流引擎。当审批层级从单级变成多级、从固定流程变成可配置流程时手写状态流转逻辑会越来越难维护这时可以考虑 Flowable 或 Activiti 这类工作流引擎。SpringBoot 整合 Flowable 的资料现在很多但工作流引擎的学习成本不低需要先掌握流程定义、流程实例、任务节点等概念再结合请假业务设计 BPMN 流程。不建议零基础直接引入先把手写状态流转的项目做完再在重构时考虑引擎化会更合适。第三个方向是文件上传改造。把请假证明的图片或附件存到本地磁盘在单机部署时没有问题。但如果有多个后端实例做负载均衡或者需要统一备份本地文件就会变成棘手问题。这时可以改成对象存储或自建 MinIO把文件路径存在数据库里。这类改造在工作中的通用性很强。第四个方向是审批数据的统计分析。学院领导通常需要看到缺勤率、请假类型分布、近期请假趋势。统计报表可以在后端按时间段聚合查询再返回给前端也可以让前端承接一部分可视化工作使用 ECharts 展示柱状图、折线图和饼图。把统计维度做清楚功能价值会明显高于普通增删改查页面。7. 开发顺序与时间分配建议最后给一份按单人开发节奏估算的开发顺序适合参考不是标准答案。第 1 阶段设计表结构、确定接口文档约 1 到 2 天。这个阶段不建议跳过快思考表结构不清晰后面写接口和页面都会反复改。第 2 阶段后端基础框架搭建包括统一返回类、异常处理、登录鉴权、公共 CRUD约 2 到 3 天。第 3 阶段学生请假核心接口包括提交、撤销、分页查询约 1 到 2 天。第 4 阶段审批接口与审批记录包括辅导员通过/驳回、学院领导通过/驳回、审批列表查询约 2 到 3 天。第 5 阶段前端工程初始化、登录页、路由守卫和基础布局约 2 天。第 6 阶段学生端请假页面和申请列表约 2 到 3 天。第 7 阶段教师端审批列表和审核弹窗约 2 到 3 天。第 8 阶段联调、修复边界问题、部署上线约 2 到 3 天。这套进度适合已经对 SpringBoot 和 Vue3 基础语法有了解的人。如果两边都还不熟需要先增加学习时间不要直接照着系统代码边查边写那样容易陷入“不知道自己在写什么但项目又能跑”的状态答辩或面试时最怕这种。真正常踩的坑我再说一次后端接口的权限校验不要只写在注释里前端表单校验不能代替后端校验状态字段不要用含义不清的 0 和 1 表示所有状态部署到服务器后先看日志再改配置。把这些做到了这套前后端分离的高校请假管理系统就算真正达到了能够稳定运行、能讲清楚设计思路、能继续扩展改进的程度。