资讯动态

Java+SpringBoot+Vue宿舍报修系统实战(含MySQL5.7全链路)

发布时间:2026/10/15 21:36:07 来源:尧图企业网站定制
简介本资源是一份面向计算机专业本科生与Java全栈初学者的毕业设计级项目资料聚焦高校宿舍后勤维修服务数字化转型场景提供从系统设计到代码实现的完整技术闭环。资源包含1个4.78MB的Word文档.docx涵盖论文全文、系统需求分析、SpringBootVue前后端技术选型说明、MySQL数据库设计含Navicat建表截图、12大核心功能模块详述如报修接单、维修进度跟踪、配件申请与宿舍分配管理及测试运行结论结构完整、逻辑清晰可直接用于课程设计参考或毕设开题支撑。文档中第2章深入解析Java、SpringBoot与MySQL关键技术点第3章开展可行性与系统分析附录隐含典型业务流程图与ER模型线索便于读者理解架构分层与数据流向。目前已有433人学习下载适合希望掌握B/S架构校园管理系统开发全流程的学习者快速上手并拓展实战能力。1. 基于 Java SpringBoot Vue 的宿舍报修系统不是 Demo是能跑通的完整闭环你有没有遇到过这样的场景学生在宿舍发现灯不亮、水管漏水、门锁损坏只能跑到宿管办公室手写一张纸条宿管再用 Excel 登记、微信转发给维修员维修员修完后口头反馈管理员月底翻着三本本子对账——整个流程没进系统数据散落在纸面、聊天记录和本地表格里。这不是虚构而是某高校后勤部门真实存在的“低效黑匣子”。这份《基于Java的宿舍报修管理系统的设计与实现》不是课程大作业式的空壳Demo而是一套从数据库建模、SpringBoot 后端接口、Vue 前端页面到 Tomcat 部署全链路可复现的实战资源包。它覆盖了真实校园场景中三类角色学生用户、维修员、管理员的完整业务流报修→接单→配件申请→进度跟踪→完成闭环。技术栈干净利落后端用 SpringBoot 2.x非 Spring 5 全新注解堆砌前端用 Vue 2.6 Element UI非 Vue 3 Composition API 复杂样板数据库用 MySQL 5.7非高版本 JSON 函数依赖所有模块都经过基础功能测试验证。如果你正要交课程设计、做毕设开题、或需要一个轻量级后勤系统原型来快速验证流程这份资源不是“参考”而是能直接 clone、改配置、启动就用的最小可行系统MVP。2. 技术选型为什么是 Java SpringBoot Vue MySQL不是跟风是权衡后的落地选择2.1 为什么后端不用 Node.js 或 PythonJava 的稳定性和生态适配性更硬核很多新手看到“管理系统”第一反应是 Node.js快或 Django轮子多但在这类校园级、需长期维护、对接教务/一卡通等潜在系统的场景里Java 的优势不是性能而是稳定性、可追溯性与团队协作成本。SpringBoot 的自动装配机制让开发者不用深陷 XML 配置地狱同时又保留了 Spring 生态对事务、安全、日志的成熟控制能力。比如本系统中“报修接单”操作必须保证① 接单状态变更② 维修员工作量统计更新③ 系统消息推送虽未实现 WebSocket但预留了消息表结构——这三个动作必须原子性执行。SpringBoot 的Transactional注解一行就能兜底而 Node.js 的 Promise 链或 Python 的 SQLAlchemy session commit 需要手动管理事务边界稍有疏忽就出现“接单成功但统计没更新”的数据不一致。另外学校机房服务器普遍预装 JDK 8/11Tomcat 部署路径清晰运维人员无需额外学习新运行时环境。我带过的某高校实训项目曾尝试用 Flask 重写此系统结果在部署阶段卡在 CentOS 7 的 Python 版本兼容问题上两周最后还是切回 Java —— 这不是语言优劣而是教育场景下“少一个故障点”比“多一个炫技点”重要得多。2.2 为什么前端选 Vue 2.6 而非 Vue 3Element UI 的组件成熟度碾压一切你可能疑惑都 2024 年了怎么还用 Vue 2答案很实在Element UI 的 Table、Form、Dialog 组件在 Vue 2 下已打磨十年文档齐全、Bug 少、社区方案多。本系统前端核心是“报修信息管理”和“维修进度看板”这两个页面重度依赖表格排序、分页、行内操作按钮如“派单”“标记完成”、表单校验报修描述字数限制、联系方式正则。Vue 3 的 Composition API 虽然更灵活但 Element PlusVue 3 版本在 2023 年前对el-table的expand-row-keys动态展开、el-form的异步校验提示等细节支持仍有坑。而本资源中的 Vue 2.6 Element UI 代码直接复制粘贴就能跑比如src/views/repair/RepairList.vue中的表格定义template el-table :datarepairList stylewidth: 100% sort-changehandleSortChange el-table-column propid label报修编号 width120/el-table-column el-table-column propsushemingcheng label宿舍楼 width100/el-table-column el-table-column propbaoxiushijian label报修时间 width160 sortablecustom/el-table-column el-table-column propbaoxiuneirong label问题描述 width200 show-overflow-tooltip/el-table-column el-table-column label操作 width180 template slot-scope{row} el-button sizemini typeprimary clickhandleAccept(row)接单/el-button el-button sizemini typesuccess clickhandleComplete(row)完成/el-button /template /el-table-column /el-table /template这段代码里sort-changehandleSortChange绑定的是自定义排序逻辑按时间倒序show-overflow-tooltip解决长文本截断问题——全是开箱即用的 Element UI 原生能力不用自己封装 Tooltip 组件。如果你强行升级到 Vue 3光是把slot-scope改成#default{row}就得查文档半小时而本资源省掉了这个“玄学调试时间”。2.3 为什么数据库用 MySQL 5.7 而非 PostgreSQL 或 SQLiteNavicat 可视化建模是刚需校园项目最怕什么不是代码写错而是数据库字段改了但忘了同步给前端、或者表关联写错导致查询为空。MySQL 5.7 的核心优势在于 Navicat 的可视化建模能力。本资源附带的.sql文件db/sushexiuxu.sql不是纯 DDL 语句而是 Navicat 导出的含注释结构文件打开后能看到清晰的 E-R 图实体关系图yonghu用户表通过yonghuzhanghao字段关联sushefenpei宿舍分配表sushefenpei又通过sushemingcheng关联sushexinxi宿舍信息表。这种可视化关系在开发“宿舍详情页”时直接决定前端要不要发三次请求用户→分配→宿舍还是用一条 JOIN SQL 一次性查出。而 SQLite 虽轻量但 Navicat 不支持其 E-R 图生成PostgreSQL 虽强大但学校机房很少预装 psql 客户端运维人员面对\d table_name命令一脸懵。所以选 MySQL 5.7本质是选“让非开发人员也能看懂数据流向”的妥协方案。提示本资源所有 SQL 文件均使用utf8mb4字符集避免学生姓名含生僻字如“䶮”“爔”时乱码。建表语句中addtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP是 MySQL 5.6 特性若你用 MySQL 5.5请手动改为DEFAULT NOW()。3. 数据库物理设计与关键表结构解析别只抄 SQL先看懂字段设计逻辑3.1 四张核心业务表yonghu、sushexinxi、baoxiu、weixiu的字段设计意图本系统数据库共 12 张表但真正驱动业务的是以下四张。它们不是随意命名每个字段名都对应真实业务动作表名核心字段设计意图实际用途举例yonghu用户表yonghuzhanghao用户账号、mima密码、yonghuxingming用户姓名学生登录凭证与身份标识学生用学号如2022001注册密码加密存储姓名用于报修单抬头显示sushexinxi宿舍信息表sushemingcheng宿舍名称、fangjianhaovarchar房间号、yizhurenshu已住人数宿舍物理属性与 occupancy 状态“东区3号楼201室”作为唯一标识yizhurenshu实时更新超员时前端禁用报修按钮baoxiu报修表yonghuzhanghao外键、sushemingcheng外键、baoxiuneirong问题描述、baoxiushijian报修时间、status状态0待处理,1已接单,2已完成报修事件的全生命周期记录学生提交后status0维修员点击“接单”时后端将status更新为1并写入weixiuyuanid字段weixiu维修表baoxiuid外键、weixiuyuanid维修员ID、peijianname配件名称、weixiujindu维修进度0~100维修过程的细化追踪维修员每次更新进度如“已更换灯泡”时weixiujindu从30→60→100前端用进度条渲染特别注意baoxiu表中没有直接存yonghuxingming用户姓名而是通过yonghuzhanghao关联yonghu表。这是范式设计的硬性要求避免数据冗余。如果学生改名只需更新yonghu表一次所有历史报修单自动显示新姓名反之若把姓名存进baoxiu就得遍历全表更新极易遗漏。3.2 token 表不是 JWT而是传统 Session 的数据库落地版很多教程一上来就讲 JWT但本系统用的是更稳妥的token表见摘要中表 4.2原因很现实JWT 在无状态服务中好用但校园系统常需强制下线如学生换密码后旧 Token 失效、或按角色踢人如维修员离职——这些操作 JWT 很难优雅实现。token表结构如下CREATE TABLE token ( id bigint(20) NOT NULL AUTO_INCREMENT, userid bigint(20) DEFAULT NULL COMMENT 用户id, username varchar(10) DEFAULT NULL COMMENT 用户名, tablename varchar(10) DEFAULT NULL COMMENT 表名, role varchar(10) DEFAULT NULL COMMENT 角色, token varchar(20) DEFAULT NULL COMMENT token, addtime timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 新增时间, expiratedtime timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 过期时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键点tablename字段存yonghu或weixiuyuan区分不同角色登录expiratedtime默认CURRENT_TIMESTAMP但后端代码会将其设为now() 72002小时超时后SELECT查不到有效 token自动跳转登录页每次用户刷新页面前端会发/api/token/refresh请求后端检查expiratedtime now()若未过期则延长expiratedtime否则返回 401。这种设计牺牲了一点性能每次请求查 DB但换来的是100% 可控的会话生命周期——这正是教育系统最看重的。3.3 宿舍分配表sushefenpei解决“谁住哪间”的动态映射难题sushefenpei表摘要中表 4.6是本系统最易被忽略却最关键的表。它不存静态数据而是实时反映“人-房”绑定关系。字段chuangweihaovarchar床位号和fenpeiriqi分配日期决定了权限边界学生只能报修自己fenpeiriqi之后分配的宿舍防止报修已退宿的房间管理员调整分配时旧记录fenpeiriqi不删而是新增一条新记录形成时间线便于审计查询“某学生当前宿舍”时SQL 是SELECT * FROM sushefenpei WHERE yonghuzhanghao 2022001 AND fenpeiriqi CURDATE() ORDER BY fenpeiriqi DESC LIMIT 1;这种设计让系统天然支持学期制宿舍轮换——某高校每学期初批量导入分配表旧数据自动归档无需修改任何业务逻辑。注意sushefenpei表中beizhu备注字段类型为longtext不是varchar(255)。因为实际使用中宿管常在此填写“该生有哮喘勿用刺激性清洁剂”等特殊说明短字段会截断。4. SpringBoot 后端核心接口实现从 Controller 到 Service 的三层穿透4.1 报修提交接口/api/baoxiu/add如何保证数据一致性与防重复提交学生点击“提交报修”时前端发送 POST 请求到/api/baoxiu/add后端 Controller 层代码如下BaoxiuController.javaPostMapping(/add) public R add(RequestBody Baoxiu baoxiu, HttpServletRequest request) { // 1. 从 token 表查当前用户信息 String token request.getHeader(token); Token tokenObj tokenService.selectOne(new QueryWrapperToken().eq(token, token)); if (tokenObj null) return R.error(登录失效); // 2. 设置报修人账号和时间 baoxiu.setYonghuzhanghao(tokenObj.getUsername()); baoxiu.setBaoxiushijian(new Date()); baoxiu.setStatus(0); // 初始状态待处理 // 3. 校验宿舍是否存在防前端伪造 Sushexinxi sushexinxi sushexinxiService.getById(baoxiu.getSushemingcheng()); if (sushexinxi null) return R.error(宿舍不存在); // 4. 保存报修单 baoxiuService.save(baoxiu); return R.ok(); }这段代码的精妙之处不在语法而在防御性编程意识第 1 步查token表而非直接信任token防止前端篡改 Header第 2 步由后端设置yonghuzhanghao和baoxiushijian杜绝前端传入假用户或未来时间第 3 步二次校验宿舍存在性避免学生绕过前端 JS 校验直接 POST 一个不存在的sushemingcheng第 4 步save()前不校验baoxiuneirong长度前端已做但 Service 层save()方法内部会调用Validator检查必填字段。血泪经验某次测试中学生用 Postman 手动构造请求把baoxiuneirong设为空字符串提交结果baoxiu表里存了一堆“无描述报修单”。后来我们在Baoxiu实体类加了NotBlank(message问题描述不能为空)注解并在 Controller 上加Valid才彻底堵住。4.2 维修员接单接口/api/weixiu/accept分布式锁的轻量级替代方案维修员点击“接单”时需满足两个条件① 该报修单status0未被接② 接单后status变为1。看似简单但并发场景下可能两人同时点导致同一单被接两次。本系统没用 Redis 分布式锁增加部署复杂度而是用 MySQL 的UPDATE ... WHERE 条件原子性// WeixiuService.java Transactional public boolean accept(Long baoxiuId, Long weixiuyuanId) { // 条件1只更新 status0 的记录 // 条件2且该报修单未被其他维修员接走通过影响行数判断 int rows baoxiuMapper.updateStatusAndWeixiuyuan( baoxiuId, weixiuyuanId, 0, // 旧状态 1 // 新状态 ); return rows 1; // 影响行数为1才成功 }对应的 Mapper XMLBaoxiuMapper.xmlupdate idupdateStatusAndWeixiuyuan UPDATE baoxiu SET status #{newStatus}, weixiuyuanid #{weixiuyuanId}, jiedanshijian NOW() WHERE id #{baoxiuId} AND status #{oldStatus} /update原理UPDATE语句在 MySQL 中是原子操作WHERE 条件不满足时rows0rows1才代表抢到了单。这种方案零依赖、零配置完美适配校园机房无 Redis 的现状。4.3 管理员导出报修统计MyBatis 动态 SQL 的实战应用管理员需要导出“本月各宿舍楼报修数量TOP10”这需要动态拼接GROUP BY和ORDER BY。MyBatis 的if标签让 SQL 更健壮!-- BaoxiuMapper.xml -- select idgetMonthlyStats resultTypejava.util.Map SELECT sushemingcheng AS dormName, COUNT(*) AS repairCount FROM baoxiu WHERE DATE_FORMAT(baoxiushijian, %Y-%m) #{month} if testdormName ! null and dormName ! AND sushemingcheng LIKE CONCAT(%, #{dormName}, %) /if GROUP BY sushemingcheng ORDER BY repairCount DESC LIMIT 10 /selectController 中调用GetMapping(/stats/monthly) public ListMapString, Object getMonthlyStats( RequestParam String month, RequestParam(required false) String dormName) { return baoxiuService.getMonthlyStats(month, dormName); }这里RequestParam(required false)让dormName可选配合if标签既支持全局统计也支持按楼筛选——不用写两个接口一个 SQL 解决所有场景。5. 常见问题排查与避坑指南那些让你加班到凌晨的“小问题”5.1 现象前端登录后跳转首页但右上角始终显示“请登录”Token 未生效原因Vue 项目中axios请求未自动携带tokenHeader或后端Interceptor未正确解析解决检查src/utils/request.js是否设置了全局请求头service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.token token; // 注意不是 Authorization: Bearer xxx } return config; });检查后端TokenInterceptor.java是否从request.getHeader(token)读取而非Authorization浏览器开发者工具 Network 标签页查看登录请求的 Response Headers 是否含Set-Cookie本系统未用 Cookie故应无若看到Set-Cookie说明后端误配了 Session需关闭 SpringBoot 的server.servlet.session.cookie.http-onlyfalse。5.2 现象添加新宿舍时sushexinxi表插入成功但sushefenpei表无数据导致学生无法报修原因管理员添加宿舍后未在“宿舍分配管理”页面手动为学生分配而系统未做“默认分配”逻辑解决方案A推荐在SushexinxiController.add()方法末尾自动为所有yonghu创建一条sushefenpei记录仅当yonghu.rolestudent方案B保守在前端SushexinxiAdd.vue的submitForm()中添加提示“请前往【宿舍分配管理】为学生分配宿舍否则无法报修”根本避坑在数据库设计时sushefenpei表的sushemingcheng字段加唯一索引UNIQUE KEY uk_sushemingcheng (sushemingcheng)避免同一宿舍被重复分配。5.3 现象MySQL 导入.sql文件时报错ERROR 1067 (42000): Invalid default value for addtime原因MySQL 5.7 严格模式下TIMESTAMP字段不能设DEFAULT 0000-00-00 00:00:00解决执行SET sql_mode(SELECT REPLACE(sql_mode,NO_ZERO_DATE,));临时关闭永久方案修改 MySQL 配置文件my.cnf在[mysqld]下添加sql_mode STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION重新导入 SQL本资源db/sushexiuxu.sql已修正为DEFAULT CURRENT_TIMESTAMP但若你用其他版本需手动替换所有0000-00-00 00:00:00。5.4 现象Vue 页面中el-table显示“暂无数据”但控制台无报错Network 查看接口返回正常数据原因Element UI 的el-table要求:data绑定的必须是数组而后端返回的是{code:200, data: [...], msg:ok}结构前端未解构response.data解决在src/api/repair.js的getRepairList()方法中export function getRepairList(params) { return request({ url: /baoxiu/list, method: get, params }).then(res res.data); // 必须加 .then 解构 }然后在RepairList.vue的created()中this.$api.repair.getRepairList().then(data { this.repairList data; // data 是数组可直接赋值 });5.5 现象Tomcat 启动后访问http://localhost:8080显示 404但/api/login接口能通原因SpringBoot 静态资源路径未配置Vue 打包后的index.html未被识别为欢迎页解决确保 Vue 项目vue.config.js中publicPath: /非./SpringBootapplication.yml中添加spring: web: resources: static-locations: classpath:/static/,classpath:/public/,file:./dist/将 Vue 打包后的dist文件夹整个复制到 SpringBoot 项目的src/main/resources/static/目录下注意不是static/dist是static/下直接放index.html重启 Tomcat此时http://localhost:8080会自动加载static/index.html。提示若仍 404请检查pom.xml中是否误删了spring-boot-starter-web依赖——这是最隐蔽的“后悔药”场景删掉后 Controller 接口全挂但SpringBootApplication仍能启动让人误以为是前端问题。6. 进阶技巧用 MyBatis-Plus 的QueryWrapper替代手写 SQL让 CRUD 更安全6.1 为什么QueryWrapper比原始 SQL 更适合校园系统手写 SQL 看似自由但在LIKE模糊查询、IN多值匹配、BETWEEN时间范围等场景下极易引发 SQL 注入或语法错误。比如管理员搜索“东区3号楼”的报修单原始 SQL 是SELECT * FROM baoxiu WHERE sushemingcheng LIKE %东区3号楼%;若学生在搜索框输入 OR 11就会变成SELECT * FROM baoxiu WHERE sushemingcheng LIKE % OR 11%;直接查出全表。而 MyBatis-Plus 的QueryWrapper会自动转义参数// BaoxiuService.java public PageBaoxiu searchByDorm(String dormName, PageBaoxiu page) { QueryWrapperBaoxiu qw new QueryWrapper(); if (StringUtils.isNotBlank(dormName)) { qw.like(sushemingcheng, dormName); // 自动转义 dormName 中的单引号 } return baoxiuMapper.selectPage(page, qw); }生成的实际 SQL 是SELECT * FROM baoxiu WHERE sushemingcheng LIKE ? -- 参数%东区3号楼%安全传递非字符串拼接6.2 用LambdaQueryWrapper写类型安全的条件查询QueryWrapper字符串写法如sushemingcheng容易拼错字段名且 IDE 无法提示。LambdaQueryWrapper用方法引用编译期就能报错// 替换掉字符串写法 QueryWrapperBaoxiu qw new QueryWrapper(); qw.eq(status, 0).orderByDesc(baoxiushijian); // 改为 Lambda 写法需继承 BaseMapperBaoxiu LambdaQueryWrapperBaoxiu lqw new LambdaQueryWrapper(); lqw.eq(Baoxiu::getStatus, 0) .orderByDesc(Baoxiu::getBaoxiushijian);这样如果Baoxiu类中getStatus()方法名被误改为getState()IDE 会立刻标红而不是等到运行时报NoSuchMethodException。6.3 批量操作用saveBatch()代替循环save()性能提升 10 倍管理员导入 500 条宿舍分配数据时若用 for 循环for (Sushefenpei item : list) { sushefenpeiService.save(item); // 每次 save 都是一次数据库连接 }耗时约 8 秒。改用saveBatch()sushefenpeiService.saveBatch(list, 100); // 每 100 条一批底层用 PreparedStatement Batch耗时降至 0.8 秒。原理是 JDBC 的addBatch()executeBatch()减少网络往返次数。注意saveBatch()要求list.size() 0空集合会抛IllegalArgumentException需前置校验。6.4 分页查询IPage与Page的区别及正确用法MyBatis-Plus 的分页插件返回IPageT不是PageT。常见错误是// ❌ 错误Page 是抽象类不能 new PageBaoxiu page new Page(1, 10); // ✅ 正确用 IPage 接收它是接口MyBatis-Plus 返回具体实现类 IPageBaoxiu page new Page(1, 10); IPageBaoxiu result baoxiuService.page(page, qw); ListBaoxiu records result.getRecords(); // 获取数据列表 long total result.getTotal(); // 获取总条数IPage的getRecords()返回ListgetTotal()返回long前端分页组件如el-pagination可直接用el-pagination :current-pagecurrentPage :page-sizepageSize :totaltotal size-changehandleSizeChange current-changehandleCurrentChange /el-pagination从那以后我每次写涉及LIKE或IN的查询都强制走一遍LambdaQueryWrapper哪怕多敲两行代码——因为线上环境查不出 SQL 注入比什么都强。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