news 2026/9/13 16:21:02

SpringBoot+MyBatis+Vue学生请假系统设计与全栈实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+MyBatis+Vue学生请假系统设计与全栈实现

简介:这是一套基于Spring Boot的学生网上请假系统完整代码,面向计算机、电子信息等专业的学习者,尤其适合需要完成毕业设计、课程设计或期末大作业的同学。项目采用B/S架构与MVC分层,集成Mybatis、Vue、Ajax等主流技术,包含学生请假申请、审批管理、个人信息维护等典型模块,能够帮助读者理解前后端分离开发与数据库设计流程。压缩包共399个文件,以Java源码、Vue组件、SVG图标为主,配套XML和YML配置、SQL脚本、启动脚本,以及PDF与Word说明文档,整体大小约9.14MB,结构清晰便于检索。目前已有117人学习下载,代码经过严格测试,可直接导入IDEA、Eclipse或MyEclipse运行,也可参考其中的接口实现和页面交互方式。对于需要完整请假系统范例的开发者,这套代码提供了清晰的模块划分、可复用的业务逻辑和前端组件,具有较强的参考与二次开发价值。

1. 学生请假审批链路上的SpringBoot角色分担

在学生请假这类教务场景里,需求永远不算复杂,但牵扯的角色和状态却不少:辅导员要确认课程安排,学生要关心审批进度,学院还想要统计。真把它做成一版能跑通闭环的系统,后端用SpringBoot扛接口、用MyBatis管数据库操作,前端用Vue负责页面交互,是当前课程设计里最稳的搭配。这套基于SpringBoot的学生网上请假系统资源正是这样的完整代码包,解压后能直接在IDEA里编译运行,覆盖了学生登录、填写请假单、教师审批、进度查询、撤销申请这几条主线。它适合两类人:准备毕设答辩的计算机和电子信息专业学生,以及刚学完SpringBoot想找一份有完整业务链路的工程来拆解的开发者。下面拆的是这份代码里真正值得借鉴的落点。

2. 环境基线、数据库表设计与MyBatis映射

2.1 JDK1.8与Maven3.6组合下依赖如何落位

资源描述里写得明确:JDK1.8、Maven3.6、MySQL5.7,SpringBoot版本对应的是2.x系列。这个组合放到2024年依然能跑,但有个新手高频坑是“springboot版本太高”——SpringBoot 3.x强制要求JDK17以上,如果按IDEA默认的Spring Initializr新建项目,拉下来的往往是3.2、3.3这类新版本,代码里javax包会整体变成jakarta,MyBatis相关starter也会大量报错。因此在这套代码里,pom.xml的父依赖建议锁定在2.7.18。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>

锁定逻辑在于让SpringBoot的父POM统一管理依赖版本,mybatis-spring-boot-starter只管声明自己的版本,不直接写传递依赖版本号。这样能减少启动时因jar包版本不一致导致的Bean实例化失败。Maven3.6和Maven3.8在这套资源上都能编译,如果学校或公司网络对Maven中央仓库访问慢,settings.xml只保留阿里云镜像,避免多镜像串行等待把构建拖到超时。

2.2 三张核心表:学生、教师、请假单

整套请假系统的业务闭环围绕三个角色展开:学生、教师、系统管理员。落到MySQL里,最少需要三张表支撑。资源包里如果没有现成的建表SQL,依照下表的字段结构自己落地也不难。

表名主要字段作用
studentid, student_no, name, password, class_name, phone学生登录与身份信息
teacherid, teacher_no, name, password, department教师登录与所属学院
leave_infoid, student_no, course_name, leave_type, reason, start_time, end_time, status, create_time请假单主流程

status字段用tinyint存储:0待审批、1已通过、2已驳回、3已撤销。用数字而不用字符串的原因有两个:一是建索引更紧凑,二是Java枚举可以直接用常量对应,省去一层转换。我建议后端定义LeaveStatus枚举类,禁止在Service代码里直接写魔法数字,这样后续改状态名只需要动枚举。一个常见的反面写法是在XML映射里硬编码status = 1,这一处在代码审查里一眼就能被挑出来。

