资讯详情

Vue+SpringBoot校园后勤管理系统:BS架构Java毕业设计实战指南

📅 2026/9/27 2:54:41 | 华诺云谱 👁 阅读
Vue+SpringBoot校园后勤管理系统:BS架构Java毕业设计实战指南
简介本资源为基于BS架构的校园后勤服务管理系统毕业设计源码包面向计算机相关专业需要完成毕业设计的学生及Java全栈初学者。项目采用前后端分离方案前端使用Vue.js配合ElementUI构建界面后端基于Java语言与SpringBoot框架通过MyBatis操作MySQL数据源开发工具兼容IDEA与EclipseJDK要求1.8及以上完整实现了用户登录管理等核心模块及数据库设计。压缩包共1122个文件约46.09MB涵盖217个Java源码、117个Vue组件、161个JavaScript脚本、96个HTML页面及52个CSS样式文件另含SQL脚本、配置文件与论文文档结构完整便于二次开发。目前已有84人学习下载。读者可获得一套可直接运行的校园后勤服务管理系统源码、配套论文、数据库脚本与前端静态资源适合作为毕业设计参考模板帮助快速理解SpringBoot与Vue整合开发流程、掌握前后端数据交互与模块化设计思路。1. 校园后勤服务管理系统一套 BS 架构的 Java 毕业设计到底在做什么很多同学拿到「vueSpringBoot 校园后勤服务管理系统」这个题目时第一反应是去搜现成源码结果下回来一堆跑不起来的压缩包或者代码里全是看不懂的注解堆砌。这套系统的本质是把学校后勤处那些靠纸质登记、微信群喊话完成的事——报修、宿舍调换、物资领用、食堂反馈——搬到一个浏览器就能打开的网页里。前端用 Vue 做交互后端用 SpringBoot 提供接口数据落在 MySQL整体是典型的 BS 架构也就是浏览器直接访问、不用装客户端。它适合两类人一是计算机毕业设计选题卡在「管理系统」方向、想找一个业务闭环完整又不至于太冷门的同学二是刚学完 Java 基础、想拿一个真实项目把 SpringBoot 和 Vue 串起来练手的入门者。这套东西能不能用、值不值得做取决于你是否愿意把「跑通」和「讲清楚」当成两件事分别对待。2. 技术选型与架构拆解为什么是 Vue 加 SpringBoot 而不是别的2.1 前后端分离在校园后勤场景下的真实收益校园后勤系统的用户角色很固定学生、宿管、维修工、后勤管理员。这四类人看到的东西完全不同学生只关心「我报的修什么时候来」管理员关心「今天有多少单没派出去」。如果还用传统的 JSP 或者 Thymeleaf 把页面和后端揉在一起改一个按钮颜色都要重新编译整个项目这在毕业设计答辩前反复调样式的阶段是灾难。前后端分离之后Vue 负责所有跟「长什么样」有关的事SpringBoot 只负责「给什么数据」两边通过 JSON 通信。我一般会跟学弟学妹说你哪怕只做三个页面也建议走分离路线因为答辩老师问「你这个架构有什么好处」的时候你能答出「前端可独立部署、后端接口可复用、职责清晰」这比背概念强得多。具体到 Vue 这边用 Vue CLI 或者 Vite 建项目都行核心是搞清楚src/api目录下怎么封装 axios 请求。很多同学把请求直接写在组件里后期接口地址一改就要满项目找这是典型的自找麻烦。正确做法是建一个request.js统一配置 baseURL 和拦截器所有业务请求走独立的 api 文件。// src/utils/request.js import axios from axios const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, // 从环境变量读取方便切换开发/生产 timeout: 10000 }) // 请求拦截器统一带上 token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }, error Promise.reject(error)) // 响应拦截器统一处理后端返回的业务码 service.interceptors.response.use(response { const res response.data if (res.code ! 200) { // 401 表示登录过期跳回登录页 if (res.code 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(new Error(res.message || 请求失败)) } return res }, error Promise.reject(error)) export default service这段代码的关键在拦截器。请求拦截器负责把登录后拿到的 token 塞进请求头后端才能识别「你是谁」响应拦截器负责统一判断后端返回的code如果登录过期就自动踢回登录页不用在每个页面里重复写判断。baseURL用环境变量而不是写死是因为你本地开发时后端跑在 8080部署到服务器可能换了端口写死的话每次都要改代码。2.2 SpringBoot 后端的目录分层与接口约定后端这边SpringBoot 的自动配置帮你省掉了大量 XML但不代表可以乱写。我见过太多毕业设计项目把所有 Controller 堆在一个文件里一个类两千行答辩时老师翻两页就不想看了。合理的分层是controller接请求、service写业务逻辑、mapper操作数据库、entity对应表结构。校园后勤的业务逻辑其实不复杂但「报修单状态流转」这一块值得单独放 Service 里写清楚待接单、已接单、维修中、已完成、已评价每个状态谁能改、改成什么这是答辩时能讲出东西的地方。// controller/RepairController.java RestController RequestMapping(/api/repair) public class RepairController { Autowired private RepairService repairService; // 学生提交报修单 PostMapping(/submit) public Result submit(RequestBody RepairOrder order) { // 从 token 中解析出当前学生 id防止前端伪造 Long studentId SecurityUtils.getCurrentUserId(); order.setStudentId(studentId); order.setStatus(0); // 0 表示待接单 repairService.save(order); return Result.success(提交成功); } // 管理员分页查询报修单支持按状态筛选 GetMapping(/page) public Result page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) Integer status) { PageRepairOrder page repairService.pageQuery(pageNum, pageSize, status); return Result.success(page); } }RequestBody表示接收 JSON 格式的请求体前端 axios 默认就是发 JSON两边对得上。SecurityUtils.getCurrentUserId()是我一般会封装的工具方法从请求头里的 token 解析用户 id这样学生提交报修时不需要在前端传自己的 id避免有人改个参数就能替别人报修。分页查询用 MyBatis-Plus 的Page对象pageNum和pageSize给默认值前端不传也不会报错。状态字段用整数而不是字符串查询效率高但要在代码里写清楚每个数字代表什么不然后期自己都忘。2.3 数据库表设计五张核心表撑起整个后勤业务校园后勤系统不需要几十张表把核心业务跑通五张表足够用户表、报修单表、宿舍表、物资表、反馈表。用户表里用role字段区分学生、宿管、维修工、管理员登录时根据角色跳不同首页。报修单表关联学生 id 和维修工 id状态字段控制流转。宿舍表记录楼栋、房间号、当前住宿学生调换宿舍时更新关联关系。物资表记录库存和领用人。反馈表存食堂或物业的评价。表名核心字段说明userid, username, password, role, phonerole: 0 学生 1 宿管 2 维修工 3 管理员repair_orderid, student_id, worker_id, content, status, create_timestatus: 0 待接单 1 已接单 2 维修中 3 已完成dormid, building, room_no, student_id一个学生对应一条住宿记录materialid, name, stock, apply_user_idstock 为 0 时前端禁用领用按钮feedbackid, user_id, type, content, scoretype 区分食堂/物业/维修建表时注意create_time用datetime并设置默认值CURRENT_TIMESTAMP这样插入数据不用手动传时间。外键约束在毕业设计里可以不建但索引要加比如repair_order的student_id和status字段不然数据量一上来分页查询会明显变慢。3. 从零跑通的最小路径环境、依赖与启动顺序3.1 前端 Vue 项目的初始化与依赖安装拿到源码后第一件事不是急着npm run serve而是先看package.json里的依赖版本。Vue 2 和 Vue 3 的语法差异很大如果项目用的是 Vue 2 的 Options API你装成 Vue 3 的环境页面能打开但交互全失效。我一般会先确认三件事Node 版本、Vue 版本、包管理器。Node 建议用 16 或 18太新的版本有些老依赖编译不过。包管理器用 npm 或 yarn 都行但不要混用混用会导致node_modules里出现重复依赖报一些莫名其妙的错。# 查看当前 node 和 npm 版本 node -v npm -v # 进入前端目录安装依赖 cd frontend npm install # 如果安装卡住或报错先清缓存再重试 npm cache clean --force npm install # 启动开发服务器 npm run servenpm install的过程如果卡在某个包不动大概率是网络问题可以换成国内镜像源。启动成功后控制台会输出本地访问地址一般是http://localhost:8080。这时候如果页面白屏按 F12 看控制台报什么错最常见的是「找不到某个模块」说明依赖没装全重新npm install即可。如果页面能打开但接口请求全部 404那是后端没启动或者代理没配好检查vue.config.js里的proxy配置。3.2 SpringBoot 后端的配置修改与数据库连接后端启动前必须改application.yml里的数据库连接信息这是新手翻车最多的地方。源码里默认写的是作者本地的数据库名和密码你直接启动肯定连不上。把url、username、password改成你自己的数据库名要和你本地建的一致。# application.yml server: port: 9090 # 后端端口注意和前端代理配置对应 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_logistics?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: 你的密码 servlet: multipart: max-file-size: 10MB # 报修时上传图片的大小限制 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL方便调试serverTimezoneAsia/Shanghai这个参数不加插入时间数据时可能差 8 小时这是血泪经验。log-impl配成StdOutImpl后每次查询都会在控制台打印实际执行的 SQL调分页或者联表查询时特别有用。数据库要先手动建好字符集用utf8mb4不然学生姓名里的生僻字会变成问号。建完库再执行项目里的sql文件初始化表结构和测试数据。3.3 前后端联调的端口与跨域处理前端跑在 8080后端跑在 9090浏览器直接发请求会触发跨域拦截。解决办法是在vue.config.js里配代理把/api开头的请求转发到后端端口。这样前端代码里写请求路径时统一用/api/xxx不用写完整地址部署时也不用改。// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:9090, // 后端实际地址 changeOrigin: true, // 允许跨域 pathRewrite: { ^/api: // 去掉 /api 前缀再转发给后端 } } } } }changeOrigin: true的作用是让后端收到的请求头里的 host 变成后端自己的地址有些安全校验会检查这个。pathRewrite把/api去掉是因为后端 Controller 里映射的路径本身就不带/api如果两边都带就会变成/api/api/repair直接 404。改完vue.config.js必须重启前端服务热更新不会让代理配置生效这一点很多人会忘。4. 核心业务模块的实现细节报修、宿舍与权限4.1 报修单状态流转的接口设计与前端联动报修是校园后勤系统里业务最完整的模块因为它有明确的状态流转。学生提交后是待接单维修工接单后变成已接单开始修变成维修中修完变成已完成学生评价后流程结束。后端每个状态变更都要校验当前用户有没有权限操作比如学生不能把状态改成已完成维修工不能替学生提交报修。// service/RepairServiceImpl.java Override public Result updateStatus(Long orderId, Integer newStatus) { RepairOrder order this.getById(orderId); if (order null) { return Result.error(报修单不存在); } Long currentUserId SecurityUtils.getCurrentUserId(); Integer role SecurityUtils.getCurrentUserRole(); // 只有维修工能接单和完成且必须是分配给自己的单 if (newStatus 1 || newStatus 3) { if (role ! 2 || !order.getWorkerId().equals(currentUserId)) { return Result.error(无权操作此报修单); } } // 只有学生本人能评价 if (newStatus 4 !order.getStudentId().equals(currentUserId)) { return Result.error(只能评价自己的报修单); } order.setStatus(newStatus); this.updateById(order); return Result.success(状态更新成功); }前端这边不同角色登录后看到的按钮不一样。学生看到的是「提交报修」和「评价」维修工看到的是「接单」和「完成」。实现方式是在 Vue 里根据role字段用v-if控制按钮显示但真正的权限校验必须在后端做前端隐藏按钮只是体验优化不能当安全措施。我见过有人只在前端判断角色结果用 Postman 直接调接口就能越权改状态答辩时被老师当场演示就尴尬了。4.2 宿舍调换与物资领用的库存扣减宿舍调换的逻辑是学生申请调换宿管审批通过后更新dorm表里该学生的住宿记录。这里要注意并发问题如果两个学生同时申请同一个空床位直接更新会导致两人都以为自己调成功了。常见做法是在dorm表里给student_id加唯一索引或者更新时用where student_id is null作为条件根据受影响行数判断是否成功。-- 调换宿舍把目标床位的学生设为当前学生条件是该床位为空 UPDATE dorm SET student_id #{studentId} WHERE id #{dormId} AND student_id IS NULL; -- 如果返回的影响行数为 0说明床位已被占用前端提示「该床位已被选」物资领用同理扣库存时不能先查再改要用原子操作。UPDATE material SET stock stock - #{count} WHERE id #{materialId} AND stock #{count};stock #{count}这个条件保证库存不足时更新失败不会出现负数库存。前端在点击领用按钮前先查一次库存做提示但最终以数据库更新结果为准。这两处是答辩时能体现「你考虑过并发」的加分项虽然校园场景并发量不大但讲出来说明你有工程思维。4.3 基于角色的菜单与接口双重拦截权限控制分两层前端路由守卫和后端拦截器。前端用 Vue Router 的beforeEach判断有没有 token没有就跳登录页后端用 Spring 的HandlerInterceptor判断请求头里的 token 是否有效以及当前用户角色能不能访问这个接口。// config/AuthInterceptor.java public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 放行登录接口 if (request.getRequestURI().contains(/login)) { return true; } String token request.getHeader(Authorization); if (token null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } // 把用户信息存入 ThreadLocal方便后续 Service 获取 SecurityUtils.setCurrentUser(JwtUtil.parse(token)); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { SecurityUtils.clear(); // 请求结束必须清理防止内存泄漏 } }ThreadLocal存当前用户信息这样在 Service 里不用每个方法都传 userId 参数。afterCompletion里必须调clear()因为 Tomcat 的线程是复用的不清理的话下一个请求可能拿到上一个用户的信息这是隐蔽的翻车点。拦截器注册时用addPathPatterns(/api/**)拦截所有接口excludePathPatterns(/api/login)放行登录。5. 避坑与排查源码跑不起来时先看这几条5.1 数据库连不上时区与驱动版本现象是启动时报The server time zone value ???ú±ê×??±?? is unrecognized原因是 MySQL 8 的驱动要求明确指定时区。解决方法是连接 URL 里加serverTimezoneAsia/Shanghai。如果报Unknown database campus_logistics说明数据库没建先手动CREATE DATABASE campus_logistics DEFAULT CHARACTER SET utf8mb4。驱动版本也要注意MySQL 5.7 用com.mysql.jdbc.DriverMySQL 8 用com.mysql.cj.jdbc.Driver写错会报Loading class ... is deprecated然后连接失败。5.2 前端依赖装不上Node 版本与镜像源现象是npm install报node-sass编译错误或者gyp ERR。原因是 Node 版本太新老版本的node-sass不兼容。解决办法是切到 Node 16或者把node-sass换成sass纯 JS 实现不需要编译。如果只是下载慢执行npm config set registry https://registry.npmmirror.com换镜像源再重试。装完之后如果启动报Module not found删掉node_modules和package-lock.json重新装不要试图手动补包。5.3 接口 404 或 401代理与 token 传递404 先看请求地址对不对浏览器 F12 的 Network 里看实际请求的 URL 是什么。如果请求发到了 8080 但后端在 9090说明代理没生效检查vue.config.js改完有没有重启。401 说明 token 没传或者过期了检查登录后有没有把 token 存进localStorage以及请求拦截器有没有把它塞进请求头。有时候登录接口本身返回 200 但 token 字段名对不上前端取的是res.data.token而后端返回的是res.data.accessToken这种字段名不一致的问题只能对着两边代码逐字核对。5.4 分页查询数据重复或总数不对现象是第二页出现了第一页的数据或者总条数比实际少。原因通常是 SQL 里order by的字段有重复值MySQL 分页时顺序不稳定。解决办法是加一个唯一字段作为次级排序比如order by create_time desc, id desc。总数不对一般是count查询的where条件和列表查询不一致MyBatis-Plus 的分页插件会自动生成 count SQL但如果手写了联表查询要确保 count 和列表用同一套条件。5.5 上传图片后访问 404静态资源映射报修时上传的图片存在服务器本地磁盘数据库里存的是相对路径。前端通过 URL 访问时 404是因为 SpringBoot 默认不映射外部目录。需要在配置类里加addResourceHandlers把上传目录映射到/upload/**路径。另外上传目录的路径不要写死在代码里用application.yml里的自定义配置项部署到不同机器时只改配置不改代码。6. 让这套系统在答辩时站得住数据初始化与演示技巧跑通只是及格线答辩时老师不会看你代码写得多花哨他会让你当场演示一个完整流程然后问几个「如果……怎么办」的问题。我的习惯是提前准备一份初始化数据脚本把学生、宿管、维修工、管理员四个角色的测试账号都建好密码统一用123456演示时切换账号不用现注册。报修单预置几条不同状态的记录这样一进列表页就能看到分页效果和状态筛选不用现场等数据。演示流程建议按「学生提交报修 → 维修工接单 → 维修中 → 完成 → 学生评价」走一遍中间穿插一次「学生试图点完成按钮但按钮不显示」的对比说明权限控制生效了。老师常问的问题有三个一是「你的 token 怎么生成的」答 JWT 加签名具体算法可以说 HS256二是「数据库密码明文存吗」答登录密码用 BCrypt 加密数据库里存的是哈希值三是「如果两个人同时抢一个维修单怎么办」答用数据库乐观锁或者update ... where status 0的判断影响行数为 0 就提示已被接单。还有一个容易被忽略的点是日志。在application.yml里配一下 MyBatis 的 SQL 日志演示时如果某个操作没成功你可以当场打开控制台看实际执行的 SQL 是什么这比干瞪眼强。另外把spring.jackson.time-zone配成GMT8不然接口返回的时间字段会比实际少 8 小时老师一眼就能看出来。最后说一个我自己的教训第一次做这类系统时我把所有配置都写死在代码里换台电脑演示就崩了。后来养成习惯凡是跟环境相关的——数据库地址、上传路径、端口号——全部抽到配置文件代码里只留占位符。这个习惯在毕业设计里可能只帮你省半小时但工作以后能帮你省一整天。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