拓冰建站拓冰建站
首页 / 资讯中心 / 正文

高校请假管理系统实战:SpringBoot+Vue3审批状态机与权限设计

一个高校请假管理系统放在前后端分离的技术栈里容易被当成一个增删改查练习。真正进入开发后你会发现难点不是输出列表而是请假状态怎么流转、审批人权限怎么落、跨域和 Token 怎么统一处理、前端路由怎么和后端接口保持同一套角色逻辑。系统可以用 SpringBoot 提供接口与事务保证由 Vue3 负责表单、路由、状态管理和审批操作两者通过 JSON 交互。这条链路如果只靠界面上一句“能提交能审核”来验证很多细节会在接口联调阶段才暴露出来。这套系统在高校场景下的典型流程是学生登录后提交请假申请辅导员查看所属学生的待审批记录并选择通过或驳回管理员从全校视角查看请假分布和审批情况。做这个项目时建议先不要急着写页面先把角色边界、请假状态、审批记录、前后端接口约定确定下来。下面围绕这套主线从需求拆分讲到表结构设计再讲到 SpringBoot 后端、Vue3 前端、联调验证和常见问题排查。1. 为什么高校请假管理系统适合作为前后端分离实战项目1.1 角色、操作与前端页面需要一一对应高校请假系统常见的角色可以拆成学生、辅导员和管理员三类。学生负责提交请假材料、查看自己的审批结果辅导员处理自己名下学生的请假单给出通过或驳回意见管理员不参与单条请假审批但需要查看全校请假总量、请假类型分布和各班审批情况。角色核心操作前端页面学生提交请假、取消未审批申请、查看审批记录请假申请页、我的假条页辅导员查看所属学生待办、审批通过或驳回审批工作台、已审批列表管理员查看统计报表、按班级或部门筛选数据概览页、用户管理页前后端分离项目里页面结构通常直接反映权限结构。学生在菜单里看不见审批按钮辅导员不需要填写个人请假表单管理员看到的更多是汇总数据。不能只在前端用v-ifuser.role ADMIN控制菜单显示后端接口也要做角色校验。否则学生调用审批接口时后端依然会执行审批逻辑前端隐藏菜单没有安全意义。1.2 审批状态机是整个系统的业务主心骨很多人在实现审批时只在请假表里放一个status字段辅导员点击通过后执行update leave_application set status 2 where id ?。这样写能跑通条线但丢失了审批过程谁在什么时间做了操作、操作前状态是多少、驳回原因是什么、提交后又经过了几次状态变化这些数据没有任何地方可查。稳妥的做法是先定义一套请假状态。学生提交前可以是草稿提交后进入待审批辅导员通过后变成已通过驳回后变成不通过如果学生在待审批状态下撤销则变成已撤销。状态值状态名称说明0DRAFT学生尚未提交只在本地草稿阶段使用1PENDING学生已提交辅导员待审批2APPROVED辅导员审批通过3REJECTED辅导员审批驳回4CANCELED学生撤销了待审批的申请状态机不只代表几个数字。它把“系统允许发生什么操作”固化成代码规则只有PENDING状态可以进入APPROVED或REJECTED只有提交人本人能发起撤销状态更新必须基于上一次版本。每一步状态迁移都要写一条审批记录这样后续要排查问题时可以直接通过假条 ID 和时间范围回溯完整历史。1.3 为什么小额审批场景不建议一上来就引入工作流引擎搜索“请假系统”时经常会看到 Flowable、Activiti 等关键词。这些工作流引擎确实可以处理复杂流转能画流程图、支持会签、加签、多级审批。但对高校请假这种角色相对固定的场景来说引入工作流引擎会带来额外负担需要维护流程定义、部署 BPMN 文件、理解引擎的任务表结构还要确保引擎版本与 SpringBoot 版本匹配。本项目的审批链路主要是“学生提交辅导员审批”数据模型和状态机足够覆盖。用一个approval_record审批记录表记录每次状态迁移比直接引入引擎更容易讲清楚也容易部署。如果需求确实变成“学生提交、辅导员初审、学工处终审、超过五天还要学院盖章”那时再来补充两个待审批状态或用 Flowable 建模会更合适。2. 技术选型与版本对齐先解决 SpringBoot 版本太高的问题2.1 后端选型JDK8 与 JDK17 之间的取舍写 SpringBoot 项目前第一个决定不是代码结构而是选哪个 SpringBoot 版本。SpringBoot 2.7.x 支持 JDK 8适合毕业设计服务器或本地环境只有 JDK 8 的情况SpringBoot 3.x 基于 Jakarta EE必须使用 JDK 17 以上且不再支持旧的javax.servlet包路径。如果本地是 JDK 8 却创建了一个 SpringBoot 3 项目启动时会看到类似UnsupportedClassVersionError或ClassNotFoundException: javax.servlet.Filter的报错这就是“springboot版本太高”最常见的后果。环境条件推荐版本搭配本地或服务器只有 JDK 8SpringBoot 2.7.x JDK 8全新项目无 JDK 版本限制SpringBoot 3.x JDK 17使用 Docker 部署镜像里的 JDK 版本必须和项目编译目标一致MyBatis-Plus 配套如果使用 SpringBoot 3需要选择其针对 SpringBoot 3 的 starterMaven 核心依赖大致如下。正式写代码前建议用 IDEA 或 Maven 确认依赖已解析成功版本冲突经常出现在 Lombok、MyBatis-Plus 和 JDK 一起切换时。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies如果选了 JDK17 SpringBoot 3依赖坐标里不要写旧的javax包要用jakarta.servlet。如果从网上复制一段老代码又会把时间花在编译错误上。2.2 前端选型Vue3 生态尽量使用当前主流组合前端部分采用 Vue3 单页应用使用 Vite 作为构建工具。Vue3 官方文档和命令行创建工具已经默认走 Vite不再像 Vue CLI 那样需要额外维护 webpack 配置。状态管理可以选 Pinia它比 Vuex 更契合 Vue3 的组合式 API。路由用vue-routerUI 组件可以使用 Element Plus。创建项目时用以下命令npm create vitelatest leave-vue -- --template vue cd leave-vue npm install npm install vue-router4 pinia axios element-plus如果后面要写 TypeScript可以在创建时把--template vue换成--template vue-ts。示例项目先按 JavaScript 写方便快速理解。项目创建完成后要统一规划接口返回结构、错误码、请求路径前缀和时间格式这一步比页面布局更重要。2.3 前后端接口约定提前定好避免后期反复改前后端分离项目最大的问题是“各自觉得已经写完了但联调时对不上”。建议代码还没开始写之前先约定以下内容后端所有接口统一返回{ code, message, data }。code 200表示成功常见错误码如401表示未登录或 Token 失效403表示没有权限500表示服务端异常。列表分页参数统一为pageNum和pageSize返回结构为{ total, records }。涉及审批、提交等写操作请求方法使用POST查询使用GET。时间字段统一使用yyyy-MM-dd HH:mm:ss后端不要返回时间数组格式。这个阶段不需要写完整代码但可以把接口路径先列出来例如登录接口、创建请假单接口、审批接口、撤销接口、待办列表接口。前后端分离项目如果路径不统一前端代理层和后端 Controller 映射经常会错位。3. 数据表设计请假流程和审批记录如何沉淀3.1 先明确需要哪几张核心表一张请假单主表只能表达当前状态表达不了历史过程。因此至少需要四张表用户表、学生与辅导员关系表、请假主表、审批记录表。用户表保存用户名、密码哈希、真实姓名、角色和所属部门。学生与辅导员关系表把学生和辅导员对应起来避免在用户表里维护一个逗号分隔的辅导员列表。请假主表保存每次申请的请假类型、起止时间、理由、附件和当前状态。审批记录表保存每次状态变更的完整审计信息。表作用sys_user保存学生、辅导员、管理员的账号与基本资料student_counselor_rel维护学生与辅导员的从属关系leave_application保存请假申请的主数据与当前状态approval_record保存每次提交、通过、驳回、撤销的操作记录3.2 建表 SQL 可以参考这套结构CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT BCrypt 密码哈希, real_name VARCHAR(50) NOT NULL COMMENT 真实姓名, role VARCHAR(20) NOT NULL COMMENT STUDENT/COUNSELOR/ADMIN, department VARCHAR(100) DEFAULT COMMENT 学院或班级, phone VARCHAR(20) DEFAULT , create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE student_counselor_rel ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL COMMENT 学生用户ID, counselor_id BIGINT NOT NULL COMMENT 辅导员用户ID, UNIQUE KEY uk_student_counselor (student_id), KEY idx_counselor (counselor_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生辅导员关系表; CREATE TABLE leave_application ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 申请人ID, leave_type VARCHAR(30) NOT NULL COMMENT SICK/PERSONAL/PUBLIC, start_time DATETIME NOT NULL COMMENT 请假开始时间, end_time DATETIME NOT NULL COMMENT 请假结束时间, day_count DECIMAL(5,1) NOT NULL DEFAULT 1.0 COMMENT 请假天数, reason VARCHAR(500) DEFAULT COMMENT 请假原因, attachment_url VARCHAR(255) DEFAULT COMMENT 附件地址, status INT NOT NULL DEFAULT 1 COMMENT 1待审批 2通过 3驳回 4撤销, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_user_status (user_id, status), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT请假申请表; CREATE TABLE approval_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, leave_id BIGINT NOT NULL COMMENT 请假单ID, operator_id BIGINT NOT NULL COMMENT 操作人ID, action VARCHAR(20) NOT NULL COMMENT SUBMIT/APPROVE/REJECT/REVOKE, from_status INT NOT NULL COMMENT 操作前状态, to_status INT NOT NULL COMMENT 操作后状态, comment VARCHAR(500) DEFAULT COMMENT 审批意见, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_leave (leave_id, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT审批记录表;这个 SQL 把请假状态默认值设为 1也就是学生提交后立即进入待审批。version字段用于防止两个管理员或辅导员同时处理同一张假条时发生覆盖。approval_record表中的from_status和to_status保存状态迁移前后值即使主表状态被覆盖也能从历史表找到当时发生了什么。3.3 状态字段和审批记录表是设计里的关键写请假模块时最容易犯的错误是在 Service 方法里直接把status从 1 改成 2然后不记录任何历史。结果出了问题后既不知道是谁审批的也不知道审批意见是什么。更合理的做法是让状态变更始终伴随一条审批记录。审批通过时需要完成两步修改主表状态为通过往审批记录表插入一条APPROVE记录。两步必须处于同一个数据库事务中否则如果审批记录插入失败主表状态却已经更新用户会看到“已通过”的假条没有对应审批流水这会造成数据不一致。这也是为什么要保留from_status。通过或驳回这些动作不是凭空产生的它们一定是从某个旧状态迁移而来。判断旧状态为PENDING是能进入审批流程的前提条件。使用student_counselor_rel表后辅导员查询待办时只需要执行一个子查询找出该辅导员名下所有学生的请假记录避免辅导员看到全校所有学生的请假单。4. SpringBoot 后端落地认证、请假提交、审批状态机与统计查询4.1 后端项目结构和统一返回类后端项目可以按controller/service/mapper/entity/dto分包也可以按模块分包。对一个中小型请假管理系统来说按技术分层足够结构清晰、排查方便。com.example.leave ├── common │ ├── Constant.java │ ├── Result.java │ └── BizException.java ├── config │ ├── CorsConfig.java │ ├── MybatisPlusConfig.java │ └── WebConfig.java ├── controller │ ├── AuthController.java │ ├── LeaveController.java │ └── StatsController.java ├── dto │ ├── LoginDTO.java │ └── LeaveApplyDTO.java ├── entity │ ├── SysUser.java │ ├── LeaveApplication.java │ └── ApprovalRecord.java ├── mapper ├── service └── util接口统一返回的Result可以这样设计Data public class ResultT { private int code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.message success; r.data data; return r; } public static T ResultT fail(int code, String message) { ResultT r new Result(); r.code code; r.message message; return r; } }业务异常可以用自定义异常抛出然后统一由RestControllerAdvice捕获并转成同样的返回结构。前端 axios 只需要判断code是否等于 200不需要对每个接口单独处理错误结构。4.2 登录认证使用 JWT 保存登录态前后端分离项目里Session 方案需要处理跨域 Cookie 和 CSRF使用起来不如 JWT 直接。登录成功后后端把用户 ID、用户名和角色写入 Token前端在后续请求的Authorization头中携带 Token后端拦截器统一解析。登录接口的核心流程是根据用户名查询用户。用BCryptPasswordEncoder校验密码。校验通过后生成 Token。返回 Token 和用户基本信息。生成 Token 的代码可以是public String generateToken(Long userId, String username, String role) { SecretKey key Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(username, username) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 24 * 60 * 60 * 1000)) .signWith(key, SignatureAlgorithm.HS256) .compact(); }登录接口不需要拦截校验但业务接口需要。最方便的方式是写一个拦截器在preHandle中读取Authorization头解析出用户后放入ThreadLocal形式的UserContext。一个容易忽略的坑是角色信息不能只存前端。前端从本地存储中读取用户角色只是用来渲染菜单和按钮后端接口方法里必须再次从UserContext中获取登录用户角色做权限校验。否则前端把 Token 里的角色伪装成ADMIN时后端所有接口都会失去控制。4.3 创建请假单与提交状态机的实现学生提交请假时接口要完成日期校验、数据组装、插入主表、写入SUBMIT审批流水四件事。整个过程必须在一个事务方法里最简单的方式是在 Service 方法上添加Transactional(rollbackFor Exception.class)。参考实现如下Transactional(rollbackFor Exception.class) public Long applyLeave(LeaveApplyDTO dto, Long userId) { LocalDateTime start dto.getStartTime();
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门