建表时还要注意,请假的“课程名”和“班级”冗余在leave_info里。这是因为该场景只需要知道哪门课、多长时间、什么原因,不需要独立的课程表和班级表。如果后续要扩展“同班学生请假统计”,则另外建class和course表,用外键关联即可,改动面集中在Mapper层。

2.3 字符集与查询索引的调整

第一次导入数据库脚本后,建议先执行两段调整SQL。第一段把所有表默认字符集统一为utf8mb4,第二段给常用查询字段加索引。

ALTER TABLE leave_info CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE leave_info ADD INDEX idx_student_no (student_no); ALTER TABLE leave_info ADD INDEX idx_status (status); ALTER TABLE student ADD UNIQUE KEY uk_student_no (student_no); ALTER TABLE teacher ADD UNIQUE KEY uk_teacher_no (teacher_no);

请假列表页最常见的查询是WHERE student_no = ? ORDER BY create_time DESC,在没有索引的情况下,随着数据量增长,MySQL会走全表扫描,页面响应时间会从几十毫秒涨到几百毫秒。加联合索引时要考虑到列表页还有教师端“按状态筛选全部请假单”的场景,因此status也单独建索引。student_no加唯一索引能同时起到账号唯一约束和查询加速的双重作用。做完这两步后,用EXPLAIN SELECT * FROM leave_info WHERE student_no = '2021001'验证一下,type列从ALL变为ref,说明索引生效了。

2.4 Mapper层XML的动态SQL写法

这个项目的MyBatis映射文件放在resources/mapper目录下,以“实体名Mapper.xml”命名。以请假条件查询为例,典型写法是:

<select id="selectLeaveList" resultType="com.example.leave.entity.LeaveInfo"> SELECT id, student_no, course_name, leave_type, reason, start_time, end_time, status, create_time FROM leave_info <where> <if test="studentNo != null and studentNo != ''"> AND student_no = #{studentNo} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY create_time DESC </select>

<where>标签会自动去掉子句开头的AND,避免拼出WHERE AND student_no = ?这种语法错误。这是MyBatis动态SQL里最常用、也最容易在手工拼接时出错的地方。#{studentNo}最终走PreparedStatement预编译,能挡住SQL注入;不要用${}直接拼字符串,尤其当参数来自前端输入时。接口定义只需一行声明对应上SQL id即可,Service层拿到List后做业务处理,Controller层只做参数校验和结果包装。分层拆开看,这个项目的访问边界很快就清楚了。

3. SpringBoot后端请假审批接口的实现细节

3.1 登录鉴权与角色路由设计

学生和教师共用同一套登录入口,后端根据role字段路由到不同表查询。这种设计比建一张统一的users表加role区分更直观,但代价是后续扩展管理员时要再写一套查询逻辑。代码里一般用LoginController接收username、password、role三个字段,校验通过后返回全局唯一标识。

@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private StudentService studentService; @Autowired private TeacherService teacherService; @PostMapping("/login") public R login(@RequestBody LoginDTO dto) { if ("student".equals(dto.getRole())) { Student stu = studentService.login(dto.getUsername(), dto.getPassword()); if (stu == null) { return R.error("账号或密码错误"); } String token = UUID.randomUUID().toString().replace("-", ""); return R.ok().put("token", token).put("role", "student").put("user", stu); } Teacher teacher = teacherService.login(dto.getUsername(), dto.getPassword()); if (teacher == null) { return R.error("账号或密码错误"); } return R.ok().put("token", UUID.randomUUID().toString()).put("role", "teacher"); } }

这段代码有两点值得注意。第一,密码比对若发现数据库里存的是明文或MD5,答辩前建议至少升级为BCrypt,引入spring-security-crypto依赖,注册时用BCryptPasswordEncoder.encode存储,登录时用matches比对。第二,UUID作为token没有设置过期时间,服务重启后token失效,前端只能重新登录。这是当前资源能跑通但不够健壮的地方。你可以在一小时内给它加上expire_time字段,在拦截器里校验当前时间,改动面非常小。

