ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于SpringBoot+Vue的宿舍管理系统设计与部署全流程解析

基于SpringBoot+Vue的宿舍管理系统设计与部署全流程解析

如果你最近正在为毕业设计选题发愁,学生宿舍管理系统大概率已经躺在你的备选清单上了。这个题目不惊艳,但它特别稳:业务需求贴近真实生活,角色划分清晰,前后端分离的主流技术栈正好能展示,而且从数据库建模到界面交互,每个环节都有充足的素材写进论文。我完整跟过一套 SpringBoot + Vue + MySQL 的宿舍管理系统,从源码结构、数据库脚本、论文初稿到部署文档全程走下来,今天就把这个过程踩过的点、绕过的坑一一掰开讲。这个题目的价值不在于“新”,而在于“全”,做完它你对后端的接口设计、前端的权限路由、数据库的表关系都会有一个非常扎实的体感。

1. 为什么选这个题目,以及系统整体是怎么搭起来的

1.1 这个选题的价值在哪

毕业设计最怕的不是题目难,而是题目空。有的题目看起来高端,结果光是环境搭建就折腾一个月;有的题目过于简单,答辩的时候老师问两句就露怯。学生宿舍管理系统恰好避开了这两个极端。

它的业务场景足够具体:学生要入住、调宿、退宿;宿舍楼里有报修、有访客、有归寝检查;管理员要分配床位、发布公告、统计入住率。这些都是宿舍日常管理的真实动作,需求分析的时候能写出一大堆用户故事,论文里也不愁没有素材。

从技术角度看,它又是一个非常标准的 CRUD 项目加业务流项目:登录鉴权、角色权限、多表关联查询、事务处理、文件上传、数据统计,这些后端基本功全部覆盖;前端则对应登录页、布局框架、列表页、表单页、统计图表,前后端所有常规组件都能练一遍。更重要的是,它的业务复杂度可控,不会让一个毕业生在两个月内陷入无限改 bug 的泥潭。

1.2 技术栈与整体架构

我最终采用的组合是 SpringBoot 2.7 + Vue 3 + MySQL 5.7,ORM 用 MyBatis-Plus,前端构建用 Vite,UI 组件库用 Element Plus,状态管理用 Pinia。这套组合在一线开发里非常常见,招聘要求里的出现频率也很高,拿出来讲不会被认为是“玩具项目”。

架构上是标准的前后端分离:

  • 后端单独启动一个 SpringBoot 服务,提供 RESTful API,端口 8080;
  • 前端单独启动一个 Vue 项目,开发环境访问 5173,通过代理转发/api请求到后端;
  • MySQL 单独存储所有业务数据;
  • 生产环境用 Nginx 托管前端打包后的静态文件,同时反向代理后端接口。

这里我要重点说一个很多人会搞混的概念:前后端分离并不是“两个项目互相独立就行了”,而是前端只管页面渲染和交互,后端只管数据接口和业务规则。前端不能直接连数据库,后端不能返回一堆 HTML 页面。这样分工之后,学生端、宿管端、管理员端其实共用一套后端接口,只是前端根据角色控制能看到的菜单和能点击的按钮。

1.3 交付物清单

一个能打高分的毕业设计,交付物绝对不是“代码能跑”四个字。我建议大家按这份清单准备:

  • 完整源码,包括前后端两个目录,且要求代码可以直接编译运行,不要留一堆 TODO;
  • 数据库初始化脚本,包含建库、建表、初始化管理员账号、示例数据;
  • 毕业论文,一般学校要求不低于 8000 字,包含需求分析、系统设计、数据库设计、系统实现、系统测试;
  • 部署文档,重点写清楚环境要求、每一步操作命令、常见问题;
  • 演示视频,5 分钟左右的录屏,从头登录到核心功能操作一遍,答辩放这个非常加分。

很多同学把源码当成唯一交付物,数据库脚本放在项目里的某个角落,部署文档懒得写。等答辩当天老师让你现场部署,你才发现自己连数据库连接串都记不清楚,这就非常被动。所以下文我会详细展开每一部分怎么做,尤其是数据库设计和部署文档这两个容易被轻视的部分。

2. 数据库设计才是宿舍管理系统的真正核心

2.1 核心业务表有哪些

