☰
基于SpringBoot+Vue的学生宿舍管理系统设计与实现
2026/10/6 14:17:55 网站建设 项目流程

“学生宿舍管理系统”这个项目,我前后做过三个版本才真正跑顺。最早用JSP+Servlet写,后来转到SpringBoot + Vue,从单体页面改造前后端分离,中间踩了不少坑。如果你正打算做一套基于SpringBoot和Vue的宿舍管理类系统,或者准备拿这个题目做课程设计、毕业设计,那这篇文章应该能帮你省掉一大半查资料的功夫。我不会只贴项目结构和代码片段,而是把整套系统的设计思路、表结构怎么建、核心功能怎么拆、部署联调有哪些隐藏问题,全部掰开揉碎讲一遍,保证你看完能直接上手复制。

1. 项目概述与整体设计思路

1.1 宿舍管理到底在管什么

我们先把问题定义清楚。学生宿舍管理,不是简单记录“谁住在哪个房间”,它背后是一整套日常管理流程:楼栋与房间资源维护、新生入住分配、学生调换宿舍、退宿清空床位、报修登记处理、卫生检查评分、晚归未归记录,再加上公告发布和统计数据汇总。以前很多学校宿管科用Excel表加微信群,信息分散不说,数据一多就乱,查一个学生的住宿历史得翻半天记录。

这套系统要解决的,就是让宿舍管理的核心数据有一个统一入口。管理员能看到全校宿舍的实时占用情况,学生能在线提交报修、查看自己的住宿信息,宿管员能处理报修、录入卫生检查结果。每一个动作都落到数据库,随时可以追溯。说白了,它是把一套线下的管理制度做成了软件系统。

1.2 为什么选SpringBoot + Vue,而不是其他组合

这个问题我被问过很多次。如果你只是交一个Demo,用传统的服务端渲染也能做。但SpringBoot + Vue是当前Java后端项目的主流组合,好处体现在三个层面。

第一个是开发速度。SpringBoot把Spring的XML配置全部干掉,内嵌Tomcat,一个mvn spring-boot:run就能启动。配合MyBatis Plus,单表的增删改查甚至不用写SQL。对于宿舍管理这种以CRUD为主的系统,开发效率非常高。

第二个是前后端分离。Vue负责页面渲染,SpringBoot只提供JSON接口,两边通过Restful API通信。这样后端同学不用管HTML,前端同学不用碰Java,分工明确。哪怕学生管理系统是一个人写完的,前后端分离也能让你后期改页面样式时不动后端代码。

第三个是就业和答辩友好。SpringBoot和Vue是当前市场上Java后端和前端开发岗位的高频技能,做这个题目既能覆盖技能点,又能在答辩时讲清楚架构思路。如果是毕设,外审老师看到“前后端分离 + SpringBoot + Vue + MySQL”这个组合,基本不会质疑技术深度。

1.3 功能模块怎么划分才合理

我画功能模块图前习惯先按角色分,因为不同角色看到的操作入口完全不一样。这套系统我切成三个端。

角色核心功能说明
管理员楼栋管理、宿舍管理、学生管理、入住分配、调换审批、报修统计、卫生评比、公告管理拥有系统全部权限,能查看所有数据
宿管员入住登记、报修处理、卫生检查录入、晚归记录、来访登记负责日常楼栋事务,不能修改基础数据
学生个人信息查看、宿舍信息查看、报修申请、卫生自查、意见反馈面向学生,只能管理自己的数据

有人会问,学生为什么不能自己调换宿舍?因为调换宿舍涉及床位释放和重新占用,必须有人审核,否则会出现一间宿舍同时被多人占用的数据混乱。我最早做的时候就放了自主调换功能,结果测试阶段就出了冲突,后来改成“申请-审批”模式才稳下来。

2. 技术选型与架构拆解

2.1 后端技术栈的细节

后端我用了SpringBoot 2.7.18,不要用3.x。因为3.x是基于Jakarta EE的,很多老教程的代码和依赖需要调整,对于学生项目来说没必要给自己加难度。JDK用1.8或11都行,我推荐1.8,兼容性最稳。

