很多来找我咨询毕设题目的同学,第一句话往往是:“学长,学生宿舍管理系统这种题是不是太简单了?老师会不会觉得没技术含量?”说实话,这种想法恰恰是误区。宿舍管理系统确实属于经典的Web管理类项目,但正因为所有评审老师都见过、都审过这类题目,它反而最能拉开差距:业务建模是否闭环、权限设计是否严密、分配逻辑能否真正处理并发冲突、数据统计是否经得起追问,这些细节决定了你拿到的是“及格毕设”还是“优秀毕设”。
这篇内容我会围绕一个Java+SpringBoot架构的高校宿舍智能管理平台Web项目展开,从业务建模、技术选型、数据库设计、核心功能实现,一路写到踩坑记录和答辩准备。文章面向的是正在做毕业设计、或者刚学完Java Web想拿一个完整项目练手的同学,内容会尽量贴近真实开发思路,而不是教科书式的步骤罗列。如果你正在纠结这类系统“到底怎么做才能不落俗套”,这篇文章应该能帮你省下不少时间。
1. 宿舍管理系统的业务边界:角色、流程与功能清单
很多同学拿到题目第一件事就是打开IDE开始建表写代码,这是最要命的做法。宿舍管理系统看似简单,但如果你没想清楚“谁在用、用哪些功能、流程怎么走”,写着写着就会发现表结构要返工、接口要推翻重来。我见过太多人做到宿舍分配那一步卡住,就是因为当初没把业务边界画清楚。
1.1 三类角色与权限矩阵
高校宿舍管理平台至少要拆出三类角色,不要合并,也不要再多。合并会导致权限失控,再多就会把毕设拖入无底洞。
| 角色 | 核心诉求 | 典型操作 |
|---|---|---|
| 系统管理员 | 管理全局基础数据 | 楼栋维护、宿舍信息维护、账号管理、数据统计 |
| 宿管员 | 负责本楼栋日常运营 | 入住审核、退宿办理、查寝登记、报修处理、水电抄表 |
| 学生 | 使用宿舍服务 | 入住申请、调宿申请、报修提交、公告查看、个人信息维护 |
我建议在项目里把这三种角色做成独立的实体,用角色字段区分,而不是给用户表加一个“type”就草草了事。角色背后对应的是菜单权限和接口权限,这一块做到了,答辩时你就有东西可以讲。
1.2 核心业务流程闭环
一个完整的宿舍管理平台,至少要覆盖“入住—住宿—退宿”的全生命周期,加上围绕住宿产生的服务流。
- 入住流程:学生提交入住申请 → 宿管员审核 → 系统分配床位 → 生成入住记录
- 调宿流程:学生提交调宿申请 → 宿管员审核 → 释放原床位 → 分配新床位 → 更新入住记录
- 报修流程:学生提交报修单 → 宿管员接单 → 维修完成 → 学生确认评价
- 查寝流程:宿管员按楼栋发起查寝 → 登记在寝/离寝状态 → 生成查寝记录 → 异常信息可通知辅导员
- 退宿流程:学生申请退宿 → 宿管员确认 → 释放床位 → 归档历史入住数据
这里有一个很容易忽略的点:入住记录不能简单地在学生表上存一个“宿舍ID”字段就完事。正确的做法是建立独立的入住记录表,保留时间维度。这样退宿之后,历史数据还在,调宿的轨迹也能追踪,统计“当前住宿人数”和“历史入住人数”时才不会乱。我见过好多项目在这个细节上栽跟头,答辩时被老师一问就卡壳。
1.3 功能清单的减法与加法
毕设项目最忌讳一上来就铺一大堆功能,最后哪个都没做透。我的建议是核心功能做深,加分功能做亮。
核心功能(必须做到位):
- 学生管理:基本信息维护、院系班级维护
- 宿舍管理:楼栋、楼层、房间、床位四层结构
- 入住与退宿:申请、审核、分配、释放
- 报修管理:提交、派单、完成、评价
- 查寝管理:发起、登记、统计
- 公告管理:发布、查看
加分功能(用最少的成本换最好的印象):
- 宿舍可视化统计:按楼栋、性别、入住率展示图表
- 水电费管理:每月录入用量,生成费用记录
- Excel导出:学生名单、入住报表、查寝异常名单一键导出
- 报修单PDF打印:方便线下归档
加分项不需要多,做两到三个就够。重点是让老师看到你不只是会写CRUD,而是有“用户体验”和“落地场景”意识。
2. 技术选型复盘:Java+SpringBoot组合的取舍逻辑
技术选型是答辩时老师必问的问题。你要能说清楚“为什么选它”,而不只是“我只会这个”。这里我把我的选型思路完整复盘一遍,你照着这个逻辑去准备,基本不会再被问住。
2.1 为什么是SpringBoot而不是SSM或SpringCloud
很多学校的课程还在讲SSM(Spring+SpringMVC+MyBatis),但如果你做毕设还写一堆XML配置,纯粹是给自己找不痛快。SpringBoot的核心价值是“约定优于配置”,内嵌Tomcat容器,一个mvn spring-boot:run就能把服务跑起来。
从答辩角度讲,SpringBoot有一个巨大的优势:它能让你把精力花在业务逻辑上,而不是纠结applicationContext.xml里那个bean到底注没注入成功。一旦SSM项目启动报错,排查成本非常高,而SpringBoot的自动配置和错误提示已经帮你挡掉了大部分低级问题。
那为什么不用SpringCloud微服务?答案很直接:宿舍管理系统是典型的单体应用,业务量级远没有到需要拆服务的程度。硬上微服务,会引入注册中心、网关、配置中心、分布式事务这些复杂概念,以毕设的开发周期和你的精力来看完全得不偿失。答辩时老师问“为什么不用微服务”,你就说:单体架构在这个业务规模下具备最高的开发效率和运维成本优势,微服务的拆分是业务复杂度驱动的结果,不应为了技术而技术。这句话一出口,档次就不一样了。
2.2 前端方案的权衡:模板引擎还是前后端分离
这个选择决定了你项目的整体长相。我的经验是:如果团队里没人真正写过Vue,老老实实用Thymeleaf模板引擎加上Bootstrap或者小体积的AdminLTE后台模板,三天就能把管理后台界面搭得像模像样。
如果你已经能熟练使用Vue3+Vite+Element Plus,那前后端分离是更好的选择。它的优势在于:动态菜单权限更好做、组件复用度高、答辩演示时页面响应更快。但代价是你要额外处理跨域、Token存储、打包部署这些问题。
我个人建议,除非你对前端已经比较熟悉,否则不要轻易为了“技术潮流”强行上前后端分离。毕设的核心是完整度和逻辑自洽,界面干净整洁、功能流畅已经足够拿高分。当然,如果你已经在简历里写了“熟悉Vue”,那还是上前后端分离吧,不然面试时容易露馅。
2.3 项目分层结构与包命名规范
无论选哪种前端方案,后端的分层结构都建议保持清晰。我的标准结构如下:
com.example.dormitory ├── controller ├── service │ ├── impl ├── mapper ├── entity ├── dto ├── vo ├── config ├── common │ ├── result │ ├── exception │ └── utilscontroller只做参数接收和结果包装,不写业务逻辑service层做业务编排和事务控制mapper层只做数据访问entity对应数据库表结构dto用于接收前端参数,vo用于返回前端数据config放Web配置、拦截器配置、跨域配置
一定要把entity和vo分开。很多同学图省事,直接把数据库实体返回给前端,结果密码字段、内部备注字段全暴露了。你分开了,答辩时就可以讲“我通过VO对象隔离了敏感字段,保证数据安全”,这又是一个加分点。
2.4 MyBatis-Plus在快速开发中的定位
MyBatis-Plus在这类项目中几乎是必选的。它的核心价值不是花哨,而是实打实帮你省掉70%的单表CRUD代码。BaseMapper提供现成的selectById、selectPage、insert、updateById,配合LambdaQueryWrapper可以写出可读性很强的条件查询。
还有一个容易被忽略的杀手级功能:分页插件。你只需要在配置类里注册一个MybatisPlusInterceptor,把PaginationInnerInterceptor加进去,然后调用selectPage,分页就自动完成了。手写分页SQL的时代真的可以过去了。
3. 数据库设计与MyBatis-Plus建表:从实体类到SQL的落地路径
数据库设计是宿舍管理系统真正的分水岭。表建得好,后面写代码是享受;表建得烂,后面每写一个查询都在还债。这一章我直接把核心表结构和几个关键设计决策讲透。
3.1 核心表结构与字段设计意图
我梳理了一套比较标准的表结构,你做的时候可以直接参考:
| 表名 | 用途 | 关键字段 |
|---|---|---|
t_user | 系统账号表 | id, username, password, role, status |
t_student | 学生信息表 | id, user_id, student_no, name, gender, college, major, phone |
t_dormitory_building | 楼栋表 | id, building_no, name, floors, gender_type, manager_id |
t_dormitory_room | 房间表 | id, building_id, room_no, floor, capacity, bed_count, used_bed_count |
t_check_in_record | 入住记录表 | id, student_id, room_id, bed_index, check_in_time, check_out_time, status |
t_repair_order | 报修单表 | id, student_id, room_id, content, images, status, create_time |
t_dormitory_check | 查寝记录表 | id, building_id, check_date, checker_id, status |
t_check_detail | 查寝明细表 | id, check_id, student_id, is_present, remark |
t_notice | 公告表 | id, title, content, create_by, create_time |
t_water_electricity | 水电费表 | id, room_id, period, water_usage, electricity_usage, fee |
这里重点讲几个容易犯迷糊的设计。
房间表为什么要冗余bed_count和used_bed_count两个字段?因为宿舍分配时,系统需要快速判断“这个房间还有没有空床位”。如果每次都去做子查询统计入住记录表,数据量大了以后性能会很差。冗余这两个字段,配合used_bed_count < bed_count条件,一次查询就能过滤出可分配房间。代价是入住和退宿时需要维护这个计数,但这在事务控制下成本很低,收益却很大。
为什么要独立的t_check_in_record表而不是在学生表上改字段?因为“当前住在哪个宿舍”只是学生的一个状态,而“住过哪个宿舍”是一段历史。用状态字段表达历史,数据更新即丢失;用记录表才能保留完整轨迹。退宿、调宿都是往这张表里插入新的记录并关闭旧记录,而不是物理删除。这样设计之后,“统计本学年入住人次”“查某个学生的住宿轨迹”就都变得非常简单,答辩时说到数据可追溯性,又有东西可讲了。
3.2 床位分配模型的两种实现思路
床位分配是宿舍管理系统最核心的业务难点。我见过两种主流做法,各有利弊。
方案A:宿舍表不维护床位数,只在入住记录表里靠房间维度存储
做法:分配时先查入住记录表统计当前入住人数,和房间容量做对比,手动计算余位。
问题:一旦并发操作,比如两个请求同时看到还剩一个床位,同时分配,就超员了。数据量大后每次分配都要聚合统计,性能也很差。这个方案在毕设里虽然也能跑通,但并发冲突你很难圆回来。
方案B:宿舍表冗余床位数和使用数,分配时通过条件更新保证不超员
做法:分配时执行一条带条件的UPDATE:UPDATE t_dormitory_room SET used_bed_count = used_bed_count + 1 WHERE id = ? AND used_bed_count < bed_count。如果更新影响行数为0,说明这个房间已经满了,就换一个房间试。这个方案利用数据库行锁天然解决了并发超卖问题,代码还简单。
方案B在答辩时非常有优势,因为你可以很清晰地解释“我用数据库的原子更新避免了超员,类似秒杀场景的库存扣减思路”。老师一听就知道你理解并发控制,分数不会低。
3.3 从实体类到建表SQL的注解映射与常见坑
MyBatis-Plus官方并不支持直接根据实体类自动生成建表SQL,但网上确实有不少工具可以做类似的事情,很多同学也习惯先写实体类再手动写SQL。这里有几个映射层面的坑,我提醒一下。
第一,@TableName注解里表名如果带下划线,实体类名是驼峰,比如DormitoryRoom对应t_dormitory_room,一定要显式标注,别依赖默认规则,不然PIUS的驼峰映射在某些边缘场景会失灵。
第二,如果表里带了逻辑删除字段(比如deleted字段),实体类对应属性要加@TableLogic。这样MyBatis-Plus执行deleteById时,实际生成的是UPDATE而不是DELETE,数据不会真丢。做毕设时这个功能很有用,比如误删学生信息还能恢复,答辩时也是加分项。
第三,时间字段类型要统一。MySQL的datetime对应Java的LocalDateTime,配合MyBatis-Plus的MetaObjectHandler可以实现插入和更新时自动填充create_time、update_time,省得每次手动set值。具体做法是实现一个MetaObjectHandler的Bean,在insertFill和updateFill里统一处理。
第四,字段里不要用数据库关键字做列名。我就见过有人把表字段命名为desc、rank,结果查询时SQL直接报错,排查半天发现是关键字冲突。类似命名的敏感词提前避开。
第五,如果你确实需要根据实体类生成SQL,可以自己写一段简单的生成工具,解析实体类的@TableName、@TableField注解,生成CREATE TABLE语句。这个工具本身也可以作为一个小亮点写进毕业论文里,标题就叫“基于注解解析的实体类自动建表工具设计与实现”,老师会觉得你有工程化意识。
4. 核心功能实现拆解:登录鉴权、宿舍分配与统计报表
功能实现是项目的重头戏,也是最能体现代码功底的地方。这一章挑三个关键模块展开讲,每个都是我实际开发时反复打磨过的逻辑。
4.1 JWT登录鉴权与拦截器配置
宿舍管理系统有学生、宿管员、管理员三类角色,登录后的权限差异必须通过鉴权层解决。我推荐用JWT + 拦截器的方案,而不是引入Spring Security或者Shiro。原因很简单:毕设项目功能边界清晰,引入安全框架反而要写大量配置类,而且框架本身的过滤器链在初学者手里极易出问题,一旦登录进不去,排查成本极高。
JWT的核心逻辑是:用户在登录接口验证用户名密码成功后,后端生成一个包含用户ID和角色的Token返回给前端。前端将Token存在本地,每次请求时放在请求头的Authorization字段中。后端写一个HandlerInterceptor,在preHandle里从请求头取出Token、解析、校验有效期,把用户信息塞到ThreadLocal里供后续业务代码读取。
核心代码大概是这个思路:
public class JwtUtil { private static final String SECRET = "your-secret-key"; private static final long EXPIRE = 7 * 24 * 60 * 60 * 1000L; public static String generateToken(Long userId, String role) { return Jwts.builder() .claim("userId", userId) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }拦截器注册这一块,我犯过一个低级错误:静态资源也被拦截了,导致登录页面加载不出CSS。后来我在WebMvcConfigurer里加上了排除路径,把/api/auth/**、静态资源路径、错误页全部放行,问题才解决。你在做的时候要记得:
registry.addInterceptor(jwtInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/auth/login", "/api/auth/register", "/error");这里还有一个进阶话题:密码存储。绝对不要明文存密码。至少用BCrypt或者至少加盐MD5。我给你的建议是直接用Spring Security Crypto包里的BCryptPasswordEncoder,引入依赖也不需要启用整个安全框架。密码加密这件事,答辩时被问到的概率极高,这是安全底线问题。
4.2 宿舍分配与调宿的事务控制
宿舍分配是最容易出现“数据不一致”的模块。分配动作涉及两步:更新房间的used_bed_count,然后插入入住记录。这两步要么同时成功,要么同时失败,必须放在一个事务里。
@Transactional注解是最简单的做法:
@Transactional(rollbackFor = Exception.class) public void assignRoom(Long studentId, Long roomId) { // 1. 原子更新床位,条件带上 used_bed_count < bed_count int rows = dormitoryRoomMapper.increaseUsedBed(roomId); if (rows == 0) { throw new BusinessException("该房间已满员,请选择其他房间"); } // 2. 查询床位序号 Integer bedIndex = dormitoryRoomMapper.countUsedBed(roomId); // 3. 插入入住记录 CheckInRecord record = new CheckInRecord(); record.setStudentId(studentId); record.setRoomId(roomId); record.setBedIndex(bedIndex); record.setStatus(1); // 1=在住 checkInRecordMapper.insert(record); }注意几点。第一,rollbackFor = Exception.class一定要写,不然运行时异常归运行时异常,但某些非预期异常可能不会触发事务回滚,这是默认策略问题。第二,bedIndex的查询和入住记录插入之间理论上还有极小的并发窗口,但对于毕设规模的项目来说,配合上面的原子UPDATE已经足够。如果你想让逻辑更严密,可以在房间表加一个bed_status字符串字段,比如101表示1号床位占用、0号床位空闲,用位运算处理。但这就过度设计了,不建议在毕设里搞。
调宿的处理稍微复杂:它等于“释放旧床位 + 分配新床位 + 更新入住记录状态”,同样加个@Transactional包裹即可。这里有个业务细节要注意:调宿申请应该有审核状态,审核通过之后才执行分配逻辑。很多同学把“提交调宿申请”和“执行调宿”混在一起,结果学生自己就能换宿舍,逻辑上说不通。正确做法是:申请表里有status字段,0待审核、1通过、2拒绝。宿管员点击通过时,后端才执行宿舍切换的完整事务。这一步体现了你对业务流审批状态的理解,答辩时值得强调。
4.3 统计报表与Excel导出
统计报表是让项目看起来“高级”的利器。宿舍管理系统最值得做的统计有三个:
第一个,入住率统计。按楼栋分组,查询总床位数、已用床位数,计算入住率。这个数据直接喂给前端ECharts的柱状图或者饼图展示。核心SQL大概是:
SELECT b.id, b.name, SUM(r.bed_count) AS total_beds, SUM(r.used_bed_count) AS used_beds FROM t_dormitory_building b LEFT JOIN t_dormitory_room r ON b.id = r.building_id GROUP BY b.id, b.name第二个,报修统计分析。按报修类型、楼栋、时间范围聚合统计,展示哪类问题最多、哪个楼栋报修最频繁。这些数据虽然是简单的COUNT+GROUP BY,但是配合图表呈现出来,说服力极强。
第三个,查寝异常统计。统计每个学生本学期缺勤次数、晚归次数,生成异常名单。
Excel导出我建议用EasyExcel而不是POI。原因就一个:POI的API太底层,写一个导出要几十行代码,而EasyExcel封装好了,三五行就能搞定,处理大文件时内存占用还更低。导出的数据记得通过VO对象组装,不要直接把实体类塞给导出工具,避免把用户密码这类字段漏出去。
5. 项目开发中的真实踩坑记录:版本、映射与联调
这里写几个我在实际开发中遇到过的典型问题,也是很多同学反复问我的内容。提前看到这些,能帮你少走很多弯路。
5.1 SpringBoot版本过高引发的连锁问题
很多同学现在新建项目,直接去Spring官网生成最新版SpringBoot,结果是3.x甚至更高。SpringBoot 3.0之后有一个大变化:javax包迁移到了jakarta。这导致网上大量旧教程里的代码直接报错——import javax.servlet.*根本找不到类。
如果你用SpringBoot 3.x,所有涉及HttpServletRequest、HttpServletResponse的代码都必须改成jakarta.servlet前缀。而且很多第三方starter的兼容版本可能还没跟上,尤其是某些老牌的Shiro整合方案,在SpringBoot 3上会遇到比较尴尬的境地。这也是我前面建议用JWT + 拦截器的原因之一。
如果你不想折腾这些兼容性问题,最稳妥的做法是选择SpringBoot 2.7.x。它的生态最成熟,教程最多,所有依赖都能找到匹配版本,对毕设来说完全够用。别因为追求新版本给自己挖坑。
5.2 MyBatis-Plus版本不匹配与自动填充失效
MyBatis-Plus的版本和SpringBoot版本要匹配。SpringBoot 2.x对应MP 3.5.x,SpringBoot 3.x需要MP 3.5.3以上的版本。版本不匹配最直观的表现是启动报错,比如Failed to configure a DataSource或者找不到SqlSessionFactory。我之前帮一个同学排查,他把SpringBoot升到3.0但MP还是3.5.1,结果启动失败,换新版本MP就好了。
还有MetaObjectHandler自动填充失效的问题。如果插入记录时create_time没有自动填充,多半是实体类字段没加@TableField(fill = FieldFill.INSERT)注解。自动填充有三个要素缺一不可:实体类字段注解、MetaObjectHandler实现类、注册为SpringBean。少一个就不生效,排查时按这三个环节逐个检查。
5.3 前后端联调中的跨域与Token失效
如果你做了前后端分离,跨域是绕不开的问题。后端写个配置类就能解决:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }但这里有一个很隐蔽的坑:允许携带凭证(cookie)时,allowedOriginPatterns("*")才能生效,如果写allowedOrigins("*")会被浏览器拒绝,因为带了allowCredentials(true)的白名单不允许通配符。改掉这个细节,跨域问题基本就解决了。
Token失效问题也是高频问题。常见表现是:登录之后访问接口,偶尔成功偶尔401。排查思路一般是三个方向。第一,前端有没有在每次请求时从本地取Token并放入请求头;第二,Token过期时间设置得太短,比如设了30分钟,学生填个表单就过期了;第三,后端拦截器对OPTIONS预检请求的处理是否正确。关于第三点,我自己就踩过坑:前端发POST请求时浏览器会先发一个OPTIONS预检请求,如果拦截器把它拦截下来并返回401,浏览器就直接报跨域错误。解决办法是在拦截器preHandle里判断请求方法为OPTIONS时直接放行。
5.4 时间与JSON序列化问题
前后端交互时,时间格式不一致是特别容易出现的问题。后端返回LocalDateTime默认序列化成"2025-01-17T10:30:00"这种带T的格式,前端拿到之后显示在表格里非常难看。解决方案有两种:一种是在application.yml里配置全局格式;另一种是在VO的时间字段上使用@JsonFormat注解。我推荐两种结合,全局兜底,局部定制。
还有时区问题。如果服务器和数据库不在一个时区,或者MySQL连接串里没有加serverTimezone=Asia/Shanghai,查询出来的时间可能会差8个小时。这个坑很阴,因为它不是报错,而是数据看起来“不对”但你又说不清哪里不对。排查时第一反应就查时区配置。
6. 部署与答辩准备:最后一公里的加分细节
程序写完了,功能跑通了,距离拿高分还差最后的部署打磨和答辩准备。这一章的经验是我带过好几届学生总结出来的,每个细节都可能影响你的最终成绩。
6.1 打包部署与服务器上线
后端部署最简单的方式就是用Maven打成jar包,然后丢到服务器上运行:
mvn clean package -DskipTests java -jar dormitory-system.jar --spring.profiles.active=prod这里有几个新手容易犯的错。
- 端口被占用:服务器上8080端口被别的进程占用,启动报错。用
lsof -i:8080查看占用进程,换端口或者杀掉旧进程。 - 防火墙和安全组:很多云服务器默认防火墙没有放开你的应用端口,外网访问不通,但本地
curl又正常。检查安全组入站规则和系统防火墙。 - 数据库连接配置:本地开发用的
localhost数据库连接,部署到服务器后要改成服务器的数据库地址,账号密码也要对应。我建议用application-dev.yml和application-prod.yml两套配置,用--spring.profiles.active切换,避免每次部署都要改配置再重新打包。 - 静态资源路径:上传的文件(比如报修单图片)要保存到配置的绝对路径下,而不是打包进jar内部。jar包内的
/tmp路径在重启后会丢失,文件就没了。 - 外部依赖:Tomcat是内嵌的,不需要单独安装,但要保证服务器上装了对应版本的JDK。SpringBoot 2.x需要JDK 8或11,SpringBoot 3.x需要JDK 17。版本不匹配启动会直接报错。
免费Web服务器方面,如果你只是演示用,国内各大云平台的轻量服务器基础套餐对学生来说性价比很高,新用户还有优惠活动。如果纯粹本地演示,笔记本上启动jar包,用局域网访问也同样可以。关键是演示前要实测一遍完整流程,不要在答辩现场跑出“页面白屏”或“数据库连不上”这种尴尬问题。
6.2 演示数据准备与演示预案
答辩演示最怕的不是功能少,而是数据“秃”。空荡荡的表格没有说服力。我建议提前准备一套完整、真实感强的演示数据:
- 至少两个楼栋,一个男生楼一个女生楼
- 每个楼栋三到五层,每层四到六个房间
- 每个房间六人间或四人间,分配一部分住满、一部分有空位
- 学生信息覆盖多个学院、多个专业
- 报修单准备几条不同状态的数据(待处理、已接单、已完成)
- 查寝记录准备两到三天的数据,其中有一天有异常记录
- 水电费准备好最近三个月的记录
这样演示的时候,每个界面一打开都有内容,统计图表也能正确渲染。不要小看这个细节,很多项目功能是好的,但演示时数据太少,看起来像半成品。
演示之前,最好把整套流程从头到尾走三遍:登录、入住分配、报修、查寝、退宿、统计。注意每一遍都在一个干净的口径下操作,看看有没有异常。我当时带的一个学生,演示前一晚才发现调宿功能在中间态情况下会报空指针,查了一晚上才找到是调宿申请审核通过时,目标房间刚好被退宿释放导致房间ID为空。这类边界条件,只有反复模拟真实流程才能暴露出来。
6.3 答辩高频问题与应对思路
我把学生被问过的问题整理成了一个清单,这些问题出现的概率极高,提前准备好思路,答辩会从容很多。
| 高频问题 | 参考回答思路 |
|---|---|
| 为什么用SpringBoot而不是SSM? | SpringBoot基于约定优于配置,内嵌容器、自动配置,开发效率高,配套生态成熟,适合中小型单体系统 |
| 权限控制是怎么实现的? | 基于角色的访问控制(RBAC),JWT鉴权拦截器校验Token,提取角色信息判断接口访问权限 |
| 宿舍满了怎么办,并发怎么处理? | 通过UPDATE ... SET used_bed_count = used_bed_count + 1 WHERE used_bed_count < bed_count这种方式,用数据库原子操作保证不会超分 |
| 如果调宿时目标房间刚好被占用了怎么办? | 调宿审核通过后执行切换动作时,使用事务加房间更新的条件判断,占用失败则回滚并提示用户 |
| 密码为什么加密?用的什么算法? | BCrypt加盐哈希,即使数据库泄露,明文密码不会直接暴露 |
| 数据量增大后系统会不会卡? | 关键查询走了索引,列表页使用分页查询,统计类报表通过聚合查询,当前业务体量下单库单表完全够用 |
| 系统能不能扩展成微服务? | 可以,但当前业务规模下单体架构开发效率最高,如果未来增加多校区管理、消息推送等场景,可以按领域拆分 |
回答问题时有个技巧:不求说得深,但求说得顺。把每个问题都用自己的话讲一遍,不要背概念,要结合你项目里的具体做法来说。老师最反感的就是“我配了这个框架,具体原理不太清楚”这种回答。你只要能把“做了什么、为什么这么做、遇到什么问题、怎么解决的”讲清楚,就已经超过大多数人了。
最后再分享一个小技巧:做系统的时候,把核心业务的关键日志打出来。比如宿舍分配成功时打印insertLog("分配宿舍", 学生ID, 楼栋-房间-床位)。答辩时如果你能现场调出日志,展示“同学某年某月某日办理入住,分配了6号楼302房间3号床位”,老师会眼前一亮——这说明你的系统具备可审计性,而这个能力在企业级系统中是实实在在的刚需。一个宿舍管理系统做到这个程度,你拿到的就不仅仅是一个毕业设计的高分,而是一段可以在面试时自信讲出来的工程项目经验。