宿舍管理系统听着简单,但真正开始整理表结构的时候,你会发现业务实体比想象中多得多。我把一套完整的表设计拆出来供参考:

表名用途关键字段
t_user登录账号username, password, role, status
t_student学生基础信息student_no, name, gender, college, dorm_id, bed_no
t_building宿舍楼栋name, floor_count, manager, phone
t_dorm宿舍房间building_id, room_no, bed_num, current_num, status
t_repair报修工单dorm_id, student_id, category, description, status
t_visitor访客登记student_id, visitor_name, phone, in_time, out_time
t_attendance归寝记录student_id, date, status, check_time
t_notice公告title, content, create_time
t_transfer调宿申请student_id, from_dorm_id, to_dorm_id, reason, status

这里每一张表都有存在的意义。t_user 和 t_student 为什么要分开?因为一个学生账号可以被停用,但学生的住宿信息还在;管理员的账号和学生根本不在同一个信息维度里。如果不分开,后期改密码、加权限的时候会非常别扭。

2.2 宿舍、床位、学生三者怎么建模

宿舍分配是这个系统里最容易出错的点。很多新手会把“床位数”直接当字段存在宿舍表里,然后学生入住的时候手动改一下数字。这样确实简单,但它没办法解决一个问题:宿舍一共有 4 个床位,1 号床、2 号床分别是谁?

如果不建模到“床位”这个级别,你根本无法直观地看到某个宿舍哪张床是空的。所以我建议至少在业务逻辑上把床位看成一个虚拟实体,学生表里的bed_no字段就代表这张床属于哪个宿舍、是几号床。分配宿舍的时候要做两步校验:

  1. 宿舍状态必须是“未满”;
  2. 床号在当前宿舍内必须是唯一的。

这两步校验要在后端事务里同时完成,否则会出现两个学生分配到同一张床的严重并发问题。我在初版实现里就踩过这个坑,后来在宿舍分配接口上加了@Transactional,并且用数据库唯一索引兜底,才算真正解决。

CREATE TABLE t_student ( id bigint PRIMARY KEY AUTO_INCREMENT, student_no varchar(32) NOT NULL, dorm_id bigint NOT NULL, bed_no int NOT NULL, UNIQUE KEY uk_dorm_bed (dorm_id, bed_no) );

这个联合唯一索引就是最后的防线。逻辑上就算代码漏判断,数据库也会直接拒绝同一宿舍插入两条相同床号的记录。

2.3 状态字段与枚举值的设计

宿舍管理系统里有大量状态字段,比如宿舍状态、报修状态、归寝状态。状态值我强烈建议用tinyint整数,不要直接用中文或者英文字符串。

举个例子,宿舍状态可以定义为:

  • 0:空置
  • 1:部分入住
  • 2:已满
  • 3:维修中

用整数存的好处是查询快、存储小,而且前端可以用状态码映射成标签显示。缺点是直接看数据库时不直观,所以你要在项目里建一个枚举类统一管理,后端返回给前端的时候转成字典值。

报修状态我分成“待受理、处理中、已完成、已评价”四个阶段。每一步都有对应时间字段:提交时间、受理时间、完成时间。答辩时老师问“你怎么保证报修流程的完整性”,你就可以说我是通过状态机加时间戳来记录的。

2.4 初始化数据脚本里容易忽略的细节

数据库初始化脚本不是随便建几张表就完事。我见过很多同学的init.sql只建表,不插入任何数据,导致系统一登录就是空的,看不到任何效果。

正确的做法是准备三部分数据:

  1. 必填的基础字典数据:管理员账号、角色菜单、状态枚举说明;
  2. 示例业务数据:5 栋楼、每栋楼若干宿舍、20 个学生分布在部分宿舍里、若干条报修和归寝记录;
  3. 边界测试数据:一个已满的宿舍、一个维修中的宿舍、一个没有任何学生的空楼栋。

有了这些数据,前端页面不用费劲造数据就能直接展示效果,论文里的功能性测试也能直接跑起来。另外要注意 MySQL 5.7 和 8.0 的建表语法差异,如果脚本里用了DEFAULT CURRENT_TIMESTAMP,8.0 对同表多个时间戳字段的默认值要求更严格,环境切换时容易报错。

3. SpringBoot 后端:从登录鉴权到业务闭环

3.1 目录分层别偷懒

