2026/10/24 10:44:13

基于SpringBoot+Vue的酒店客房管理系统:核心业务闭环与并发控制实践

基于SpringBoot+Vue的酒店客房管理系统:核心业务闭环与并发控制实践 简介这是一套面向计算机专业学生与Java全栈初学者、可用于课程设计或毕业设计的酒店客房管理系统完整源码基于Vue.js前端与SpringBoot后端构建搭配MySQL数据库采用前后端分离架构并区分管理后台与用户网页端支持管理员、会员两类角色覆盖会员管理、清洁工管理、客房管理、用户预约与入住等核心业务模块项目编号T099。压缩包共1788个文件约39.36MB以354个Java后端源码、120个Vue组件、306个JavaScript脚本、124个HTML页面为主体辅以SVG图标、GIF与JPG图片、CSS样式及SQL建表脚本、XML配置等资源结构完整、层次清晰。已有92人学习关注。作者同步提供项目录屏、启动教程与讲解视频便于读者快速跑通环境、理解模块划分与业务流转对照源码梳理预约入住流程与权限设计是上手SpringBoot与Vue实战、完成课程作业的实用参考。1. 从一张前台排班表说起这套酒店客房管理系统要解决什么很多做毕设的同学一上来就问我酒店客房管理系统这种题目是不是太烂大街了我的回答通常是——题目老不老不重要重要的是你交上去的东西能不能让答辩老师看到一条完整的业务闭环。我见过太多版本前端页面做得花里胡哨点进去发现入住登记只是往数据库插一条记录房态图是写死的静态图片退房结算连押金扣减的逻辑都没有。这种系统在演示环节撑不过三个追问。这套基于 JAVA Vue.js SpringBoot MySQL 的酒店客房管理系统瞄准的就是这条闭环客人到店、选房开单、入住登记、换房续住、消费挂账、退房结算、房态回写每一步都要在数据库里留下可追溯的状态变化。它适合两类人——正在做毕设或课程设计、需要一个结构完整且能讲清楚设计取舍的学生以及需要快速搭一套中小型酒店前台业务原型、后续再往上叠会员和报表的从业者。技术栈选得很务实SpringBoot 扛后端接口和事务Vue.js 做前后端分离的交互层MySQL 存业务数据JAVA 作为主语言保证生态和调试工具链成熟。这套组合不是追新而是出了问题能查到资料、答辩时能讲明白的稳妥选择。2. 需求与角色建模先把三张核心表和四类用户定死动手写代码之前我最怕看到的一种情况是需求文档写了一堆用户管理、权限管理、日志管理唯独没说清楚谁在什么场景下对哪条数据做什么操作。酒店客房管理系统的业务其实很收敛把角色和核心实体定死后面百分之七十的争议都会消失。2.1 四类角色与他们的操作边界系统里通常划分四类角色每类的权限边界要在设计阶段就写进表里而不是等到写接口时临时判断。角色核心操作数据可见范围前台接待开单、入住登记、换房、退房结算全部房态与在住订单客房服务查看待清扫房间、上报维修仅房态与维修单不看客人证件财务账单查询、押金流水、日结报表订单金额与支付记录不看房态操作系统管理员房型房价维护、员工账号、参数配置全量但操作留痕这张表的价值在于它直接决定了后端接口的鉴权粒度。我一般会在 SpringBoot 里用拦截器加注解的方式做角色校验而不是在每个 Controller 里写 if-else否则后期加角色就是灾难。2.2 三张核心表撑起业务主干客房管理的数据模型不需要一上来就画几十张表先把主干立住房间表、订单表、账单明细表。房间表存房号、房型、楼层、当前状态空闲/在住/待清扫/维修订单表存入住人、入住时间、预计离店、实际离店、押金、订单状态账单明细表存每一笔消费房费、餐饮挂账、赔偿、折扣。三张表通过房间号和订单号关联房态的变化由订单状态驱动而不是人工去改。-- 房间表房态是系统的心跳用枚举约束避免脏数据 CREATE TABLE room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(16) NOT NULL UNIQUE COMMENT 房号, room_type_id BIGINT NOT NULL COMMENT 房型外键, floor INT NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1在住 2待清扫 3维修, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号 ); -- 订单表入住与退房的时间戳是结算和报表的依据 CREATE TABLE room_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, room_id BIGINT NOT NULL, guest_name VARCHAR(32) NOT NULL, check_in_time DATETIME, expect_out_time DATETIME, actual_out_time DATETIME, deposit DECIMAL(10,2) DEFAULT 0, status TINYINT NOT NULL DEFAULT 0 COMMENT 0预订 1在住 2已退 3取消 );这里有个设计说明值得单独讲房间表加了version字段做乐观锁。原因是前台可能两个人同时给同一间房开单如果没有并发控制就会出现一间房两条在住订单的鬼单。用乐观锁在更新房态时校验版本号冲突时让后一个操作失败并提示刷新比数据库行锁更轻也更容易在答辩时讲清楚。2.3 房态流转图要先画在纸上房态不是随便改的它有一条明确的流转路径空闲 → 在住开单入住→ 待清扫退房→ 空闲清扫完成维修状态可以从空闲进入、修好后回到空闲。我建议在需求阶段就把这张流转图手画出来贴在显示器边上因为后面所有涉及房态的接口都要对照它做合法性校验。比如待清扫的房间不允许直接开单必须先走清扫完成在住的房间不允许删除只能走退房流程。这些约束写进 Service 层比事后补 bug 省太多事。3. 总体架构与技术选型前后端分离下的分层与接口约定架构这件事学生最容易犯的错是为了分层而分层Controller 里调 ServiceService 里又塞了一堆 SQL 拼接最后 Mapper 成了摆设。这套系统的分层要落到职责上而不是落到文件夹名字上。3.1 后端分层Controller 只做参数校验和组装SpringBoot 后端按经典四层走Controller、Service、Mapper、Entity。但我要强调一个血泪经验——Controller 里不要写业务判断。它的职责只有三件事接收请求参数、做基础格式校验比如房号非空、日期格式合法、调用 Service 并包装统一返回体。真正的业务规则比如押金不足不能开单房间状态不合法不能入住全部放在 Service 层。RestController RequestMapping(/api/order) public class OrderController { Autowired private OrderService orderService; // 入住登记只做参数接收业务规则交给 Service PostMapping(/checkin) public ResultOrderVO checkIn(RequestBody Valid CheckInDTO dto) { // Valid 触发 DTO 上的注解校验失败由全局异常处理器统一返回 return Result.success(orderService.checkIn(dto)); } }这段代码的关键点在Valid和全局异常处理器。DTO 上用NotBlank、Future这类注解把格式校验前置Service 里只处理业务校验两层职责清晰。答辩时老师问你的参数校验在哪做的你能指着 DTO 和异常处理器说清楚比含糊其辞强得多。3.2 前端结构Vue.js 按业务域拆视图Vue.js 这边我一般按业务域拆目录而不是按文件类型堆在一起。views/room放房态图和房间管理views/order放入住退房views/report放报表api目录统一封装 axios 请求。这样做的直接好处是改一个业务模块时不用在几十个文件里翻找。// api/order.js请求统一走封装便于加 token 和错误提示 import request from /utils/request export function checkIn(data) { return request({ url: /api/order/checkin, method: post, data }) } export function checkOut(orderNo) { return request({ url: /api/order/checkout/${orderNo}, method: post }) }封装 request 的意义在于拦截器请求头统一带登录 token响应统一判断业务码401 跳登录、500 弹提示。这些如果散落在每个页面里写后期改一处要动几十个文件是典型的当时省事、后面还债。3.3 接口约定与统一返回体前后端分离项目最容易扯皮的地方是返回格式不统一。我的做法是定一个ResultT包装类包含 code、message、data 三个字段所有接口无一例外走它。前端拦截器只认这一种结构页面里就不用到处判断这次返回的是数组还是对象。这个约定要在项目第一天就写进文档而不是等联调时发现前端解析不了再回头改。4. 核心模块实现入住、退房与房态联动的落地细节模块实现这一章我挑三个最能体现设计功力的点来讲入住登记的事务边界、退房结算的金额计算、房态联动的并发处理。这三块写扎实了系统的主干就立住了。4.1 入住登记一个事务里完成三件事入住登记表面上是插一条订单实际上要同时完成校验房间状态、更新房间为在住、生成订单记录。这三步必须在一个事务里否则中途失败就会出现房间显示在住但没有订单的黑匣子状态。Service public class OrderServiceImpl implements OrderService { Autowired private RoomMapper roomMapper; Autowired private OrderMapper orderMapper; Override Transactional(rollbackFor Exception.class) public OrderVO checkIn(CheckInDTO dto) { // 1. 查房间并校验状态空闲才允许入住 Room room roomMapper.selectById(dto.getRoomId()); if (room null || room.getStatus() ! 0) { throw new BizException(房间当前不可入住); } // 2. 乐观锁更新房态version 不匹配说明被人抢先 int updated roomMapper.updateStatusWithVersion(room.getId(), 1, room.getVersion()); if (updated 0) { throw new BizException(房态已变化请刷新后重试); } // 3. 生成订单押金默认取房型标准 RoomOrder order buildOrder(dto, room); orderMapper.insert(order); return toVO(order); } }Transactional(rollbackFor Exception.class)这个参数不能省默认只回滚运行时异常业务里抛的受检异常如果不指定就不会回滚这是很多人踩过的坑。乐观锁那一步的updated 0判断是并发安全的最后一道闸冲突时给用户明确提示而不是让两条订单都写进去。4.2 退房结算金额计算要能对得上账退房结算是整个系统里最容易被老师追问的地方。核心公式是应收 房费 挂账消费 - 押金抵扣房费按实际入住天数乘房价超时离店按半天或全天加收。我一般把计算逻辑单独抽一个BillingCalculator类输入订单和消费明细输出账单对象这样单元测试好写答辩时也能单独拿出来讲。public BigDecimal calculate(RoomOrder order, ListConsumeItem items) { // 房费按实际天数向上取整超时规则由配置决定 long days ChronoUnit.DAYS.between( order.getCheckInTime().toLocalDate(), order.getActualOutTime().toLocalDate()); days Math.max(days, 1); BigDecimal roomFee order.getRoomPrice().multiply(BigDecimal.valueOf(days)); // 挂账消费累加 BigDecimal consume items.stream() .map(ConsumeItem::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); // 押金抵扣不足部分需补缴 return roomFee.add(consume).subtract(order.getDeposit()); }金额一律用BigDecimal不要用 double浮点误差在财务场景是硬伤。天数计算用ChronoUnit.DAYS而不是自己减时间戳避免时区和夏令时带来的玄学问题。退房成功后房态要联动改为待清扫这一步同样放在退房事务里保证订单状态和房态一致。4.3 房态联动状态变更集中到一个入口房态被多处修改——入住改在住、退房改待清扫、清扫完成改空闲、维修改维修。如果每个业务方法里各写各的 update迟早会出现某处漏改导致状态错乱。我的做法是抽一个RoomStatusService所有房态变更必须走它内部统一做合法性校验和乐观锁更新。这样房态流转规则只有一份实现改规则时只动一个地方。5. 避坑与排查这套系统里最容易翻车的五个点做这类系统功能写出来只是及格线能不能稳定演示、能不能经得起追问取决于你有没有提前踩过这些坑。下面五条是我带项目时反复见到的翻车现场按现象 → 原因 → 解决整理。第一条一间房出现两条在住订单。现象是房态图显示在住但订单列表里同一房间有两条未退记录。原因是入住接口没有并发控制两个请求同时通过了状态校验。解决办法就是前面说的乐观锁加版本号更新失败即回滚并在前端提示刷新。第二条退房后房态没变房间一直显示在住。现象是订单已退房态图却还是红色。原因多半是退房逻辑里更新订单和更新房态不在同一个事务或者房态更新那行代码被异常吞掉了。排查时先看日志里事务是否回滚再确认RoomStatusService是否被正确调用。第三条金额算出来差几分钱。现象是账单总额和手工计算对不上。原因通常是用了 double 做金额运算或者天数计算用了时间戳相减导致跨天判断错误。统一换BigDecimal天数用日期差而不是毫秒差基本能根治。第四条前端刷新后登录态丢失。现象是页面一刷新就跳回登录页。原因是 token 只存在组件内存里没落到 localStorage 或 sessionStorage。在 request 拦截器里统一从存储读取 token登录成功时写入退出时清除这个问题就消失了。第五条日期格式前后端对不上。现象是提交入住时间时报 400。原因是前端传的是yyyy-MM-dd字符串后端 DTO 里是LocalDateTime反序列化失败。在 DTO 字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)或者全局配置日期格式二选一即可别两边各写一套。提示这五条建议在开发阶段就写成自测清单每完成一个模块对照过一遍比等到演示前夜才发现问题从容得多。6. 验收与答辩用一条完整业务链证明系统能跑通验收这件事我的观点很直接不要用功能列表打勾的方式证明系统做完了要用一条完整的业务链走一遍给老师看。具体做法是准备一个固定的演示脚本新建一间空闲房 → 前台开单入住并收押金 → 中途挂一笔餐饮消费 → 换房一次 → 退房结算并核对金额 → 房态变为待清扫 → 清扫完成回到空闲。这条链走完房态、订单、账单三张表的数据变化都能对上系统的完整性就不需要额外解释了。答辩时老师最爱问的几个问题提前准备好答案房态并发怎么处理的乐观锁加事务、金额精度怎么保证的BigDecimal 加统一计算类、权限怎么控制的拦截器加角色表、如果加会员积分你改哪里Service 层扩展不动 Controller。回答时尽量落到具体类名和字段上比泛泛而谈用了 SpringBoot 框架有说服力得多。验收标准我一般列成一张表交付前逐项核对验收项通过标准入住闭环开单后房态变在住订单可查押金入账退房结算金额与手工计算一致房态转待清扫并发安全同一房间并发开单只有一条成功权限隔离客房服务角色无法访问账单接口数据一致性订单状态与房态始终匹配无孤儿数据最后说个我自己的习惯。早些年我做这类系统总想着先把页面堆出来结果联调时发现接口字段对不上、事务边界没想清楚返工返到怀疑人生。从那以后我每次开新项目都强制先把三张核心表和房态流转图定死再写第一个接口宁可前期慢两天也不让后期还债。这套酒店客房管理系统的结构就是按这个顺序搭起来的你拿去改房型、加会员、接支付主干都不用动。希望帮到你。本文还有配套的精品资源点击获取