持久层用MyBatis Plus而不是原生MyBatis。原因很简单,宿舍管理系统大部分表都是单表操作,MyBatis Plus自带BaseMapper,调用selectById、insert、updateById就能完成基本CRUD,分页插件PageHelper也集成好了。如果你用原生MyBatis,每张表都要自己写Mapper XML,工作量翻一倍。

数据库连接池我用Druid,因为它的监控页面可以看到SQL执行情况,排查慢查询很方便。登录认证用JWT + 拦截器,不引入Spring Security。Spring Security功能强,但配置复杂,对学生项目来说属于大炮打蚊子。JWT的做法是用户登录成功后生成一个token,前端每次请求带上,后端拦截器解析token没问题就放行。代码量少,逻辑还清晰。

2.2 前端技术栈的细节

前端我选了Vue 2.6 + Element UI。为什么不直接用Vue 3?因为Element UI对应Vue 2,Element Plus对应Vue 3。虽然Vue 3是趋势,但Element Plus在部分组件细节上还有兼容调整,网上教程质量参差不齐。对你做项目来说,用Vue 2 + Element UI反而最省心,遇到问题搜索到的解决方案最多。

辅助库方面,路由用Vue Router,HTTP请求用Axios,状态管理如果项目不太复杂,根本不用Vuex。宿舍管理页面的筛选条件、表格展示、弹窗表单,这些用Element UI的el-table、el-dialog、el-form组件就能搞定。图表统计我用ECharts,用来展示各楼栋入住率、报修类型分布。

2.3 前后端分离的工作流程

讲一下完整的数据流,这块答辩经常被问。用户在登录页输入账号密码,前端把请求发到后端的/api/login,后端验证成功后返回一个加密的JWT token,前端把token存到localStorage。之后每一次请求,Axios的拦截器会在请求头加上Authorization字段。后端有一个HandlerInterceptor,在请求进入Controller之前先解析token,如果解析失败直接返回401。

后端接口的统一返回结构是一个Result对象,包含code、message、data三个字段。code为200表示成功,401表示未登录或token过期,500表示服务器异常。这样前端拿到结果后,根据code判断是提示错误还是渲染数据。每张表的CRUD接口都遵循这个规范,代码风格统一,后期维护也省事。

注意:统一返回结构这个习惯一定要养成。我见过很多新手项目,接口有的返回Map、有的返回实体、有的直接返回String,前端拿到数据一脸懵,那才是真的灾难。

3. 数据库设计与核心表结构

3.1 数据库设计的两条原则

宿舍管理系统的表不算多,但设计不好照样出问题。我的原则是:第一,核心表之间用逻辑外键,不建物理外键约束。就是说在业务层保证引用关系,数据库层面不加外键。原因是物理外键在插入、删除时会有约束检查,批量导入数据或者删除楼栋时会很麻烦,性能上也有损耗。

第二,冗余字段适度保留。比如在宿舍表里冗余一个已住人数,每次分配床位时更新这个字段。如果不冗余,查某栋楼的入住率就得count所有入住记录,数据量大一点就慢。宿舍表加一个floor_number字段,直接按楼层筛选,不用通过房间号字符串去模糊匹配。

3.2 核心表结构设计

我列几张核心表的建表SQL,你直接用就行。

用户表,存登录账号。注意这里不能只建一张学生表,因为管理员和宿管员也要登录,所以我单独建sys_user表,角色字段区分。sys_user和student表是一对一关联,student表存扩展信息。

CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(255) NOT NULL COMMENT '密码,BCrypt加密', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `role` tinyint NOT NULL COMMENT '1管理员 2宿管员 3学生', `status` tinyint DEFAULT '1' COMMENT '状态:1启用 0禁用', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

宿舍表,这个是核心中的核心。