后端项目结构直接影响你后面写论文和答辩的流畅度。我用的分层是:

com.example.dorm ├── controller // 接口层,只负责参数接收和结果返回 ├── service // 业务逻辑层 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 前端传输对象 ├── common // 统一返回结果、异常处理 └── config // 配置类、拦截器

controller 里不要写业务逻辑。有的同学把查询、判断、更新全都堆在 controller 里,一个方法写几十行,看似省了很多类,实际上维护起来非常痛苦。你只要记住一句话:controller 只做三件事——接收参数、调用 service、返回结果。业务逻辑全部下沉到 service,数据访问全部交给 mapper。

3.2 登录鉴权:JWT 拦截器方案

毕业设计项目我不建议引入 Spring Security 全家桶,配置量大,学习成本高,答辩的时候反而说不清楚。更好的选择是 JWT 加自定义拦截器,代码量小,原理也容易讲明白。

思路是这样的:登录成功后,后端把用户 id、角色、姓名等信息加密成一个 token 返回给前端。前端每次请求在请求头里带上Authorization: token。后端定义一个拦截器,对所有非登录接口校验 token,解析成功后把用户信息放进请求上下文。

@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } request.setAttribute("userId", JwtUtil.getUserId(token)); request.setAttribute("role", JwtUtil.getRole(token)); return true; } }

角色权限怎么控制?很简单,在每个 controller 方法上做个@RequireRole("ADMIN")注解,拦截器里再判断当前角色是否满足要求。这样做的好处是权限规则直接写在接口旁边,可读性很高。

3.3 典型业务:入住、报修、归寝的后端闭环

宿舍管理系统里的业务不只是“增删改查”,有几条业务流必须要在后端形成一个闭环,这是答辩时的加分点。

第一个是学生入住。流程是:管理员选择学生和宿舍 → 判断宿舍是否未满 → 判断床号是否唯一 → 给学生写入宿舍和床号 → 宿舍当前人数加一 → 若住满则更新宿舍状态为已满。这五个动作必须在一个事务里完成。我建议你把这一段整个写在 service 方法里,用@Transactional标注。

第二个是报修。学生提交报修后,由宿管员受理并生成处理记录,维修完成后状态改为已完成,学生可以查看处理结果。后端接口至少要提供三个:提交报修、处理报修、查看历史报修。这三个接口对应三种角色身份,正好能体现权限设计的价值。

第三个是归寝管理。传统做法是宿管在后台按宿舍楼、按日期录入归寝情况,系统后台生成当天未归名单,并提供补录和请假接口。统计未归人数时可以用一个聚合查询,按日期分组统计,这个 SQL 写进论文里也很有说服力。

3.4 事务、异常与查询性能

我在实际调试中发现,最容易出现事务失效的场景是@Transactional加在了私有方法上,或者在同一个类内部进行方法调用。SpringBoot 的事务是通过代理实现的,同类内部调用根本不会经过代理,所以事务就失效了。正确做法是:要么把需要事务的方法放到不同类里,要么直接将事务方法定义为 public。

异常处理这块,我统一封装了一个Result<T>返回体,正常返回code=200,业务异常返回code=500并带错误信息。在后端加一个@RestControllerAdvice全局异常处理器,任何未捕获的异常都会转成统一结构,前端就不用疯狂解析各种奇形怪状的报错格式了。

查询性能方面,学生列表默认要分页,并且必须使用连表查询返回宿舍号和楼栋名。MyBatis-Plus 的分页插件配置很简单,但要注意在多表关联时不要用Page直接包一个自定义 SQL,容易导致 count 查询出错。

4. Vue 前端:动态菜单、权限路由和页面复用

4.1 技术选型为什么用 Vue3 + Element Plus

前端我用的是 Vue 3 + Vite + Element Plus + Pinia。Vue 3 的组合式 API 写起来比 Vue 2 的选项式更清晰,尤其是处理表格查询、分页、弹窗这类业务逻辑的时候,setup里所有状态一目了然。

Element Plus 的表格、表单、分页、弹窗、标签、上传组件非常齐全,几乎覆盖了宿舍管理系统的全部页面需求。你不需要手写复杂的 CSS 布局,把精力放在业务逻辑上更重要。

4.2 axios 统一封装

