毕业设计做了个宿舍管理系统,项目名就叫“基于SpringBoot+Vue的宿舍管理系统设计与实现”,技术栈是Java+MySQL+MyBatis,前端用的Vue,整套源码自己手写的。这个题目在本科毕设里出现频率极高,网上能搜到的成品源码质量参差不齐,能跑的少、报错的多。这篇就拿我实际做完并成功答辩的项目来拆,从需求梳理、表设计、后端接口、前端页面到部署上线,把关键细节和踩过的坑一并写清楚,给准备做同类系统的同学一个完整参考。
1. 需求拆解与整体方案选型
1.1 先搞清楚宿舍管理系统到底要管什么
很多同学一上来就建表、写接口,做着做着发现需求没定清楚,页面改了又改。做这种管理系统,第一步一定是把用户角色和业务边界画出来。
宿舍管理系统的核心角色通常有三类:
- 学生:查宿舍、交电费、报修、查看卫生评分
- 宿管员(楼栋管理员):分配宿舍、处理报修、录入卫生检查结果、管理学生入住/退宿
- 系统管理员:管理楼栋、宿舍、学生账号、宿管账号、数据统计
业务模块可以拆成这几个:宿舍资源管理(楼栋、房间、床位)、学生入住管理(分配、调宿、退宿)、报修管理(学生提交-宿管处理-完成反馈)、卫生检查管理(评分录入与查询)、公告管理(发布宿舍通知)。
我说句实在话,毕设阶段不需要做成微信小程序那种花哨前端,也不需要引入Redis缓存、MQ消息队列这些中间件。把SpringBoot+Vue这套基础链路跑通,业务表设计合理,CRUD规范,就已经能拿到不错的评分。过度设计反而容易在答辩时被问住。
1.2 为什么选SpringBoot+Vue而不是JSP或前后端分离以外的方案
这个组合能成为毕设主流方案,有几个很实际的原因:
第一,SpringBoot把SSM(Spring+SpringMVC+MyBatis)里大量繁琐配置变成了自动配置和起步依赖,不用再写一堆XML配置文件。对毕设来说,项目能快速跑起来比什么都重要。
第二,Vue前端是组件化开发,页面逻辑清晰,维护起来比传统JSP+Servlet那个模式舒服太多。而且Vue的路由、Axios、Element UI这些工具链都成熟,一个月搞定前端完全可行。
第三,Java+MySQL这套东西网上资料太多,遇到问题搜索解决方案一抓一大把,答辩时老师问什么都有东西可讲。
从部署角度看,SpringBoot内置Tomcat,打包成Jar直接跑,前端Build完丢到Nginx或者直接塞进static目录,单机部署非常省心。
2. 数据库设计:别急着写代码,先把表结构想透
2.1 用户表设计的核心思路
用户这块常见的做法是建一张user表,用role字段区分角色,而不是为每种角色单独建表。对毕设这种规模,一张用户表完全够用,还省去了联表查询的麻烦。
我的user表核心字段:
- id、username、password(BCrypt加密后存储)、real_name、role(student/admin/worker)
- student_no(学号,仅学生有)、phone、email
- create_time、status(禁用/启用)
这里有个容易被忽略的点:密码加密。我见过很多毕设源码直接明文存密码,答辩时老师一问安全设计就卡壳。用Spring Security的BCrypt或者Hutool的BCrypt工具类做加密,代码量不大,但能在答辩时体现出安全意识。
2.2 宿舍、楼栋、床位的层级关系建模
宿舍资源是典型的三层结构:楼栋(building)→ 房间(dormitory)→ 床位(bed)。我没给床位单独建表,而是在dormitory表里加bed_count和used_count字段,通过计算判断是否满员。这样对毕设来说足够,代码逻辑也简单。
dormitory表关键字段:
- id、building_id、room_number、floor、bed_count、used_count
- type(四人间/六人间等)、is_available(是否可分配)
注意一个细节:宿舍分配时要加一个事务控制,分配成功则used_count加1,退宿则减1。不然并发操作时数据就错了。我在Service层加了@Transactional注解,用synchronized做了简单的同步控制,避免两个学生同时申请最后一个床位。
2.3 报修、卫生检查、公告这些业务表
报修表(repair):
- id、student_id、dormitory_id、content、images(可存图片路径列表,用逗号分隔)
- status(pending/processing/finished)、apply_time、finish_time、handler_id
卫生检查表(inspection):
- id、dormitory_id、inspector_id、score、comment、inspect_time
公告表(notice):
- id、title、content、publisher_id、publish_time、is_top
这三张表是业务的核心出口,前端页面的信息展示基本都围绕它们展开。成绩展示、报修进度跟踪、公告列表,都是毕设演示时最容易被老师点开看的页面,务必把数据填得真实一些。
建表语句我就不贴全部了,提醒一个新手容易踩的坑:MySQL的text/date类型和Java实体类的对应关系要提前确认。比如datetime类型在MyBatis里映射到LocalDateTime,如果用String接收也行,但排序和比较会很怪。
3. 后端实现:SpringBoot+MyBatis的关键落地细节
3.1 项目初始化与依赖配置
我用的是SpringBoot 2.7.x版本,搭配MyBatis Spring Boot Starter 2.2.x,JDK 1.8。为什么不用SpringBoot 3.x?因为3.x最低要求JDK 17,而且部分毕设用的老教程和插件不兼容,为了少踩坑,2.7.x是最稳妥的选择。
pom.xml里核心依赖就这几个:
- spring-boot-starter-web
- mybatis-spring-boot-starter
- mysql-connector-j(注意8.0以上版本驱动类名改成com.mysql.cj.jdbc.Driver)
- lombok(少写一堆getter/setter)
- hutool(工具类,做加密、日期处理很方便)
3.2 统一响应体和全局异常处理
后端接口如果不做统一返回格式,前端处理会非常痛苦。我封装了一个Result类:
public class Result<T> { private Integer code; private String message; private T data; // 省略构造方法、getter/setter public static <T> Result<T> success(T data) { ... } public static <T> Result<T> error(String message) { ... } }统一返回结构后,前端只需要判断code是否为200,不用关心每个接口不同的字段命名。
全局异常处理用@RestControllerAdvice,捕获业务异常和兜底的Exception。这样即使代码有bug,前端拿到的也不是一坨Tomcat的错误页,而是规范的JSON提示。答辩时老师随便输入一个不存在的ID,看到返回“数据不存在”而不是500页面,印象分会高不少。
3.3 MyBatis映射文件里最容易翻车的几个点
MyBatis写SQL比JPA灵活,但坑也多。我总结三个高发问题:
第一个是参数传递。多条件查询时,我推荐用@Param注解显式命名参数,而不是靠顺序匹配。之前写List<User> selectByCondition(String role, String keyword)没加@Param,改成两个参数后有概率报Parameter 'role' not found,后来统一加@Param就稳定了。
第二个是动态SQL。条件查询、分页查询都用到<where>和<if>标签,注意<if test="keyword != null and keyword != ''">这个判断一定要写全,不然空字符串条件也拼进去,查出来的结果就是错的。
第三个是联表查询的字段映射。宿舍查询经常要带出楼栋名称,我用的方式是写一个VO类,继承实体或者直接聚合字段,然后在Mapper XML里用resultMap或别名映射。最简单的是SQL里直接写别名:
SELECT d.*, b.name AS building_name FROM dormitory d LEFT JOIN building b ON d.building_id = b.id这样Java实体类里加一个buildingName字段,就能直接接收,不需要写复杂的resultMap,省事。
3.4 Service层的业务逻辑要点
Service层不要只是把Mapper方法包装一层就完事,那还不如直接Controller调Mapper。我建议在Service里处理这几个逻辑:
分配宿舍时的校验:房间是否存在、是否满员、学生是否已有宿舍、学生状态是否正常。多层校验放在Service,Controller保持轻薄。
报修流程的状态流转:student提交 → 变为pending → 宿管接单 → processing → 完成并填写处理结果 → finished。每个状态变更都记录时间,前端才能展示时间线。
退宿时的数据一致性:学生退宿需要把宿舍used_count减1,同时把学生的宿舍ID置空,这两个操作必须在一个事务里。用@Transactional包住,回滚才能保证数据不会越变越乱。
4. 前端实现:Vue工程搭建与页面设计
4.1 用Vue3还是Vue2?环境坑怎么避开
我做的是Vue2 + Element UI。原因是Vue3 + Element Plus在组件用法上改动不少,很多毕设教程和参考资料还是Vue2居多。如果你对Vue3更熟,用Vue3也没问题,核心逻辑其实一样。
这里有个比较容易卡住的点:Node版本。Vue2项目建议Node 14或16,Vue3项目建议Node 16以上。我一开始用Node 18跑Vue2项目,提示OpenSSL Error,原因是新版Node和旧版Webpack的加密库不一致,后面把命令改成:
NODE_OPTIONS=--openssl-legacy-provider npm run serve或者直接切换到Node 16。这类环境问题网上搜索量特别大,趁早把Node版本管好能省很多事。
4.2 Axios封装与跨域处理
前端请求后端,最典型的拦路虎就是跨域。后端解决我有两个方案:
方案一:SpringBoot加配置类
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true); } }方案二:用Vue的devServer代理
devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }建议开发和部署都搞定:开发期用代理,部署后把前端打包产物丢Nginx,再配置Nginx反代到后端。这样前后端路径一致,跨域问题直接消失。
Axios封装这块,我在request.js里做了三件事:统一请求前缀、请求拦截器带token、响应拦截器统一处理Result的code。这样业务页面里只需要写api.getStudentList(),不需要每个页面重复处理loading和错误弹窗。
4.3 页面结构与路由守卫
页面按角色来组织:
- 学生端:首页看板、我的宿舍、报修申请/记录、卫生评分查询、公告
- 宿管端:宿舍管理(房间列表/床位状态)、入住分配/退宿处理、报修处理、卫生检查录入、公告管理
路由需要做权限控制。我用了一个简单的思路:登录后把用户角色存到Vuex或sessionStorage,路由meta里设置roles数组,在全局前置守卫里判断当前用户是否有权限访问该路由,没有就重定向到403页面。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() } else if (!token) { next('/login') } else { const roles = store.state.user.roles if (to.meta.roles && !to.meta.roles.includes(roles)) { next('/403') } else { next() } } })这个模式简单、可运行,答辩时解释起来也顺畅,不必上复杂的动态路由。
4.4 前端页面状态管理的小技巧
宿舍分配页面要同时展示楼栋筛选、房间列表、已选学生信息,跨组件传值多的时候用Vuex。但也不是所有状态都要进Vuex,页面私有的弹出框状态用组件data管理就行。
Element UI的表格组件配分页是标配。分页查询接口设计的参数pageNum和pageSize,前端表格翻页时触发查询请求,把total字段回填到分页组件。这个模式我在所有列表页统一使用,前端代码结构清晰,后端接口也能复用。
5. 常见问题排查与避坑实录
5.1 项目启动就报错的几个高发原因
有段时间同学群里发的最多的问题是“启动SpringBoot直接Failed to configure a DataSource”。这个基本就是没配数据源,或者配置了但连不上数据库。检查思路:
- application.yml里的url、username、password和MySQL实际是否一致
- MySQL服务是否启动,Windows下用
net start mysql或者在任务管理器看服务状态 - POM里是不是只引入了MyBatis Starter,但忘了引入mysql驱动
还有一个经常忽略的问题:驱动类。MySQL 5.x用com.mysql.jdbc.Driver,MySQL 8.x用com.mysql.cj.jdbc.Driver,URL里8.x还要加serverTimezone=Asia/Shanghai和useSSL=false、characterEncoding=utf8,不然报警告或中文乱码。
5.2 MyBatis相关报错排查
最经典的“Invalid bound statement (not found)”错误,原因是Mapper接口和XML映射文件没有正确关联。检查点:
- XML文件是否在
mapper-locations指定的目录下,比如classpath:mapper/*.xml - Mapper接口的全限定名和XML的namespace是否完全一致
- XML里的id是否和接口方法名一致
- target/classes目录下有没有把XML编译进去(Maven构建时资源过滤问题)
第二个高频问题是“Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax”。这个大多不是真SQL语法错,而是XML里用了不该有的特殊字符。SQL里的<、>需要转义成<、>,比如时间比较条件。或者用<![CDATA[]]>包住SQL片段,避免XML解析器干扰。
5.3 前后端联调时的数据问题
接口通了,但页面显示乱码。先看数据库连接URL有没有characterEncoding=utf8,再看后端接口有没有返回中文正常。如果后端正常前端乱码,检查前端页面有没有<meta charset="UTF-8">,Vue的index.html里一般自带,所以多数情况坏在数据库插入阶段。
还有一个真实踩过的坑:设计表时用了description这个字段名,前端也一切正常,但是数据库把desc当成关键字,导致某些查询报错。所以字段命名尽量避开SQL关键字,比如不要用desc、order、group这些做列名,否则SQL里要加反引号,非常麻烦。
5.4 端口占用问题
SpringBoot默认8080端口经常被占用。排查命令:
netstat -ano | findstr 8080找到占用进程的PID后,任务管理器结束进程,或者直接改端口:
server: port: 8081Vue前端默认8080,如果后端也设8080就冲突了。开发时后端用8080,前端用8081,让代理指向后端8080。这样两个服务互不打架。
6. 部署上线与演示准备
6.1 后端打包与前端构建
后端打包就一条命令:
mvn clean package -DskipTests打出的Jar包在target目录下。这里踩过一个时间坑:SpringBoot的Maven插件版本和父POM版本不一致会导致repackage失败,打出的包缺少依赖,运行报“no main manifest attribute”。解决办法是确保插件版本明确:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.7.5</version> </plugin>前端构建:
npm run build生成dist目录,里面是静态资源。部署时如果用Nginx,把dist内容拷到Nginx的html目录,再配置一个代理转发到后端接口。如果用单Jar部署,可以直接把dist拷到SpringBoot的static目录下,重新打包一并发布。
6.2 答辩演示前要做的几个准备
做过无数次项目演示,总结出几个让答辩更顺畅的小细节:
数据库里预置的数据要有真实感。学生姓名、学号、宿舍号、报修内容都别用“test1、test2”这种,老师一眼看出来就没兴趣了。我提前录入了20个学生、6间宿舍、4条报修、8条卫生检查记录,演示时点开页面直接出效果。
演示要准备两条线:主流程和边界流程。主流程是从管理员登录开始,建立楼栋、添加宿舍、分配学生、录入报修处理,一气呵成;边界流程是故意演示一些异常场景,比如分配一个已满员的宿舍,展示系统的错误提示。这两条线走完,老师对系统的完整性基本有数了。
6.3 项目后续还可以怎么扩展
做完这套系统,如果想在毕设基础上加亮点,有几个方向比较省力:
引入Redis做用户登录态和验证码缓存,这个在答辩时能讲出技术深度。改造成本不高,但在技术栈上多了一层。
用WebSocket做报修状态实时通知,学生端提交报修后宿管端页面实时弹出新消息,这个效果演示起来非常抢眼。
把卫生检查的评分数据用ECharts做成可视化图表,展示各楼栋的周评分趋势。前端引入ECharts很简单,但界面查询时观感提升很大。
个人总结
做完这个项目最大的体会是:管理系统类的毕设,难度不在技术,而在需求的完整性和数据的一致性。把一个简单的业务做扎实,比堆砌一堆新技术要重要得多。我实际踩过最大的坑反而是环境问题——Node版本、MySQL版本、Maven依赖冲突,这些琐碎问题浪费的时间远超写代码本身。
最后分享一个调试技巧:开发时把MyBatis的SQL日志打开,在application.yml配置:
mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这样每次查询都会在控制台打印完整的SQL和参数。遇到查询结果和预期不符时,先看打印出来的SQL,再对数据库执行一次,问题出在哪一目了然。这个技巧贯穿了整个开发过程,比任何调试工具都实用。