3.2 请假提交、审批与撤销的状态机

请假流程的核心是状态机,关键在于“状态只在合法路径上流转”,否则会出现重复审批或撤销后仍被通过的问题。建议在代码里列一个状态流转约束表,既是编码依据,也是答辩时的图表素材。

操作前置状态后置状态
学生提交申请新建无状态待审批(0)
教师通过待审批(0)已通过(1)
教师驳回待审批(0)已驳回(2)
学生撤销待审批(0)已撤销(3)

Service层里审批方法的实现一般长这样:

public void approve(Integer id, Integer passFlag) { LeaveInfo info = leaveMapper.selectById(id); if (info == null) { throw new BusinessException("请假单不存在"); } if (!Integer.valueOf(LeaveStatus.APPROVING.getValue()).equals(info.getStatus())) { throw new BusinessException("当前状态不可审批"); } if (passFlag == 1) { info.setStatus(LeaveStatus.PASS.getValue()); } else { info.setStatus(LeaveStatus.REJECT.getValue()); } leaveMapper.updateById(info); }

审批前必须“先查后改”,这是状态机的最基本保障。当前实现是线程不安全的,因为两个教师同时审批同一条记录时,后提交的一方会用失效的status覆盖。升级方案是把update语句改成UPDATE leave_info SET status = #{newStatus} WHERE id = #{id} AND status = 0,用影响行数判断是否竞态。答辩时能讲出这个差异,暴露的是真实的并发意识。

等待审批时为什么要复用同一个leave_info表而不拆做到期的记录?这个事表格设计阶段是合理的——需求里没有批假会签、没有课表冲突检测,多一张表反而让MyBatis的联表和事务管理都变得复杂。保留边界是这一部分真正想表达的点。

3.3 统一返回结构与异常嵌套

这套资源的Controller层遵循统一返回体模式,前端所有请求都拿{code, msg, data}结构。好处是前端判断逻辑收敛到一处,不必每个接口单独做错误分支。常见实现如下:

public class R extends HashMap<String, Object> { public static R ok() { R r = new R(); r.put("code", 200); r.put("msg", "success"); return r; } public static R error(String msg) { R r = new R(); r.put("code", 500); r.put("msg", msg); return r; } }

配套的全局异常处理器用@RestControllerAdvice捕获Service层抛出的BusinessException。这样做有一个实际好处:Service中写throw new BusinessException("库存不足")不必担心Controller层漏接,用@ExceptionHandler统一定义返回格式即可。多参数校验时,直接在实体字段上加@NotBlank注解,配合@Valid就能在校验失败时自动落入异常处理器,不会进入到业务逻辑内部。

这里的职责边界是:Controller只做参数收口,Service只做业务流转,Mapper只做数据访问。把这三层分开后,“哪段代码出问题先看哪层”的定位逻辑对新手调试非常友好。

4. Vue前端组件、Ajax请求与批处理运行脚本

4.1 vue.bak备份文件里的组件资产

解压代码包后,能看到update-password.vue.bak、IndexAsideStatic.vue.bak、BreadCrumbs.vue.bak、IndexHeader.vue.bak这类文件。它们是开发者在调试时手动改出来备份的,真正参与编译的是没有.bak后缀的.vue文件。很多人在IDEA里看到一堆.bak,误当成源码,改了一通之后发现页面没变化,其实改错了文件。正确的做法是打开src/views目录下的同名.vue,.bak留在原地备查。

IndexHeader.vue是后台布局的顶部栏,BreadCrumbs.vue是面包屑,IndexAsideStatic.vue对应左侧导航菜单。从命名可以看出,这套前端是标准的单页后台管理布局。想做差异化展示,直接在IndexAsideStatic.vue里改el-menu-item的标题和图标即可,不需要动路由表。这里用原生el-menu渲染侧边栏,新增菜单项时要同步检查router里的对应路由是否存在,否则点开会白屏。