前端必须封装一个 axios 实例,统一处理 baseURL、请求头、超时时间和错误提示。我的做法是创建src/utils/request.js,在请求拦截器里自动把 token 从 localStorage 取出来加进请求头;在响应拦截器里判断后端返回的 code,如果是 401 就跳转登录页,如果是业务错误就弹出统一的错误提示。

import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res } ElMessage.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) }, error => { if (error.response && error.response.status === 401) { router.push('/login') } else { ElMessage.error('网络异常,请稍后重试') } return Promise.reject(error) } )

注意开发环境要用 Vite 的代理转发,在vite.config.js里配置/api指向http://localhost:8080,这样前端代码里就不会写死后端地址,后期部署迁移也方便。

4.3 一个标准 CRUD 页面怎么写

宿舍管理系统里大多数页面都是“表格 + 搜索表单 + 弹窗表单 + 分页”的组合。你会发现这个模式只要写熟练一个,后面全都能照葫芦画瓢。

以“报修管理”页面为例,核心代码逻辑是:

  1. searchForm保存查询条件,比如宿舍楼栋、报修状态;
  2. 每次查询调用getRepairPage接口,传当前页码和搜索条件;
  3. 表格绑定tableData,分页组件绑定total和pageNum/pageSize;
  4. 点击“处理”按钮弹出携带当前行数据的抽屉或对话框。

这里有个小经验:凡是带状态的列,前端不要直接显示数字,应该用一个statusMap对象映射成中文标签,再用 Element Plus 的el-tag渲染不同颜色。比如“待受理”用 warning 色,“处理中”用 primary 色,“已完成”用 success 色。这样视觉上立刻就能区分业务状态,不用查数据库看含义。

4.4 几个加分的小交互

基础 CRUD 谁都会写,但以下几个功能能让你的系统在答辩时明显超出预期:

第一个是仪表盘统计。首页展示总学生数、总宿舍数、当前入住率、今日未归人数,用 ECharts 画一个柱状图和饼图。这些数据来自后端一个聚合接口,把几行 SQL 统计结果聚合返回,技术含量不高但效果非常直观。

第二个是导出 Excel。列表页加一个“导出”按钮,点击后直接把当前搜索结果全部导出为 Excel 文件。用 EasyExcel 在后端实现,代码量很少,但在教研室演示时很实用。

第三个是公告自动滚动。系统首页轮播最新公告,用 Element Plus 的el-carousel组件就行。这个小功能看起来不起眼,却能让系统“活”起来,而不是一堆死表格。

5. 部署交付:从本地跑通到写出能照着做的部署文档

5.1 环境准备与版本匹配

部署文档的第一章必须写清楚环境要求,不要默认读者什么环境都装好了。宿舍管理系统最常用的环境是:

软件版本建议备注
JDK1.8 或 11项目 pom.xml 中指定
Maven3.6 以上用于打包
Node.js16 或 18Vite 5 对 Node 版本有一定要求
MySQL5.7 或 8.0注意驱动差异
Nginx1.20 以上生产环境托管前端

版本坑是最多的。比如某同学本地 MySQL 是 8.0,项目里驱动还写着com.mysql.jdbc.Driver,启动直接报错,必须改成com.mysql.cj.jdbc.Driver,并在连接串里加上serverTimezone=Asia/Shanghai。这些坑踩一次就够了,文档里一定要提前写好。

5.2 前后端打包和启动

后端的打包命令非常简单,在项目根目录执行:

mvn clean package -DskipTests

打包成功后会在target目录生成一个dorm-server.jar。本地启动就用:

java -jar dorm-server.jar

如果你是在 Linux 服务器上部署,别忘了先检查防火墙和端口占用。我遇到过项目在本地跑得好好的,一到服务器就报端口被占用,结果发现是以前启动的进程没杀掉。

前端打包更简单:

npm install npm run build

执行完会在dist目录生成静态文件,这就是部署时 Nginx 要指向的目录。要强调一点:打包之前一定确认有写后端接口地址的地方是不是用了相对路径api开头,如果你在 axios 里写死了http://localhost:8080/api,打包后部署到服务器上就全废了。

5.3 Nginx 反向代理的配置

Nginx 在这套部署里的作用有两个:托管前端页面,代理后端接口。完整配置可以这样写:

server { listen 80; server_name your-server-ip; root /usr/share/nginx/html/dist; index index.html; location / { 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; } }