CREATE TABLE `dormitory` ( `id` bigint NOT NULL AUTO_INCREMENT, `building_id` bigint NOT NULL COMMENT '所属楼栋ID', `room_number` varchar(20) NOT NULL COMMENT '房间号,如A101', `floor_number` int NOT NULL COMMENT '所在楼层', `capacity` int NOT NULL COMMENT '可住人数', `used` int DEFAULT '0' COMMENT '已住人数', `gender_type` tinyint NOT NULL COMMENT '1男寝 2女寝', `status` tinyint DEFAULT '1' COMMENT '状态 1可用 0维修中', PRIMARY KEY (`id`), UNIQUE KEY `uk_building_room` (`building_id`, `room_number`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

报修表,记录整个报修流程。

CREATE TABLE `repair` ( `id` bigint NOT NULL AUTO_INCREMENT, `student_id` bigint NOT NULL COMMENT '报修学生ID', `dormitory_id` bigint NOT NULL, `content` varchar(500) NOT NULL COMMENT '报修内容', `repair_type` varchar(50) DEFAULT NULL COMMENT '报修类型:水电/家具/门窗', `status` tinyint DEFAULT '0' COMMENT '0待受理 1维修中 2已完成 3已驳回', `handler_id` bigint DEFAULT NULL COMMENT '处理人ID', `handle_reply` varchar(500) DEFAULT NULL COMMENT '处理回复', `create_time` datetime DEFAULT NULL, `finish_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

其他表比如楼栋表、学生信息表、入住记录表、卫生检查表、公告表,结构都比较直接。楼栋表就是楼栋名称、楼栋编号、性别类型、楼层数。入住记录表保存学生和宿舍的关联以及入住时间,退宿时更新退宿时间,保留历史。

3.3 表关系梳理

管理系统的表和表之间就那几种关系。楼栋和宿舍是一对多,一个楼栋有多个房间。宿舍和入住记录是一对多,一个宿舍在不同时间段会住多位学生,但同一时间只能有一个学生占用一个床位。学生和入住记录是一对多,因为学生可能调换宿舍,每次调换生成一条新的入住记录,旧记录标记退宿时间。

报修表和宿舍是多对一,宿舍和学生是一对多关联。卫生检查表和宿舍是多对一,每次检查产生一条评分记录。理解这些关系,后面写JOIN查询就不容易出错。

4. 核心功能的实操实现

4.1 登录认证与权限控制

登录接口不复杂,难的是权限控制不混乱。我用自定义注解配合拦截器实现。先写一个@RequireRole注解,value值表示允许访问的角色。在需要控制权限的Controller方法上加注解,拦截器检查用户角色是否在允许列表里。

JWT工具类主要做三件事:生成token、解析token、判断token是否过期。token的payload里放userId和role,设置过期时间24小时。

public class JwtUtils { private static final String SECRET = "your-secret-key"; public static String generateToken(Long userId, Integer role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + 86400000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }

拦截器里从请求头取Authorization,去掉Bearer前缀,解析token,把userId和role放到request的attribute里,方便后续Controller获取当前登录用户。这里有个坑,前端用Axios设置请求头token时,跨域场景下会先发一个OPTIONS预检请求,预检请求不带token。所以拦截器里必须放行OPTIONS请求,否则前端所有有自定义请求头的接口都会莫名404。

4.2 宿舍分配与调换逻辑

宿舍分配是所有功能里最容易出bug的。分配一个学生入住时,要做四步验证:第一,宿舍和学生的性别必须匹配;第二,宿舍状态必须可用;第三,已住人数不能超过容量;第四,学生当前没有未退宿的入住记录。这四步全部通过,才能插入入住记录并更新宿舍的used字段加1。

因为整个操作涉及多张表更新,必须加事务。在Service实现类上打@Transactional,保证插入入住记录失败时宿舍人数不会被污染。

调换宿舍的逻辑是释放旧宿舍,占用新宿舍。如果只更新入住记录而不处理两间宿舍的人数增减,第二天看统计报表就会发现人数对不上。我第一版就是这个bug,调换十个人后楼栋总人数凭空多了不少。正确做法是:事务里先更新新宿舍used加1,再更新旧宿舍used减1,同时把入住记录的dormitoryId改成新房,更新时间,最后记录一条调换日志。

注意:宿舍分配功能里,事务和并发是两大杀手。真实场景下两个宿管员同时给不同学生分配同一间宿舍的最后一个床位,如果不做校验,就会出现超住。简单解决方案是在dormitory表加一个校验SQL,update时带上condition:update dormitory set used = used + 1 where id = ? and used < capacity。影响行数为0说明没床位了。

4.3 报修流程的状态机

报修流程是体现系统逻辑的一个好例子。我定义了一个状态机:待受理(0) -> 维修中(1) -> 已完成(2),同时允许受理人点击驳回,状态变为已驳回(3)。

学生提交报修时,前端只传content和repairType,后端自动带上当前登录学生的ID和宿舍ID,状态默认0。宿管员列表页看到待受理单据,点击“受理”按钮,状态变为1,同时记录handlerId和处理时间。维修完成后,宿管员填写处理意见,状态变为2。学生端可以查看自己提交的报修单,按状态筛选。

有人会问,为什么报修完成不由学生确认?现实中有些学校是学生确认销单,但这样容易产生纠纷:宿管员说修好了,学生说没修完,单据就一直挂着。我更建议由宿管员闭环处理,学生只能对处理结果留言评价。这样流程短、状态明确,管理上也好考核宿管员的响应时效。

4.4 卫生检查评分模块

卫生检查的评分维度我设计了四个:地面清洁、床铺整洁、桌面物品摆放、用电安全。每个维度满分25分,总分100。检查记录表存四个维度的分数和总分,同时关联宿舍和楼栋,方便按楼栋汇总排名。

评分的核心功能是月度统计。写一个SQL按楼栋分组,求出当月平均分,再在前端用ECharts做柱状图展示。这里要注意,卫生检查数据会持续增长,统计SQL必须加create_time的月份条件,并且索引要建在create_time上,不然几个月后查询速度就会明显下降。

前端实现用el-table展示检查记录,支持按楼栋、时间范围筛选,每行后面一个“编辑”按钮,弹窗修改维度分数。新记录的录入用el-form表单,分数输入框用el-input-number组件,限制范围0到25,校验规则不能漏。

5. 项目运行环境配置与部署

5.1 本地开发环境准备

后端需要JDK 1.8、Maven 3.6以上、MySQL 5.7或8.0,前端需要Node.js 14以上。如果你用IDEA,直接把项目pom.xml引入,等Maven下载依赖。前端在工程目录下打开终端执行npm install,安装时间长短看网络情况。

我第一次搭建这个环境时被Node版本坑过。项目里的node-sass依赖对Node版本敏感,Node版本太高直接编译失败。后来锁定了Node 14.21.3,npm install才能通过。现在做Vue 2 + Element UI项目,建议直接用nvm管理Node版本,避免环境问题浪费一下午。

5.2 后端启动步骤

后端启动的核心是数据库配置。在application.yml里填数据库连接信息,注意MySQL 8的驱动类名和时区参数。

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/dormitory?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: yourpassword type: com.alibaba.druid.pool.DruidDataSource

把项目附带的dormitory.sql导入数据库,执行方式很简单:在Navicat或命令行里source文件。启动类直接右键运行,看到Spring Boot的启动日志,最后一行显示Tomcat started on port 8080就说明成功了。可以用POSTMAN测试登录接口,返回token就代表数据库连接正常。

5.3 前端启动与打包方式

前端启动执行npm run serve,默认端口8080,和前端端口一致会冲突。没关系,前端配置里把代理指向后端,开发环境的跨域问题就解决了。在vue.config.js里设置proxy。

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };

打包发布时执行npm run build,会生成dist目录。这里有两种部署方式。第一种是前后端完全分离,把dist目录放到Nginx服务,Nginx配置反向代理,/api开头的请求转发到后端SpringBoot的8080端口。第二种是合并部署,把dist目录里的静态资源复制到SpringBoot的src/main/resources/static目录下,打成jar包一起运行。

server { listen 80; server_name yourdomain.com; location / { root /usr/share/nginx/html/dormitory; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

Nginx配置里try_files那行不能省,否则前端路由用history模式刷新页面时会404。这个问题几乎所有Vue项目部署都会遇到,我记不清帮人排查过多少次了。

5.4 生产环境部署要点

生产环境和本地有两点不同。第一,MySQL密码不能明文写在application.yml里,可以通过环境变量注入,比如写成${DB_PASSWORD}。第二,JWT的SECRET也要换成一个足够长的随机字符串,不能把代码里默认的secret带上线。

服务器上跑SpringBoot项目,用java -jar dormitory-system.jar启动是最简单的。为了可靠,建议用systemd配一个服务,设置Restart=always,这样进程意外退出会自动拉起。数据备份用crontab定期执行mysqldump,备份文件保留一周的量就够。

6. 常见问题与排查技巧

6.1 前后端联调阶段的坑

联调是出问题最多的阶段。我列几个高频问题给你参考。

现象可能原因解决办法
前端请求接口报404后端接口路径和前端请求路径不一致确认Controller的@RequestMapping和Axios请求URL完全一致
请求能发出但后端收不到参数JSON格式不对,字段名映射不上后端用@RequestBody接收,前端data必须是JSON对象;用@RequestParam时前端要用params传
带token的请求报跨域错误拦截器拦截了OPTIONS预检请求拦截器放行OPTIONS请求,处理方式见4.1
后端修改代码后前端看不到效果前端代理指向了旧地址检查vue.config.js的target端口,确认后端启动端口

这里还想多说一句排查思路。遇到接口问题先在浏览器Network里看请求URL、请求方式、请求体和响应体。大多数问题在这一步就能定位,不要直接跑到后端打印日志,那样效率很低。

6.2 数据库连接和数据问题

第一个常见问题是MySQL 8连接报Public Key Retrieval is not allowed。这是因为MySQL 8默认的caching_sha2_password认证插件要求客户端做RSA密钥交换。解决方法是JDBC连接串加上allowPublicKeyRetrieval=true,或者直接把依赖的MySQL驱动更新到8.0.20以上。

第二个常见问题是中文乱码。建库时没指定utf8mb4,存进去的中文变成问号。注意两点:建库语句用CREATE DATABASE xxx DEFAULT CHARACTER SET utf8mb4,JDBC连接串带characterEncoding=utf8,两处配合才能彻底解决。

第三个问题是springboot版本太高导致的兼容性报错。如果你看到类似“Cannot resolve symbol ‘springframework’”或者依赖版本冲突,建议直接改成2.7.18这种稳定版本。不要看到新版本就往上冲,稳定压倒一切,这句话在做项目时永远成立。

6.3 MyBatis Plus的使用细节

MyBatis Plus用起来方便,但有几个细节不注意会浪费半天。实体类属性用驼峰命名,数据库字段用下划线命名,MyBatis Plus默认开启驼峰映射,不需要加额外注解。但如果数据库字段是createTime(不带下划线),映射会失败,需要@TableField("create_time")显式指定。

还有一个分页问题。MyBatis Plus的分页插件必须配置PaginationInnerInterceptor才能生效,不是引入依赖就自动有分页的。没配置插件时,page方法查出来的total永远是0,这个问题我在交流群里见过至少五次。

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

7. 扩展思路与个人经验

7.1 把这个项目往上多走一步

如果你的时间允许,我建议在基础版本上做三个小升级。第一,引入Redis缓存楼栋人数统计结果,查宿舍首页时直接从缓存取,减少数据库压力。第二,增加Excel导入导出功能,用EasyExcel实现学生批量导入和报修记录导出,这也是实际宿管工作中很强烈的需求。第三,用WebSocket做通知推送,比如学生提交报修后,宿管员的待办列表实时收到新提醒,不需要手动刷新页面。

三个功能在技术上都不复杂,但能让你的项目从“毕设Demo”往“可用系统”方向迈一大步,简历里也有东西可写。

7.2 从做系统到做工程

最后聊点个人的体会。做这个项目的过程中你会遇到很多问题,搜索、阅读、测试、修复,这个循环本身就是工程能力的训练。我在带新手做项目时经常说:代码能跑起来只是第一步,更重要的是知道“为什么这么写”——为什么分配宿舍要加事务、为什么拦截器要放行OPTIONS、为什么表结构要冗余占用人数。把这些为什么都想清楚,以后再碰到类似的管理系统,你就能快速设计出靠谱的方案。

如果做这套系统是为了答辩,建议把注意力放在数据库设计的完整性和权限控制的安全性上。这两个点是最容易展示专业性的地方,也是评委老师最常追问的方向。愿你能顺利完成自己的项目。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询