4.2 Axios请求封装与拦截器

页面中的所有Ajax请求都通过axios发出。为了避免每个组件里重复写token注入和错误提示,一般采用统一封装方式。下面是一份可直接搬进项目的request.js简化版本:

import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['token'] = token } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { alert(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res }, error => { alert('网络异常,请检查后端服务是否启动') return Promise.reject(error) } ) export default request

这段代码把三件公共事务集中处理:请求头token自动注入、响应统一解包、业务错误统一提示。业务页面只调用request.get('/leave/list')request.post('/leave/submit', formData),网络细节被完全隔离。后续如果要把alert换成ElementUI的Message组件,只需改这一个文件。

4.3 install、run、build三个批处理脚本拆解

压缩包里自带1-install.bat、2-run.bat、3-build.bat三个脚本,运行顺序是固定的:先install,再run,最后build。不少首次使用的人把3-build.bat当成启动项目入口,点完没反应,实际它是给前端打包生产文件的。拆开看每个脚本的真实内容:

REM 1-install.bat 安装前端依赖并编译后端 @echo off cd /d %~dp0 call npm install call mvn clean package -DskipTests pause

install脚本做了两件事:npm install把Vue项目依赖拉到本地node_modules,mvn package把SpringBoot代码打成jar并跳过测试。如果本机缺少前端依赖或Maven本地仓库缓存,报错会集中在这一阶段。npm install失败时先检查node版本,Vue2项目建议Node14到Node16,Node18以上容易触发OpenSSL错误,解决办法是设置NODE_OPTIONS=--openssl-legacy-provider

REM 2-run.bat 启动SpringBoot后端服务 @echo off set JAVA_HOME=D:\JDK1.8 path %JAVA_HOME%\bin;%path% java -jar target\xxx.jar pause

run脚本里的set JAVA_HOME需要改成你机器上的JDK实际路径,如果本机只装了一个JDK,也可以直接写java -jar。改错路径或路径中含空格都可能导致脚本闪退。这个窗口启动后不能关,因为JVM是前台运行的,关掉窗口后端服务就停了。

build脚本一般用来执行前端构建,生成供Nginx或Tomcat托管的dist目录。如果你在Windows上双击脚本没有反应,可以打开cmd手动执行,方便看到每一行命令的实际输出。

4.4 联调时的跨域代理与后端配置

前后端分离开发时,前端页面在8081端口、后端在8080端口,直接请求会有跨域问题。Vue项目里最省事的方案是vue.config.js中加devServer代理:

devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

含义是所有以/api开头的请求,在开发服务器上转发到后端的8080端口,浏览器的同源限制在代理层就规避了。前端代码里请求地址必须写相对路径/api/xxx,不能写http://localhost:8080/api/xxx,否则代理不生效。后端对应application.yml里做基础配置:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/class_leave?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.leave.entity

url里的serverTimezone必须设置,否则MySQL驱动会报时区异常。如果你本地实际是MySQL5.7,但连接串体现的是MySQL8的驱动类名,大概率是依赖版本被拉高了,需要回退mysql-connector-java版本。数据库账号密码以你本机为准,首次运行先确认Database名和resources目录下是否有.sql脚本,没有就按前面第二章节的表结构自己建。数据库客户端用SQLyog或Navicat均可,执行脚本前先检查默认字符集是否为utf8mb4,以免中文乱码从头影响到尾。资源描述里提到的Tomcat 8.0/9.0指的是SpringBoot内嵌的Servlet容器,正常代码实际上不需要单独外装Tomcat就能运行;若坚持用外置Tomcat部署,则需要把打包方式改为war并排除spring-boot-starter-tomcat依赖,改动成本不小,不推荐。

5. 启动核对、构建物瘦身与前后端同步脚本

5.1 后端jar启动后先看这三个指标