这里最关键的是try_files $uri $uri/ /index.html;。Vue 是单页应用,路由在浏览器端控制,你直接访问某个子路由时 Nginx 必须把所有未匹配路径都回退到index.html,否则一刷新就 404。

5.4 部署文档该怎么写

部署文档不是给懂行的人看的,是给“什么都不知道”的答辩老师或者下一个接手的同学看的。我的建议是写给傻瓜也能照着做:每一条命令单独一行,执行结果是什么样也写清楚,遇到什么报错应该怎么处理也列出来。

部署文档结构可以这样组织:

  1. 环境要求表;
  2. 数据库初始化步骤,包括直接用 navicat 新建数据库、执行 sql 脚本的过程;
  3. application.yml 里需要改的配置,用截图圈出来;
  4. 后端打包启动命令;
  5. 前端打包步骤;
  6. Nginx 配置和启动;
  7. 常见问题排查表。

我当时还做了一件小事,把部署过程中所有的命令整理成一个.sh脚本,服务器上直接执行就能一键发布。虽然毕业设计不要求这么细,但在项目展示时拿出来讲,老师对工程化能力的认可度会明显提高。

6. 论文写作与答辩准备的四个关键细节

6.1 论文结构怎么排

论文结构按学校模板来,内容逻辑上我建议务必覆盖这七块:绪论、相关技术介绍、需求分析、系统总体设计、数据库设计、系统详细实现、系统测试。

很多同学容易犯的错是把相关技术介绍写成名词解释大杂烩。正确的做法是每一段技术介绍都要紧跟“本系统如何用它”。比如介绍 Vue 时,不要只写 Vue 是什么,还要写“本系统采用 Vue3 构建学生管理模块,利用组件化开发封装了宿舍分配弹窗”等具体内容。这样答辩老师才会觉得你是真做了,而不是抄了一堆概念。

6.2 图表怎么截

论文里的图表是整个分数的直接体现。首先是 ER 图,用 ER 图把 t_student、t_dorm、t_building 之间的关系画清楚。然后是系统架构图,前后端分离的这个架构图一定要画,很直观。最后是系统功能结构图,按角色分树形结构画出模块。

截图的时候不要直接把整个浏览器窗口截下来,身边的杂色和标签页都会影响观感。建议用干净的浏览器窗口,把地址栏隐藏掉,只截内容区域。每个截图旁边一定要加图表编号和文字说明,不能用裸图应付。

6.3 答辩现场会被追问的问题

我把这次毕设展示中可能遇到的问题提前列出来,你可以提前准备:

问题参考回答思路
为什么不用 Vue2 用 Vue3组合式 API 逻辑更集中,生态逐步迁移,新项目选 Vue3 合理
JWT token 过期怎么办前端通过 401 识别,跳转登录页并清除本地 token,可扩展 refresh token 机制
报修流程怎么保证数据一致后端在关键环节开启事务,状态流转用显式赋值并带时间戳
宿舍分配时并发怎么办联合唯一索引加事务,先更新再查询
系统如何应对数据量变大列表分页加索引,统计接口按日汇总,后续可引入缓存

每个问题都要先讲清楚自己的设计思路,不要背答案,最好结合系统里的具体界面说明,这样更容易让老师相信是你自己做的。

6.4 演示 Demo 的注意事项

答辩展示环节最忌讳的就是现场“翻车”。我的建议是在答辩前一天把系统的核心功能录一段视频,按顺序操作登录、分配宿舍、报修处理、归寝统计这几个场景。演示的时候先用录好的视频放一遍,再现场登录系统操作一遍,双重保险。

另外现场演示一定要用真实数据库里的示例数据,不要用空库。如果宿舍列表里一片空白,就算代码没有问题,也给老师留下“没做完整”的印象。所以init.sql里的演示数据一定要精心准备,模拟出一个真实的宿舍楼使用场景。


最后补一句个人体会:这套项目最花时间的地方不是前端页面的花活,也不是后端接口的堆叠,而是数据库设计是不是真的贴合业务、部署文档是不是真的能让人照着跑通。很多同学都把数据库脚本放到最后草草补一个,结果自己项目换台电脑都跑不起来。宿舍管理系统看起来简单,但把简单的事情做到闭环、可展示、可部署,本身就是毕业设计最该有的样子。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进