每年到了做课程设计或者毕业设计的时候,都会有一大批人涌向"酒店客房管理系统"这个题目。说实话,这个选题不算新鲜,但每年仍然有很多人选它,原因很简单:业务场景清晰、需求容易理解、技术栈可以覆盖SpringBoot常用知识面,而且演示效果好。但越是看起来简单的题目,越容易做出一个"只是把数据库表格搬到网页上"的CRUD作业。我这次把自己做基于SpringBoot的酒店客房管理系统的整个过程整理出来,包括需求分析、数据库设计、后端核心业务怎么写、源码怎么用、答辩怎么演示,尽量把那些"文档里不会写、但实际开发一定会遇到"的坑都讲清楚。
这个项目适合两类人:一类是做课程设计或毕业设计的学生,另一类是想通过一个完整案例把SpringBoot练扎实的初学者。你不需要有很深的Java基础,只要能看懂Maven依赖、写基本的Controller,跟着这篇文章把逻辑走通,就能把一个能演示、能答辩、能写进简历的项目做出来。
1. 为什么我选了"酒店客房管理系统"当课程设计
1.1 这个题目看起来简单,但想做好并不简单
我见过很多同学的管理系统,打开一看就是五个页面:房间列表、客户列表、订单列表、增删改查、登录退出。这种项目答辩时很容易被老师问倒,因为除了增删改查没有任何业务逻辑,一问"房间状态是怎么维护的""如果两个客人同时订同一间房怎么办"基本就卡住了。
酒店客房管理系统的难度其实刚刚好:它有真实的业务状态流(空闲、预订、入住、退房、清洁),有金额计算(押金、房费、退款),有角色划分(管理员、前台),还有数据统计需求。这意味着它天然需要数据库设计得规整、后端逻辑不只是简单的Mapper调用、页面上也有东西可以展示。把这个系统的业务逻辑想清楚,比单纯堆功能要值钱得多。
另外一个现实因素是,这个题目的参考代码多,市面上能找到的源码和文档也相对多。对课程设计来说,"可参考的成熟项目"意味着你遇到问题时有地方查、有地方问,不至于卡死。但参考多也是双刃剑,如果照着抄不改造,查重和答辩都会很难看,这个后面我会专门讲。
1.2 我从入住流程反推出来的功能清单
做系统之前,不要先想着建表,先想清楚"用户在真实场景里是怎么操作一家酒店的"。我把整个业务流程走了一遍:
- 客人到店或电话/线上预订房间
- 前台确认房态,办理预订登记
- 客人到店办理入住,收押金、分配房间
- 住店期间可以换房、续住、登记消费
- 客人离店,结算房费、退还押金、办理退房
- 房间转为"待清洁"状态,保洁完成后再变为"可预订"
- 管理员需要查看房间状态、入住率、营业额,管理员工账号
按这个流程反推,系统至少要拆成下面几个模块:
- 系统登录与用户管理:管理员和前台账号,不同角色不同权限
- 房间类型与房间信息管理:维护房型、价格、楼层、房间号
- 客房预订管理:预订登记、预订取消、预订转入住
- 入住管理:办理入住、房间分配、押金管理
- 退房结算管理:消费结算、退款、房态更新
- 客户信息管理:登记客户身份证、电话、会员信息
- 数据统计:入住率、营业额、订单趋势(这部分是加分项)
这样拆完你就会发现,系统不是"房间管理+客户管理+订单管理"三个孤立的表格,而是一条完整的业务线。写文档的时候,把这些模块串成流程图,老师一眼就能看出你真的做过需求分析。
1.3 先划清楚系统边界:哪些功能不做
做课程设计最容易犯的错是追求大而全,把会员充值、积分商城、餐饮管理、门锁对接全塞进来。我自己的经验是:这个阶段,砍需求比加需求重要。
理由很简单:功能越多,意味着表越多、关联越复杂、出Bug的地方越多,而你的时间和精力是有限的。一个能把"预订-入住-退房"整条链路做完整、做严谨的系统,比一个打开了八张表但每张表都只做了简单增删改查的系统,分数绝对更高。
我当时给自己定的边界是:不做支付对接(太复杂)、不做多门店(用不上)、不做复杂权限(一个管理员一个前台就够)、不搞Vue前后端分离(课设场景JSP/Thymeleaf或简单Vue都行,但别因为前后端联调耗时把核心业务挤掉)。把有限的精力放在核心业务流程的正确性上,这才是这个阶段该做的事。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计:整个系统的地基,我踩过的那些坑
2.1 核心表的ER关系梳理
数据库是这类系统最容易翻车的地方。很多同学的库表就是一个订单表挂两个外键,结果订单取消、入住、退房的状态全部堆在一个字段里,写到最后自己都搞不清数据到底是啥。
我把表拆成了七张核心表:
sys_user:系统用户表(管理员/前台)room_type:房型表(标准间、大床房、套房等)room:房间表(具体到房间号)customer:客户信息表reservation:预订表check_in:入住单表(也叫订单表)check_out_record:退房记录表(也可以并入入住单)
它们之间的关系是这样的:一个房型下有多个房间,一个房间可以被多条预订记录引用,一个客户可以有多条预订、多次入住。入住单是整个系统的核心,它连接了客户、房间、操作人和退房结算信息。
特别注意,预订和入住我拆成了两张表。这是不少人会忽略的点:预订是"意向",入住是"事实"。客人订了房可能不来,也可能提前到店直接入住没有预订记录。如果强行合一张表,业务逻辑会写得很别扭。
2.2 表结构设计与字段说明
下面把我当时用的核心表结构简化之后贴出来,你可以直接参考,也可以根据自己项目的功能调整。
sys_user 用户表:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar(50) | 登录名,唯一 |
| password | varchar(255) | 加密后的密码 |
| real_name | varchar(50) | 姓名 |
| role | varchar(20) | 角色,admin/前台 |
| status | tinyint | 账号状态,1启用/0禁用 |
| create_time | datetime | 创建时间 |
room_type 房型表:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| type_name | varchar(50) | 房型名称 |
| price | decimal(10,2) | 门市价格 |
| bed_num | int | 床位数 |
| area | decimal(8,2) | 房间面积 |
| remark | varchar(255) | 备注 |
room 房间表:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| room_no | varchar(20) | 房间号,如 801 |
| room_type_id | bigint | 关联房型表 |
| floor | int | 楼层 |
| status | tinyint | 0空闲/1已预订/2已入住/3待清洁 |
| remark | varchar(255) | 备注 |
customer 客户表:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| name | varchar(50) | 姓名 |
| id_card | varchar(18) | 身份证号 |
| phone | varchar(20) | 手机号 |
| gender | tinyint | 性别 |
| is_member | tinyint | 是否会员 |
| create_time | datetime | 首次登记时间 |
reservation 预订表:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| order_no | varchar(32) | 预订单号,手动生成 |
| customer_id | bigint | 关联客户 |
| room_id | bigint | 关联房间 |
| arrive_date | date | 预计到店日期 |
| leave_date | date | 预计离店日期 |
| status | tinyint | 0已预订/1已入住/2已取消/3已完成 |
| create_time | datetime | 预订时间 |
check_in 入住单表:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| order_no | varchar(32) | 入住单号 |
| customer_id | bigint | 关联客户 |
| room_id | bigint | 关联房间 |
| arrive_date | date | 实际入住日期 |
| expect_leave_date | date | 预计离店日期 |
| actual_leave_date | date | 实际离店日期 |
| deposit | decimal(10,2) | 押金 |
| room_fee | decimal(10,2) | 房费总额 |
| other_consume | decimal(10,2) | 其他消费 |
| total_amount | decimal(10,2) | 结算总额 |
| status | tinyint | 0入住中/1已退房/2已取消 |
| operator_id | bigint | 操作人 |
| create_time | datetime | 入住时间 |
这里我加了一个 actual_leave_date,一开始没加,后面退房统计的时候发现根本不知道客人到底哪一天走的,只能查日志,非常被动。这种字段属于"一开始就该想好"的字段,省不掉的。
2.3 状态字段用数字还是字符串
这是一个很小但考试常问的问题。我的做法是:数据库里存数字,代码里用枚举或常量类映射。比如房间状态 0空闲、1已预订、2已入住、3待清洁,Java代码里写一个 RoomStatusEnum,而不是在业务代码里到处散落魔法数字。
好处有两个:一是代码可读性好,是"空闲"还是"已入住"一眼能看出来;二是将来加状态(比如维修中=4)只需要动枚举和涉及的地方,而不是像掏地雷一样到处找字符串。答辩时老师如果问"为什么不用字符串'空闲'直接存",你可以回答:字符串占用空间大、输入不规范会脏数据、枚举维护方便。这本身就是加分点。
2.4 金额与时间字段的精度问题
金额字段必须用 decimal,不要用 double 或 float。这个几乎每次做课设都会有人踩。浮点数在Java和MySQL里都存在精度丢失问题,房费算着算着多了几分钱,做结算的时候对不上账。decimal(10,2) 表示最多存8位整数加2位小数,酒店场景完全够用。
时间字段也要想清楚什么情况用 date、什么情况用 datetime。到店日期和离店日期是"日期",用 date 就够了;入住时间、退房时间、订单创建时间是"日期+时刻",用 datetime。这个细节不复杂,但你在写文档的数据字典时能写明白,至少说明你真的理解字段含义。
还有一点,和时区有关的坑:如果MySQL连接串和服务器时区不一致,datetime 查出来可能比实际时间差8小时。我建议数据库连接后面加上 serverTimezone=Asia/Shanghai,同时实体类里所有时间字段在查询时统一处理格式,避免JSON序列化时输出一长串数字。
3. SpringBoot后端的核心实现思路
3.1 分层结构与目录规划
后端我用的是经典分层:controller、service、mapper、entity、dto、vo、config、common。对于课程设计来说,这套结构足够清晰,答辩介绍时也容易说明白。
一个标准的包结构大概长这样:
code复制com.example.hotel
├── common # 通用类:返回结果、异常处理、常量、枚举
├── config # 配置类:拦截器、CORS、静态资源映射
├── controller # 接口层:接收请求、参数校验、返回结果
├── dto # 前端传入参数对象
├── vo # 返回给前端的视图对象
├── entity # 数据库实体类
├── mapper # MyBatis-Plus的Mapper接口
├── service # 业务逻辑层
│ └── impl
└── utils # 工具类:JWT、日期处理、订单号生成
写Controller的时候,我习惯上不写业务逻辑,只做"接收参数-调Service-返回结果"这三件事。业务规则通通放Service层,比如"办理入住前要校验房间状态""退房时要计算房费",这些只能放在Service里,否则Controller会膨胀到没法维护。
3.2 核心业务接口设计:办理入住、预订、退房怎么实现
我在做项目时把核心业务抽象成这几个接口:
POST /api/reservation新增预订PUT /api/reservation/cancel取消预订POST /api/checkin办理入住POST /api/checkin/checkout退房结算POST /api/checkin/changeRoom换房GET /api/room/available查询可用房间
看起来好像就是几个接口,但里面的逻辑才是灵魂。以"办理入住"为例,如果是从预订单转入住,Service层大致要干这么几件事:
- 校验预订单是否存在、状态是否是"已预订"
- 校验房间状态是否可用(不能被别人抢先入住)
- 创建入住单,录入客户信息、押金、预计离店日期
- 将房间状态改为"已入住"
- 如果是从预订转来的,把预订状态改为"已入住"
- 保存操作人信息,生成入住单号
这一步如果不加事务控制,中间任何一步失败(比如房子分配完、但入住单创建失败),就会造成"房间已经是已入住状态,但系统里没有任何入住记录"的脏数据。所以Service方法上必须加 @Transactional。这个注解是面试和答辩的高频考点,我建议你不仅要会加,还要能说清楚它背后的原理:默认遇到运行时异常就回滚,数据库事务要么全成功、要么全失败。
3.3 登录鉴权:Session还是JWT
课程设计里的登录鉴权,有两种主流方案:传统的 Session 和现在的 JWT。
如果做前后端不分离(服务端渲染页面),用 Session 就够了,SpringBoot里加个拦截器,判断 session.getAttribute("userId") 是否存在。如果做前后端分离(比如前端Vue,后端接口),JWT更合适。
我用的是JWT。原因有三:一是无状态,后端不用保存会话信息,扩展性好;二是移动端和前端都好用;三是在课设论文里能写的内容更多,比如讲讲token的构成(header.payload.signature)、为什么要加过期时间、如何配合拦截器做校验。
JWT的接入也不复杂:
- 登录成功时生成token,把用户的id、用户名、角色放进去
- 客户端拿到token后存在localStorage里,每次请求带在请求头的
Authorization字段 - 后端写一个拦截器或过滤器,每次请求进来先验证token,然后把用户信息放进ThreadLocal
- 放行白名单之外的接口,白名单包括登录接口、静态资源等
需要注意的是,JWT有个"注销难"的问题,服务端没法主动让token失效。课程设计阶段可以不用处理,但如果老师问到了,你要能说得上来:解决方案可以引入黑名单机制把退出的token存起来,或者缩短过期时间。答不上来才是真的扣分。
3.4 怎么防止同一个房间被订两次
这是这个系统里最容易被问到的并发问题。想象这样一个场景:两个前台同时操作,客人A和客人B都看中801房间,同时点了预订。如果代码只是先查房态再更新,大概率会出现"两个人都订成功"的情况。
解决思路有两个方向:
第一种是数据库层面加唯一约束或悲观锁。预订表里可以针对 room_id + arrive_date + status 做约束,但实现起来比较绕,课设阶段不推荐从这个角度切入。
第二种是行锁,查询房间状态时用 SELECT ... FOR UPDATE 把房间这一行锁住,等更新完再释放。这样第二个请求会等第一个事务结束,然后看到房间已经不是空闲,自然就预订失败了。这个方案理解起来直观,答辩时也容易讲。
还有一种更轻量的是乐观锁,在房间表加一个 version 字段,更新时 WHERE version = ?,如果更新行数为0说明被改过了,就提示"房间刚刚被预订,请刷新重试"。这个方案性能好,但实现逻辑会稍微绕一点。
我个人在做课设时采用的是共享锁+事务组合:先从 reservation 和 check_in 表检查重叠日期,再用行锁更新房间状态,这样把"预占"和"确认"两步变成原子操作。这个粒度对你来说可能有点复杂,如果只想快速实现,用悲观锁处理预订接口就够了,关键是让老师看出你有并发控制的意识。
3.5 异常处理与统一返回
一开始写接口时,我每个Controller都返回不同的结构,有的返回Map,有的直接返回实体,前端写得非常痛苦。后来统一成了 Result 对象:
java复制public class Result<T> {
private Integer code; // 200成功,500失败
private String message;
private T data;
}
配合SpringBoot的全局异常处理,在代码里只需要抛业务异常,由全局处理器统一捕获并转成 Result 返回。这样做的好处是所有接口的错误格式一致,前端只要封装一个请求函数,统一判断 code 弹出提示即可。实际开发中这叫"横切关注点"下沉,是很值得写进文档的一个设计点。
密码存数据库前要用加密算法处理,不能用明文。我当时用的是 BCryptPasswordEncoder,它是SpringSecurity里自带的一个加密器,每次加密结果不同但验证仍能通过。这个点老师大概率会问,你要能答出来"我用的这个算法是加盐哈希,即使两个用户密码相同,密文也不一样,能有效防彩虹表攻击"。
4. 拿到源码之后怎么改成自己的
4.1 源码、数据库、文档应该如何使用
市面上这类推荐源码往往是一个压缩包,里面通常包含:后端代码、前端代码、SQL脚本、README或文档。我拿到手之后不会直接运行,而是按下面这个顺序走:
- 打开SQL脚本,先看建库和建表语句,确认数据库版本和字符集
- 用
mvn spring-boot:run或者IDEA直接启动,跑通默认配置 - 启动后先用默认账号登录,走一遍预订-入住-退房流程
- 对照源码里的
application.yml检查数据库连接、端口号、文件上传配置 - 确认一切正常后,再开始改造成自己的项目
很多同学栽在第一步:压缩包里给的是 utf8mb4 字符集,本地MySQL建库时写成了 utf8,结果启动或插入数据时中文乱码。建议建库语句改成 CREATE DATABASE hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;,同时JDBC连接串也要加 characterEncoding=utf8。
4.2 改名、改包名、换主题色:最容易被忽略的查重点
如果你是从参考项目改造的,千万不能只改个页面标题就算完事。答辩和查重第一眼看的就是"你的项目是不是和网上那个一模一样"。
我整理的改造清单供你参考:
- 改项目名:把
hotel-demo改成你自己的项目名 - 改包名:把
com.example.hotel改成com.yourname.hotel - 改数据库名:把
hotel_db改成有辨识度的名字 - 改前端标题、Logo、页脚:搜索项目里的
hotel、HOTEL、酒店名称,全局替换 - 改登录页和主界面主题色:一套配色换颜色,视觉效果立刻不一样
- 删掉源码里原作者相关的注释、README、版权声明
- 替换默认账号的密码,重新初始化admin用户数据
但改名字不是目的,目的是逼自己把项目代码读一遍。如果连里头哪些Controller管什么功能都不清楚,答辩老师一问你代码里某个方法在哪里,你连看都不敢看,那基本就露馅了。所以我更建议先读代码再改代码。
4.3 想让它更像真的毕设:加哪些低成本高价值的扩展
如果基础功能已经跑通,还有余力的话,我推荐加下面这些低成本但高价值的功能,它们在答辩时是天然的亮点:
- 数据可视化:用ECharts画入住率趋势图和房型收入占比,前端加一个统计页,后端写一个聚合查询接口。这个功能不复杂,但视觉冲击力强,我答辩时老师在这页停了很久。
- 会员折扣:给客户表加一个会员等级字段,退房结算时按等级打折。涉及价格计算,属于业务逻辑层面的扩展。
- 订单搜索与分页:把列表页的搜索条件(房间号、客户姓名、订单状态)做细,并用MyBatis-Plus的分页插件实现分页。
- 操作日志:用一个拦截器或AOP记录用户的每次关键操作。这个功能扩展成本低,但讲系统设计时很能说事。
- 定时任务:用
@Scheduled每天自动检查超时未到店的预订单,将其自动取消并释放房间。一个注解加一个方法,就能体现你对业务闭环的思考。
不要一次加太多,选两到三个你觉得能讲明白的就行。功能在精不在多,能把一个扩展功能的业务逻辑讲清楚,比列十个只做了增删改查的功能要强很多。
5. 答辩与演示:怎么把系统讲出亮点
5.1 环境准备与备份
答辩翻车最多的场景是现场连不上数据库,或者项目起不来。我当时的做法是:准备一台演示电脑,把项目环境完全装好,并且保证网络断开也能跑。因为很多课设答辩教室的网络很迷,如果项目依赖了远程数据库或第三方接口,现场一断网就全完了。
具体操作建议:
- 本地装好JDK和MySQL,用本机数据库跑项目
- 数据库连接账号密码写死成
root简单密码,避免现场忘记 - 把项目打包成Jar包,命令行运行
java -jar xxx.jar,避免IDE打开慢、依赖报错的问题 - 准备一份初始化数据脚本,里面包含至少20个房间、10个客户、几条不同状态的预订记录和入住记录
- 提前截图所有的页面和关键接口测试结果,存成PPT备用的备用
如果在答辩现场项目真的出了状况,千万不要慌张,直接从截图开始讲你的设计和实现,然后说"现场环境问题,我本地已经完整跑通了",大多数老师都能理解。
5.2 演示脚本设计:用一条完整业务线串起来
演示不是打开系统乱点一通,而是跟着业务线走。我当时的演示顺序是这样的:
- 登录:管理员账号登录,说明权限设计的区别
- 主页仪表盘:展示房间数量、入住率、今日营收,点出数据统计功能
- 房间管理:展示不同状态的颜色区分(空闲/已入住/待清洁)
- 客户管理:演示新增客户、编辑信息、会员标记
- 预订:给某间房创建一个新预订,强调日期冲突校验
- 办理入住:从预订单转入住,演示押金录入和房间状态变化
- 退房结算:演示费用计算、退款、房间状态转为待清洁
- 清洁:把待清洁房间标记为空闲,完成闭环
这样一条线下来,老师看到了完整的业务流程,而不是零散的功能点。我在演示到第6步时特意把数据库里房间状态的变更一起展示出来,效果很好,让人觉得这个系统是真的在管理数据,而不是在做界面演示。
5.3 答辩高频问题
根据我自己的答辩经历和帮同学模拟的经验,下面这些被问到的概率非常高,提前心里有数:
@Transactional的原理是什么?什么情况下会失效?- 房间状态是怎么维护的?为什么不用一个订单状态代替?
- 密码为什么不能存明文?你用的什么算法加密?
- 如果两个前台同时操作同一间房,会发生什么?怎么解决?
- JWT的token泄漏了怎么办?过期时间怎么设置?
- 数据库表之间是什么关系?为什么预订和入住要分成两张表?
- 如果入住之后客户要续住,你的系统怎么支持?(这个问题我当时没处理好,后来想想其实可以简单把预计离店日期改掉,加上换房记录)
这些问题没有一个特别难,但都要求你对自己写的代码有真实的理解。这也是我反复强调"拿到源码一定要自己读一遍、改一遍"的原因。
6. 关于课程设计文档和答辩PPT的几点体会
6.1 万字文档应该怎么写
标题里说"附万字文档",其实文档不是字数越多越好,而是章节逻辑清晰、图表规范。一份合格的课程设计文档大概包含这些内容:
- 摘要:项目做什么、用了什么技术、完成了什么功能
- 绪论或背景:为什么做酒店管理系统、国内外现状(简要写)
- 需求分析:功能需求、非功能需求、数据流图或用例图
- 系统设计:总体架构、功能模块划分、数据库E-R图和数据字典
- 系统实现:每个模块的核心代码片段和页面截图
- 系统测试:测试用例表、测试结果、边界情况
- 总结:收获、不足、展望
E-R图我推荐用draw.io或ProcessOn画,画完截图放进文档。数据字典表用Word的三线表格式,字段名、类型、是否为空、说明列清楚。核心代码不要全部贴,每个模块贴最关键的方法(比如办理入住那个Service方法)就够了,代码前面用一到两句话说明思路。
6.2 答辩PPT的页面逻辑
答辩PPT一般控制在10到15页,结构我建议是:
- 选题背景与意义(一页)
- 技术选型(一页)
- 系统功能结构图(一页)
- 数据库设计(E-R图加核心表说明,两页)
- 核心业务实现(预订/入住/退房流程加代码,三到四页)
- 系统运行截图(两到三页)
- 遇到的问题与解决方案(一页)
- 总结与展望(一页)
"遇到的问题与解决方案"这页我强烈建议一定要写。不要写那种"遇到了Bug,解决了",而是写具体一点:比如"退房结算时浮点数精度导致金额对不上,后来改用BigDecimal/DECIMAL解决""两个前台并发预订同一房间,新增行锁配合事务解决",这样的内容才是老师真正想看的东西,也是你和其他同学拉开差距的地方。
PPT上字不要多,讲才是重心。讲的时候控制在5到8分钟,语速别太快,重点是让老师看到你对这个系统的掌控感。
最后再分享一个自己的体会:课程设计它不是一门"交差"的任务,它是你第一次有机会把一个想法从一个页面、一张表逐步变成一个完整系统。整个过程里最值得投资的不是你下载了多少代码,而是你把哪一段逻辑真正想明白了——哪怕只是一个预订的并发问题,只要你想通了,答辩时你就有了底气,将来面试聊项目也就有了可以讲的故事。希望这篇东西能帮你少走一点弯路。