后端jar跑起来后,先不要急着点前端页面,用接口验证比页面验证更准。执行java -jar target\xxx.jar后,控制台出现“Started Application in 5.2 seconds”才算真正启动完成;只出现“Tomcat started on port(s): 8080”代表Web容器就绪,但Spring上下文可能还没完全初始化。接下来按顺序验证三件事:数据库连通性、登录接口、请假列表查询。

如果登录接口返回500,先看日志里有没有Unknown databaseAccess denied字样,前者是库名写错,后者是账号权限问题。如果请求路径返回404,先确认RequestMapping里的路径和前端调用路径是否一致,注意大小写。如果SQL执行报错,优先检查表中的字段名和Mapper XML里的字段是否一一对应。浏览器兼容上,用Chrome、Edge或Firefox均可,Vue2构建的前端在旧版IE里必然白屏,这一点在演示前一定要确认。

安全细节也不能忽略:SpringBoot如果开启了Actuator监控,management.endpoints.web.exposure.include建议只保留health,不要暴露env和heapdump端点,否则heapdump文件存在被未授权读取的风险,这在真实环境里是有过对应漏洞案例的。

5.2 构建物瘦身与编辑器的小设置

mvn package默认打包出的jar往往是几十MB,因为SpringBoot把全部依赖打进了一个Fat Jar。作为课程设计直接提交完整jar最省事,老师拷到任意一台装有JDK8的机器都能java -jar运行。若需要瘦身,可以改成thin-launcher模式或用-Dloader.path外置依赖目录,但部署复杂度会上升,非必要不动。

编辑器体验方面,一个实用小建议:如果你在WSL Ubuntu里写这套项目,IDE终端字体换成JetBrains Mono或Cascadia Code,缩进和括号对齐的辨识度明显更高,接近macOS上写代码的观感。字体属于个人习惯,但选对等宽字体会让长XML和Vue模板的阅读效率有明显改善。资源包里的必读推荐.docx大概率写了默认账号和配置说明,动手前先打开它,比猜省时间。

5.3 从毕设到可维护的三处增强

建设性的方向是把这套代码推向更真实的工程状态。第一步,把数据库密码、Redis地址和文件路径全部抽到application.yml的profile里,用spring.profiles.active切换开发和生产配置。第二步,给MyBatis的Mapper接口统一添加@MapperScan注解,后续新增Mapper无需反复写扫描路径。第三步,登录鉴权由Spring MVC拦截器或Gateway网关统一处理,替换掉“哪个Controller需要鉴权就手动加”的散弹式写法。完成这三步后,这套系统的可维护性就从“毕设级”提升到了“小型内部工具级”。

如果只是交作业,保持原代码姿态即可,不要为了炫技改得太深。最实用的小改动其实在构建脚本上:在3-build.bat末尾加一行copy /Y dist\*.* E:\leave-front\,构建完自动把前端产物同步到部署目录。这个一次性配置能省去每次手动复制dist的步骤,也算对构建流水线的第一次切身体验。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 16:17:04

MATLAB生成高斯随机粗糙表面:频域滤波原理与参数校准

简介&#xff1a;面向工程与科研场景的 MATLAB 表面粗糙度分析源码包&#xff0c;聚焦基于高斯分布模型的表面形貌数值模拟与参数计算。资源共 4 个 .m 文件&#xff0c;压缩包仅 2KB&#xff0c;核心脚本 zaihe.m 覆盖从数据读取、去噪预处理到高斯拟合及粗糙度参数求解的完整…

作者头像 李华
网站建设 2026/9/13 16:13:30

量化交易入门:Python回测脚手架搭建与MA策略解析

简介&#xff1a;本资源是面向零基础入门者的量化交易Python实践教学包&#xff0c;聚焦数据获取、清洗、分析、策略构建与回测全流程&#xff0c;帮助初学者通过可运行代码理解量化逻辑并动手搭建简易交易系统。压缩包共139个文件&#xff0c;含43个Python脚本&#xff08;覆盖…

作者头像 李华
网站建设 2026/9/13 16:11:50

STM32外部触发DMA+FMC高速数据采集实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华