从选题到上线,这篇说透SpringBoot宿舍管理系统怎么做
又到毕业设计季节,后台收到不少同学问宿舍管理系统怎么做,也有几位在职的朋友想拿这个练手SpringBoot。这题目确实经典——业务边界清晰、角色分明、功能闭环完整,用来演示SpringBoot的Web开发能力再合适不过。但正因为做的人多,网上模板代码质量参差不齐,照抄很容易翻车。
我前段时间刚帮一位学弟把这个项目完整走了一遍,从需求分析、表结构设计到核心模块编码、最后服务器部署,踩了不少坑。这篇就把整个过程中的关键设计决策和实打实的代码经验整理出来,给准备做这个题目的同学一个可以直接复用的参考。
1. 选题定位与需求分析:宿舍管理系统的核心价值在哪
先说选题。很多同学担心“SpringBoot宿舍管理系统”太普通、没亮点。我的看法恰恰相反——对毕设来说,需求边界清晰、逻辑闭环完整、技术延展空间大这三个特性,比题目听起来“高大上”重要得多。宿舍管理系统恰好全占了。
1.1 角色划分与权限边界
宿舍管理系统本质上是一个多角色、多权限的校园后勤业务系统。按我实际梳理,角色至少分四类:
- 学生:查看宿舍分配、提交报修申请、查询水电费、访客登记
- 宿管员:处理报修工单、管理入住/退宿、登记访客、抄录水电表
- 辅导员:查看所辖学生的住宿情况、审批调宿申请
- 系统管理员:维护宿舍楼/房间基础数据、创建用户账号、统计报表
这里有个关键设计点:不要为了“功能多”而堆页面,要把核心业务闭环做扎实。我见过太多项目把“失物招领”“二手交易”“论坛交流”塞进来,结果报修功能还没跑通。一个能完整走通“提交-派单-处理-回访-评价”的报修模块,比五个半成品模块都有说服力。
1.2 核心业务闭环识别
我用一张业务流程图梳理需求(这里不贴图,直接讲逻辑链):
- 住宿管理闭环:学生信息 → 入住分配(宿舍楼/房间/床位)→ 调宿申请 → 退宿登记 → 房间状态变更
- 报修管理闭环:学生提交 → 宿管派单 → 维修工处理 → 学生确认完成 → 评价
- 费用管理闭环:水电表抄录 → 月度账单生成 → 学生查看欠费 → 缴费登记
- 访客管理闭环:访客登记 → 进出时间记录 → 宿舍楼门禁联动(逻辑上)
每个闭环都有状态流转,而状态流转恰好是SpringBoot开发中展示业务逻辑能力的最佳载体。后面我会详细讲报修模块的状态机设计。
1.3 表结构设计:从需求到数据库模型
数据库设计是这个项目最见功力的地方。我最终拆了9张核心表,这里给出建表SQL的关键片段和设计思路:
-- 宿舍楼表 CREATE TABLE dorm_building ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '楼栋名称', manager_id BIGINT COMMENT '宿管员ID', floor_count INT NOT NULL DEFAULT 6, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 宿舍房间表 CREATE TABLE dorm_room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, building_id BIGINT NOT NULL, room_number VARCHAR(20) NOT NULL UNIQUE COMMENT '门牌号', bed_count INT NOT NULL DEFAULT 4 COMMENT '床位总数', bed_used INT NOT NULL DEFAULT 0 COMMENT '已用床位', status TINYINT NOT NULL DEFAULT 1 COMMENT '1可用 0停用', UNIQUE KEY uk_building_room (building_id, room_number) ); -- 学生住宿表 CREATE TABLE dorm_student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, student_name VARCHAR(50) NOT NULL, room_id BIGINT COMMENT '当前房间', bed_no VARCHAR(10) COMMENT '床位号', checkin_time DATETIME, status TINYINT COMMENT '1在住 0已退宿' );有几个设计决策值得展开说:
第一,房间表和床位不单独建表,而是用bed_used字段加bed_no字符串字段记录。
有些同学喜欢把每个床位建一条记录,结果一张四人间宿舍要拆4条床位记录,房间信息大量冗余,查询还要join。我选择在dorm_student表里直接存bed_no,配合bed_used计数器做校验,逻辑更清晰。床位维度的统计(哪几个床位空了)通过dorm_student表查询即可得到。
第二,房间状态与床位数量直接挂钩,引入“乐观锁计数器”思想。
每次分配床位时,代码要校验bed_used < bed_count,然后执行update操作时带上WHERE bed_used = 旧值再SET bed_used = bed_used + 1。这就是乐观锁的简单应用,防止并发分配超员。
第三,逻辑外键而非物理外键。
我建议在外键关系上不用FOREIGN KEY约束,只在代码层保证关联性。原因很现实:毕设项目后期调试时经常要清表重置数据,物理外键会带来一堆删不掉、插不进的麻烦。用逻辑外键(即普通字段加索引)配合MyBatis-Plus的封装,开发效率高很多。
2. 技术选型与项目结构:为什么是SpringBoot + MyBatis-Plus + Vue
技术栈选择直接影响开发效率和论文工作量。我的组合是:
- 后端:Spring Boot 2.7.x + MyBatis-Plus 3.5.x + Spring Security + JWT
- 前端:Vue 3 + Element Plus + Axios + Vite
- 数据库:MySQL 8.0
- 开发工具:IDEA + Navicat + Postman + Swagger
2.1 版本选型的取舍逻辑
Spring Boot选2.7.x而不是3.x,这是很多同学容易踩的坑。Spring Boot 3.0起要求Java 17,并且javax包全部迁移到jakarta,很多老教程的代码会直接报错。如果你用IDEA自带的Spring Initializr新建项目,默认就是3.x,这时候把教程里javax.servlet替换成jakarta.servlet才能跑通。我在项目里统一用2.7.18版,配合Java 8/11都稳定,网上绝大多数资料也能直接参考。
MyBatis-Plus而不是MyBatis,核心是省掉大量CRUD代码。单表查询直接继承BaseMapper<T>,分页查询用PaginationInnerInterceptor,逻辑删除用@TableLogic注解,比手写XML爽太多。不过要注意,MyBatis-Plus只适合单表操作,多表查询还是要手写SQL,不要强行用它的Wrapper处理复杂关联。
2.2 后端项目结构的分层设计
我的package结构如下,这个分层经过了实际项目的检验,写论文画架构图也方便:
com.example.dms ├── config // 配置类:CORS跨域、JWT拦截器、MyBatis-Plus分页插件 ├── controller // 控制层:接收请求、参数校验、返回统一结果 ├── service // 业务层:业务逻辑、事务控制 │ └── impl // 接口实现 ├── mapper // 数据访问层:继承BaseMapper的接口 ├── entity // 实体类:对应数据库表 ├── dto // 数据传输对象:表单请求对象、响应对象 ├── vo // 视图对象:给前端返回的组装数据 ├── common // 通用类:统一返回结果、异常处理、常量、工具类 └── security // JWT过滤器和权限相关代码这个分层的核心思想是**“controller只做转发、service只管业务、mapper只碰数据”**。具体到代码里就是:
- Controller不写任何SQL相关逻辑,只接收参数、调用Service、处理异常
- Service是业务逻辑的核心层,事务注解
@Transactional加在Service方法上 - 实体类不要直接暴露给前端,查询结果组装的VO单独定义
2.3 统一返回体与异常处理的必要性
这是很多同学忽略但答辩必被问的地方。如果每个Controller方法返回类型五花八门,有的返回Map,有的直接返回对象,前端处理会非常痛苦。我统一用了一个Result<T>类:
@Data public class Result<T> { private Integer code; // 200成功 400参数错误 401未登录 500服务器错误 private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }配合一个全局异常处理器@RestControllerAdvice,把业务异常、参数校验异常、未知异常统一转成这个格式返回。这样前端只需要判断code字段就能处理所有响应。我习惯自定义一个BusinessException,业务逻辑里遇到“房间已满”“重复提交”等情况直接抛出,全局处理器捕获后返回对应错误信息。
3. 核心模块实现细节:入住分配、报修流转与水电费账单
骨架搭好了,现在进入最核心的部分。这三个模块是宿舍管理系统的“灵魂”,也是答辩时评委最爱深挖的地方。
3.1 入住分配:并发安全与业务规则校验
入住分配的逻辑不算复杂,但边界场景特别多,而且这些边界场景正是论文里“系统测试”章节的好素材。
核心代码逻辑:
@Service public class DormStudentServiceImpl implements DormStudentService { @Autowired private DormRoomMapper dormRoomMapper; @Autowired private DormStudentMapper dormStudentMapper; @Override @Transactional(rollbackFor = Exception.class) public boolean assignRoom(AssignRoomDTO dto) { // 1. 学生是否已分配宿舍 Long count = dormStudentMapper .selectCount(new LambdaQueryWrapper<DormStudent>() .eq(DormStudent::getStudentNo, dto.getStudentNo()) .eq(DormStudent::getStatus, 1)); if (count > 0) { throw new BusinessException("该学生已分配宿舍,不能重复分配"); } // 2. 房间是否存在且状态正常 DormRoom room = dormRoomMapper.selectById(dto.getRoomId()); if (room == null || room.getStatus() == 0) { throw new BusinessException("房间不存在或已停用"); } // 3. 床位是否充足(乐观锁更新) int rows = dormRoomMapper.update(null, new LambdaUpdateWrapper<DormRoom>() .eq(DormRoom::getId, dto.getRoomId()) .eq(DormRoom::getBedUsed, room.getBedUsed()) .lt(DormRoom::getBedUsed, room.getBedCount()) .set(DormRoom::getBedUsed, room.getBedUsed() + 1)); if (rows == 0) { throw new BusinessException("该房间床位已满,分配失败"); } // 4. 创建住宿记录 DormStudent dormStudent = new DormStudent(); dormStudent.setStudentNo(dto.getStudentNo()); dormStudent.setStudentName(dto.getStudentName()); dormStudent.setRoomId(dto.getRoomId()); dormStudent.setBedNo(dto.getBedNo()); dormStudent.setCheckinTime(LocalDateTime.now()); dormStudent.setStatus(1); return dormStudentMapper.insert(dormStudent) > 0; } }这段代码里最值钱的是第3步的乐观锁更新。两个学生同时申请同一个房间最后一个空床位时,如果没有eq(DormRoom::getBedUsed, room.getBedUsed())这个条件,两个请求都会通过校验然后插进来,造成超员。加上这个条件后,只有一个update能返回1,另一个返回0被拦截。这个点写进论文里,比堆一百个页面都加分。
3.2 报修工单的状态机与流程闭环
报修模块我设计了五个状态:待派单 → 维修中 → 待验收 → 已完成 / 已取消。
public enum RepairStatus { PENDING(0, "待派单"), PROCESSING(1, "维修中"), WAITING_CONFIRM(2, "待验收"), COMPLETED(3, "已完成"), CANCELED(4, "已取消"); private final Integer code; private final String desc; RepairStatus(Integer code, String desc) { this.code = code; this.desc = desc; } public Integer getCode() { return code; } public String getDesc() { return desc; } }**状态流转的核心是:不允许跳状态、不允许逆流转。**比如待验收的工单不能被取消,已完成的状态不能改回维修中。我用一个transition方法统一管理:
public boolean changeStatus(Long orderId, Integer targetStatus, Long operatorId) { RepairOrder order = repairOrderMapper.selectById(orderId); if (order == null) { throw new BusinessException("工单不存在"); } // 校验当前状态是否允许流转到目标状态 Set<Integer> allowedTrans = getAllowedTransitions(order.getStatus()); if (!allowedTrans.contains(targetStatus)) { throw new BusinessException("非法状态流转"); } // 权限校验:不同角色只能执行特定操作 // ... order.setStatus(targetStatus); return repairOrderMapper.updateById(order) > 0; }suspend(挂起)这个状态我没加,因为实际场景里“维修中”本身就包含等待配件、等待师傅到场的情况。状态设计越少,流程越清晰,论文里也越好解释。
3.3 水电费账单:定时任务与数据汇总
水电模块的常见误区是把水电费做成手工录入——管理员每月逐个房间填数字。这样实现简单,但体现不出SpringBoot的特色。我的方案是用定时任务自动生成月度账单。
在启动入口加@EnableScheduling,然后写一个定时任务:
@Component public class BillGenerateTask { @Autowired private DormRoomMapper dormRoomMapper; @Autowired private BillMapper billMapper; // 每月1日凌晨1点生成上个月账单 @Scheduled(cron = "0 0 1 1 * ?") public void generateMonthlyBill() { // 获取上个月的年月 YearMonth lastMonth = YearMonth.now().minusMonths(1); String billMonth = lastMonth.toString(); // 格式:2026-01 // 查询所有有学生入住的房间 List<DormRoom> rooms = dormRoomMapper.selectList( new LambdaQueryWrapper<DormRoom>().gt(DormRoom::getBedUsed, 0)); for (DormRoom room : rooms) { // 查询上个月房间用水用电量(从meter_reading表获取) MeterReading reading = meterReadingMapper.selectOne( new LambdaQueryWrapper<MeterReading>() .eq(MeterReading::getRoomId, room.getId()) .eq(MeterReading::getMonth, billMonth)); if (reading == null) { continue; // 没有抄表记录就不生成账单 } // 计算金额:用水量 * 单价 + 用电量 * 单价 BigDecimal amount = reading.getWaterUsage() .multiply(new BigDecimal("3.5")) .add(reading.getElectricUsage().multiply(new BigDecimal("0.8"))); Bill bill = new Bill(); bill.setRoomId(room.getId()); bill.setMonth(billMonth); bill.setAmount(amount); bill.setStatus(0); // 未缴费 billMapper.insert(bill); } } }cron = "0 0 1 1 * ?"这个表达式的含义是:每年每月1日的01:00:00执行。前面的0 0 1是时分秒,1是日期,*是月份(每月),?是星期(不指定)。这里要注意Cron表达式的坑——SpringBoot的Cron只有6个字段(没有年),秒、分、时、日、月、周,写多了直接启动报错。
另外一个细节:定时任务里的操作要防重复执行。比如刚部署完服务器,定时任务把7月的账单生成了一遍,第二天调整代码重新部署,又执行了一次,就会生成两批重复账单。我在bill表加了唯一索引uk_room_month(room_id, month),插入时用insertOrUpdate,从数据库层面防重。
4. 从开发到部署:IDEA配置、Maven打包与Linux环境适配
代码写完只是第一步,能跑起来、能给别人演示才是关键。这一节讲部署相关的实际操作经验。
4.1 IDEA中SpringBoot的配置细节
IDEA 2026版本(以及其他较新版本)运行SpringBoot项目时,端口配置可以在Run/Debug Configurations里临时指定,也可以通过配置文件统一管理。我的习惯是端口写死在application.yml,因为部署到服务器时用外部配置文件覆盖即可:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/dorm_manager?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 redis: host: 127.0.0.1 port: 6379 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0IDEA热部署的坑:spring-boot-devtools这个依赖建议加上,但它有个很坑的点——修改application.yml后它会自动重启应用,连不上数据库时启动失败会疯狂循环重启。解决办法是在application.yml里配置:
spring: devtools: restart: enabled: true exclude: static/**,public/**,templates/**4.2 Maven构建与打包的必备姿势
Maven打包是毕设演示前最容易翻车的环节。用IDEA右侧Maven面板执行package命令时,SpringBoot项目默认打包的是可执行jar包。有几步必须注意:
第一步,pom.xml里确保有spring-boot-maven-plugin插件:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>第二步,打包时跳过测试:
mvn clean package -DskipTests如果加了spring-boot-starter-test依赖,打包时默认会自动跑单元测试。一旦某个测试类连接数据库失败,整个打包流程直接红灯退出。我习惯把-DskipTests写进Maven配置里,团队开发时也不会互相坑。
第三步,打包产物区分:
打包完成后target目录下会有两个jar:项目名-1.0.0.jar(可执行jar)和项目名-1.0.0.jar.original(普通jar)。必须用不带.original的那个,这才是SpringBoot的可执行jar包,里面包含了所有依赖。.original是给外部容器用的,直接双击运行不了。
4.3 部署到Linux服务器的环境适配
带学弟部署的时候,我发现最大问题不是代码,而是Windows开发环境到Linux运行环境的差异。主要解决三件事:
数据库连接适配:本地MySQL密码是123456,服务器上改成了强密码,代码里直接写死密码很危险。我的方案是启动时用环境变量覆盖:
java -jar dorm-system.jar --spring.datasource.password=${DB_PASSWORD}这样jar包里可以保持默认配置,真实密码只存在服务器的环境变量里。答辩展示的时候,现场改数据库连接也不需要重新打包。
文件路径问题:Windows的C:\Users\xxx在Java代码里要写System.getProperty("file.separator")而不是硬编码\\。我在做宿舍管理系统的头像上传功能时就是用这种方式拼接路径,Linux下才能正常存到/var/www/uploads。
防火墙和端口:服务器安全组和系统防火墙都要放行8080端口。我经常遇到“jar包启动成功但浏览器访问不了”的情况,十有八九是阿里云/腾讯云安全组没配,要么是firewall-cmd没放行。这个坑写进论文的“部署与测试”章节里,反而是亮点——说明你真的遇到并解决了实际问题。
宝塔面板部署:如果你用宝塔面板,“网站”菜单里选“Java项目”,上传jar包、填写端口、设置启动命令java -jar dorm-system.jar,绑定域名后就能直接访问。但要注意宝塔默认使用的是Java 8环境,如果你把Spring Boot 3.x项目部署上去,会因为Java版本不兼容直接启动失败。这也是我为什么坚持Spring Boot 2.7版——兼容性最好。
5. 前后端联调与Vue打包进SpringBoot的几种方式
宿舍管理系统如果只做后端API,页面全靠Swagger看不直观,答辩演示效果差很多。这里分享前后端联调的经验。
5.1 联调阶段:跨域与接口联调最常见的问题
跨域问题是前后端分离开发的第一个坎。前端跑在http://localhost:5173(Vite默认端口),后端跑在8080,浏览器会拦截跨域请求。我的解决方案是在后端配置类统一处理:
@Configuration public class CorsConfig { @Bean public CorsWebFilter corsWebFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("http://localhost:5173"); // 前端地址 config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }这里有个细节:allowCredentials(true)表示允许携带Cookie,但一旦设置为true,addAllowedOrigin就不能用*通配符,必须指定具体地址,否则浏览器直接拒绝。
另一个联调常见问题是日期时间格式。后端返回LocalDateTime默认序列化成"2026-01-15T10:30:00"这种中间带T的格式,前端展示很丑。我的解决方式是在application.yml配置全局的Jackson格式:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+85.2 Vue项目打包后如何给SpringBoot“带”着跑
毕设答辩时最轻松的演示方式是启动SpringBoot jar包,浏览器直接访问8080端口就能看到页面,不用额外启动Vue的dev server。这就涉及怎么把Vue打包产物放进SpringBoot项目里。有两种常用方式,我分别说清楚优劣:
方式一:把dist目录放进SpringBoot的static目录(简单但不好维护)
npm run build # 把生成的dist目录下的文件copy到后端项目的src/main/resources/static/这样打包jar时前端页面会自动包含进去。这种方式最省事,但有个坑:Vue路由如果是history模式,刷新页面时会出现404,因为后端根本没有对应的路径。解决方案一个是改用hash路由(createWebHashHistory),另一个是后端写一个转发控制器:
@Controller public class PageForwardController { // 解决history模式路由刷新404 @RequestMapping(value = {"/", "/login", "/dashboard", "/student/**", "/repair/**"}) public String forward() { return "forward:/index.html"; } }方式二:用Maven插件把前端构建集成进后端打包流程
在pom.xml里配置frontend-maven-plugin,打包后端jar时自动执行npm install和npm run build,然后把产物拷贝进后端资源目录。好处是每次mvn package都能打出一个完整可运行的jar,不需要手动复制;缺点是首次构建需要下载Node环境,比较慢。人演示时间紧张的时候,我推荐方式一,手动copy一次就够了。
5.3 联调中的API前缀统一问题
我习惯在Controller的@RequestMapping里统一加前缀,比如/api/student、/api/admin、/api/repair,前端封装的axios实例统一用baseURL: '/api'。这样前后端都清晰,部署到服务器后如果加了Nginx反向代理,也只需要在Nginx里把/api前缀转发到SpringBoot的8080端口,非常灵活。
6. SpringBoot版本过高的适配问题:从2.7到3.x的几条经验和避坑记录
答辩前一周,学弟突然问“老师让我把SpringBoot版本升到3.x,说更主流,怎么办”。很多同学会在这个环节被难住,其实思路清晰的话半天就能解决。我陪他踩了一遍坑,把这部分经验完整记录下来。
6.1 从javax到jakarta:代码迁移的机械步骤
Spring Boot 3.x最核心的变化是Java EE包名从javax迁移到jakarta。如果你代码里写的是:
import javax.servlet.http.HttpServletRequest;Spring Boot 3.x下直接编译报错“找不到javax.servlet”。全局搜索import javax.,全部替换成import jakarta.。基本上涉及Servlet、Validation(javax.validation)、Annotation(javax.annotation)这几类。IDE的全局替换功能五分钟搞定。
但要注意:有些第三方库内部还在用旧的javax包,如果它们没有升级到兼容Jakarta的版本,就会在运行时抛NoClassDefFoundError。比如某些老版本的pagehelper、fastjson,都会踩到。所以升级前先检查依赖清单,优先用Spring官方或主流社区更新过的库。
6.2 自动装配机制的兼容性变化
Spring Boot 3.x对自动装配的机制也做了调整。2.7时代如果你要自定义一个自动配置类,是在META-INF/spring.factories文件里声明:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.MyAutoConfiguration3.x换成了META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,每行一个配置类的全限定名。很多第三方starter没跟上这个变化,就会在3.x里失效。如果你遇到“明明加了依赖但配置不生效”的问题,优先怀疑这个。
6.3 我最终的建议:不要盲目升级
跟学弟折腾完Spring Boot 3.x的适配后,我的建议其实是如果毕设要求没硬性规定,留在2.7完全没问题。原因有三:
- 2.7仍在社区维护周期内,资料多、报错可查
- 论文的“技术栈选型”章节,写清楚为什么选2.7(稳定性优先、兼容性广)本身就是合理的选型论证
- 3.x带来的主要优势(AOT编译、GraalVM原生镜像等)对毕设场景没有实质帮助,反而徒增复杂度
如果老师确实要求3.x,那上面的迁移步骤足够应付。答辩被问“你如何考虑版本升级风险”时,能说出javax到jakarta的变化、spring.factories到AutoConfiguration.imports的变化,就是加分项。
6.4 关于SpringBoot整合周边组件的提醒
热搜词里有“springboot整合flink”“springboot集成tdengine”“springboot集成activemq”这些整合类的词。宿舍管理系统这类业务系统,不建议为了体现技术深度去硬塞Flink或TDengine——一个宿舍管理系统根本产生不了流式计算和时序数据库级别的数据量,硬整合会让论文“需求分析”和“技术选型”自相矛盾:高并发大数据量的技术栈和低并发轻量级的业务场景不匹配。如果你确实想体现扩展能力,整合一个RabbitMQ或ActiveMQ做报修通知异步化,已经是恰到好处的复杂度。
7. 答辩前最终的部署验证清单与常见故障处理
最后给一份我总结的“答辩前48小时部署验证清单”,这是帮多位同学做完项目上线后沉淀的实操经验。按这个顺序检查,基本能避免现场翻车。
第一项:数据库初始化验证
- 数据库能否用项目自带的
init.sql脚本一键重建?答辩换个环境运行,最怕数据库表缺失 - 把数据库账号密码改成和目标环境一致的,或者确认环境变量注入正常
第二项:jar包启动验证
- 确认
mvn clean package -DskipTests能产出可执行jar - 执行
java -jar xxx.jar,看启动日志有没有红字异常 - 用
curl http://localhost:8080/api/health验证服务存活
第三项:前端资源验证
- 访问
http://localhost:8080/能否正常出现登录页 - 登录、查询、提交工单等核心流程走一遍
- 重点测试刷新页面,确认history路由的404问题已处理
第四项:服务器部署验证
- 服务器防火墙和安全组是否放行端口
- 数据库连接地址是否替换成了服务器地址
- 后台定时任务是否正常触发(可以把cron改短测试,比如每5分钟执行一次,确认数据无误后再改回每月执行)
高频故障对照表:
| 现象 | 原因排查顺序 | 常用解法 |
|---|---|---|
启动时报Failed to configure a DataSource | application.yml数据库连接配置错误 | 检查url/username/password,确认数据库已创建且服务已启动 |
| 前端请求全部401 | JWT拦截器把登录接口也拦截了 | 在拦截器配置里放行/api/auth/login等白名单路径 |
| 上传图片后页面显示404 | 静态资源映射没配 | 配置spring.web.resources.static-locations指向上传目录 |
| 打包后前端页面白屏 | Vue-router使用history模式 | 切换hash模式,或配置后端转发 |
| 刷新页面404 | history模式无转发 | 按上文方式配置PageForwardController |
| 接口返回时间差8小时 | 数据库时区与Jackson时区不一致 | 数据库url加serverTimezone=Asia/Shanghai,Jackson加GMT+8 |
帮学弟走完这个项目的整个过程,我最大的体会是:管理系统类项目能不能做好,不在功能页面的数量,而在核心业务闭环的质量。一个能完整走通入住-报修-账单流程、状态流转逻辑严谨、部署方案清晰的项目,远比堆了十几个不痛不痒的功能模块更有说服力。
如果你正在做这个题目,按上面的方案从表结构设计入手,先把核心闭环跑通,再考虑扩展功能。遇到具体卡点,欢迎来交流。