酒店管理系统课程设计:从数据库建模到答辩演示的完整指南
简介面向计算机专业学生的酒店管理信息系统课程设计与毕业设计资料包基于C与MFC开发覆盖数据库系统综合实训环节。系统围绕酒店日常业务实现房间状态查询、入住登记、住客信息管理、退房结算、房间预订及系统用户权限维护等核心功能适合参考其功能模块划分、界面设计与编码实现。压缩包共52个文件代码部分以17个h头文件和16个cpp源文件为主涵盖登录、订房、退房、查房等对话框与文档视图模块另含软件工程报告书doc文档、ReadMe说明及项目工程配置可辅助理解从需求分析到编码测试的整体流程。资源整体约10.63MB已有150人浏览学习。通过学习可快速掌握MFC单文档程序结构、数据库操作与酒店业务场景的联动开发思路适合作为课程设计或毕业设计的直接参考。1. 酒店管理系统课程设计先弄懂它在答辩现场的真实分量如果你正在为课程设计或毕业设计选题发愁酒店管理系统是每年出现频率最高的题目之一。这个题目看起来平平无奇但能拿高分的版本和只能勉强及格的四件套增删改查之间差距往往不在界面多华丽而在订单状态模型和房态数据一致性这两块硬骨头上。很多人在答辩时被老师追问“并发下同一间房会不会被订两次”“退房后房间何时变为可售”就直接卡壳。这份资源正是围绕酒店管理系统的完整设计与实现展开的覆盖从需求分析、数据库建模到前后端联调、答辩演示的完整链路。适合正在做课程设计的学生、需要快速搭出一个可演示系统的开发者以及想把“酒店管理”这个经典选题做出差异化的毕业生。它给你的不是把页面堆出来就完事而是一套能讲清楚“为什么这么设计”的落地工程。2. 技术选型与框架搭建课程设计用哪套组合最稳边界在哪2.1 技术栈对比不同场景下的选型逻辑酒店管理系统作为一个典型的管理信息系统可选的技术栈组合非常多。我在拆解这份资源时首先看到的是它对主流方案的兼容度。常见做法是分成三条路线纯 Java 系、Python 系和 .NET 系。这里给出一份实际的对比方便你判断哪条路线最适合自己的答辩环境。技术栈组合开发效率答辩加分点部署成本常见院校要求JSP Servlet JDBC低纯手动拼装几乎没有需要 Tomcat课程设计、大一/大二Spring Boot MyBatis Vue高前后端分离工程化、规范化打包 jar 即可运行毕业设计、课程设计SSMSpring SpringMVC MyBatis中配置繁琐适合讲“框架原理”需要 Tomcat 配置课程设计Django DRF Vue高自带 Admin数据处理直观相对简单偏 Python 方向的毕业设计ASP.NET Core EF Core高强类型Windows 环境调试方便需要 .NET SDK偏微软技术栈院校这套资源的核心代码以 Spring Boot 为主线同时保留了 JSP 版本的兼容写法。如果你所在院校要求必须用 JSP 做展示层不要慌资源里对应的 Controller 层代码可以直接复用只把视图解析部分从模板引擎切换到 JSP 即可。我一般会建议优先选择 Spring Boot MyBatis 组合原因是 MyBatis 的 SQL 完全由自己掌控答辩时老师问“这条查询能不能用索引优化”你能直接贴出 XML 里的 SQL 来讲解远比 JPA 那种自动生成的查询更有说服力。如果想要管理端界面省事可以在 Spring Boot 基础上接入若依框架但要注意若依自带的代码生成器会让代码风格和手写部分明显不一致。2.2 项目骨架初始化与依赖配置拿到资源包后第一步不是急着读代码而是先把项目跑起来。资源包中通常会包含一个完整的 Maven 工程目录核心依赖在 pom.xml 里已经配好。这里给出一份典型配置标注了每个依赖的用途和版本选择的理由。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent properties java.version1.8/java.version mybatis-plus.version3.5.3/mybatis-plus.version mysql.version8.0.33/mysql.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version${mysql.version}/version scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这段配置里有几个参数值得单独说明。Spring Boot 版本选 2.7.18 而不是 3.x是因为 3.x 要求 JDK 17而绝大多数课程设计环境还在用 JDK 8改版本会导致 javassist、mybatis 等中间件报兼容性异常。MyBatis-Plus 3.5.3 相比原生 MyBatis 的好处是自带分页插件和 ActiveRecord 风格操作对于订单列表这种需要分页展示的场景只需要调用 selectPage 方法。MySQL 驱动必须用 8.0.33配合连接串里的 useSSLfalse 和 serverTimezoneAsia/Shanghai 参数才能避免出现时区错误和 SSL 警告。注意如果你的 MySQL 是 5.7驱动版本可以降回 5.1.49但连接串里仍然要写时区参数否则日期查询会默认取 UTC 时区比北京时间慢八小时。2.3 配置文件里的隐藏参数端口、时区、日期格式resources 目录下的 application.yml 是启动的关键。资源包默认的配置如下这里逐项解释为什么这么写server: port: 8080 servlet: context-path: / spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0context-path 保持根路径是为了让前端页面中的相对路径不产生歧义。characterEncodingutf8 必须写成 utf8 而不是 utf8mb4虽然 MySQL 8.0 默认是 utf8mb4但某些老版本 JDBC 驱动会在识别字符集时直接报错。r如出现 allowPublicKeyRetrievaltrue 是因为 MySQL 8.0 的 caching_sha2_password 认证机制在第一次连接时要拉取公钥不加这个参数会报 Public Key Retrieval is not allowed。jackson 的 date-format 决定了后端返回 JSON 时日期字段的序列化方式。酒店管理系统的订单查询、报表统计都涉及时间如果不统一设置为 yyyy-MM-dd HH:mm:ss前端拿到的时间格式会是带 T 的 ISO 8601 格式例如 2024-06-01T12:00:00需要额外做字符串转换很多新手会在这一步翻车。逻辑删除配置logic-delete-field是为了让订单取消和客户删除操作不真正删数据行这样统计报表中还能保留历史订单的原始记录。答辩时老师问“用户退单了数据怎么办”你就可以直接说逻辑删除并指出数据库中 deleted 字段的变化。3. 数据库与核心表设计房态、订单、账务三张主表如何撑起整个系统3.1 表结构拆解为什么房间表要单独关联房型酒店管理系统最容易踩的坑就是把所有字段堆在一张表里。比如直接在 room 表放 room_price、room_type_name、bed_count看起来简单但后续改一间房的价格要更新多行数据查房价时还要带着房间状态判断。这套资源里的表结构遵循了三范式拆分核心是四张互相关联的表。room房间表、room_type房型表、customer客户表、orders订单主表。其中订单主表是关键也是容易做乱的地方。先看房型表和房间表的建表语句CREATE TABLE room_type ( id INT PRIMARY KEY AUTO_INCREMENT, type_name VARCHAR(50) NOT NULL UNIQUE COMMENT 房型名称如豪华大床房, bed_count TINYINT NOT NULL DEFAULT 1 COMMENT 床位数, base_price DECIMAL(10,2) NOT NULL COMMENT 挂牌价门市价, area FLOAT COMMENT 房间面积平方米, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房型表; CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, room_number VARCHAR(10) NOT NULL UNIQUE COMMENT 门牌号如 1201, room_type_id INT NOT NULL COMMENT 关联房型表, floor TINYINT COMMENT 所在楼层用于按楼层查房, status TINYINT NOT NULL DEFAULT 0 COMMENT 房间状态0-可售 1-入住 2-打扫 3-维修, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除标记, KEY idx_room_type_id (room_type_id), KEY idx_room_status (room_status), CONSTRAINT fk_room_type FOREIGN KEY (room_type_id) REFERENCES room_type(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT物理房间表;房型表存的是固定属性房间表只存位置和当前状态。这样拆带来的直接好处是修改房价时只需更新 room_type 的 base_price所有同房型房间同步生效。room 表中的 status 是频繁变更字段每次入住、退房、打扫都会更新它所以必须单独建索引否则后续写“按状态统计房间数”的报表时全表扫描会非常慢。deleted 字段配合前面 MyBatis-Plus 的逻辑删除配置在代码里调用 removeById 时实际执行的是 UPDATE room SET deleted1 WHERE id?查询时所有普通查询自动追加 and deleted0。这个字段放在每张业务表中即可不需要额外建索引因为逻辑删除的数据占比很小。提示房间号直接用字符串类型存不要拆成 “楼栋房号” 两列。课程设计阶段没有那么多楼栋维度拆开反而让查询条件复杂化。3.2 订单表是系统的心脏字段粒度与状态机设计订单主表是整个系统中最容易设计过度的表。很多课程设计代码喜欢把订单拆成“预订单”“入住单”“结算单”三张表实际开发中这会产生大量 join 查询。这份资源采用单表状态机方案一张 orders 表通过 status 字段驱动流程。CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单编号规则见下方说明, customer_id INT NOT NULL COMMENT 客户ID关联customer表, room_id INT NOT NULL COMMENT 实际入住房间ID, room_type_id INT NOT NULL COMMENT 预定时锁定的房型ID, check_in_date DATE NOT NULL COMMENT 计划入住日期, check_out_date DATE NOT NULL COMMENT 计划退房日期, actual_check_in_time DATETIME COMMENT 实际办理入住的时间, actual_check_out_time DATETIME COMMENT 实际退房结账时间, night_count TINYINT NOT NULL COMMENT 入住晚数由日期计算得出, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总额含加收费用, deposit DECIMAL(10,2) DEFAULT 0 COMMENT 预授权或押金金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0-待支付 1-已支付/已预订 2-已入住 3-已退房 4-已取消 5-已结算, remark VARCHAR(255) COMMENT 订单备注如加床、无烟层, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_room_id_status_date (room_id, status, check_in_date), KEY idx_customer_id (customer_id), CONSTRAINT fk_order_room FOREIGN KEY (room_id) REFERENCES room(id), CONSTRAINT fk_order_customer FOREIGN KEY (customer_id) REFERENCES customer(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;order_no 的生成规则建议用日期加随机数yyyyMMddHHmmss 4位随机数比如 202406151430257861。不要在数据库里设置自增 id 为订单号外露自增 id 会给答辩老师一种“你不太专业”的印象更重要的是若做分布式部署时自增 id 会冲突。date 类型的 check_in_date 和 check_out_date 与 datetime 类型的 actual_check_in_time 分开设计是有讲究的。前者表示客人预订时选择的时间范围后者表示前台实际办理入住或退房的时间点。如果用一个字段存退房延迟后的时间计算会出现歧义报表统计“平均入住时长”也没法通过简单的 timestampdiff 函数计算。状态字段的流转逻辑是答辩时的核心考点。订单从待支付到已预订到已入住再到已退房和已结算其中已退房和已结算之间还涉及押金退回、加收费用生成等动作后面会展开说明。这里需要明确的是status 值千万不要用字符串类型用数字枚举配合代码里的常量类或枚举类这样后续在做 SQL 统计时执行 group by status 效率更高也不容易出现大小写不一致导致的脏数据。3.3 房态实时联动的核心通过订单状态反推房间状态这张表设计完成后系统的一个核心逻辑就是订单状态变化与房间状态变化的联动机制。普遍会犯的低级错误是客人退订后只改了订单状态忘了把房间状态从 1入住改回 0可售导致前台看到房间一直是红色不可售。实现正确做法是房间状态不直接由某一个订单操作来设置而是由数据库聚合查询得出。核心 SQL 逻辑如下UPDATE room r SET r.status 1 WHERE r.id #{roomId} AND r.status 0 AND NOT EXISTS ( SELECT 1 FROM orders o WHERE o.room_id r.id AND o.status IN (1, 2) AND o.check_in_date #{checkOutDate} AND o.check_out_date #{checkInDate} );这段 SQL 的意思是只有当房间当前状态为 0可售且没有与其他订单的入住日期范围重叠时才能将房间置为入住状态。注意后半段的日期重叠判断条件o.check_in_date #{checkOutDate} 且 o.check_out_date #{checkInDate}这是区间相交的标准判断。很多人会写成 check_in_date 新订单开始时间这种错误条件漏掉跨天重叠的情况。实际代码中我一般用 service 方法封装这个逻辑先查询 room 当前状态再以行锁方式更新配合事务实现可靠性。在单品课程设计中用这条 SQL 就能通过“并发防止超卖”的考察点。它用 NOT EXISTS 子查询兜底即使两个请求同时进来数据库的行锁和事务隔离级别也能保证只更新成功一次。4. 核心业务链路预订、入住、退房结算的代码责任划分4.1 预订流程创建订单时的校验细节预订操作的前后端交互是这份资源的重点演示部分。前端提交的数据是 roomTypeId、checkInDate、checkOutDate、customerId后端要做两件事一是校验日期合法性二是计算 totalAmount。下面这段 Spring Boot Service 代码是从资源中提炼出的核心逻辑Override Transactional(rollbackFor Exception.class) public OrderVO createOrder(OrderCreateDTO dto) { // 1. 校验入住日期不能早于今天 LocalDate today LocalDate.now(); if (dto.getCheckInDate().isBefore(today)) { throw new BizException(入住日期不能早于今天); } // 2. 校验离店日期必须晚于入住日期 if (!dto.getCheckOutDate().isAfter(dto.getCheckInDate())) { throw new BizException(离店日期必须晚于入住日期); } // 3. 查询房型和可用的房间 RoomType type roomTypeMapper.selectById(dto.getRoomTypeId()); Assert.notNull(type, 房型不存在); ListRoom availableRooms roomMapper.selectAvailableRooms( dto.getRoomTypeId(), dto.getCheckInDate(), dto.getCheckOutDate()); if (availableRooms.isEmpty()) { throw new BizException(该日期范围内没有可用房间); } Room targetRoom availableRooms.get(0); // 4. 计算晚数和金额 long nights ChronoUnit.DAYS.between(dto.getCheckInDate(), dto.getCheckOutDate()); BigDecimal totalAmount type.getBasePrice().multiply(BigDecimal.valueOf(nights)); // 5. 构建订单实体并插入 Orders order new Orders(); order.setOrderNo(OrderNoGenerator.generate()); order.setCustomerId(dto.getCustomerId()); order.setRoomId(targetRoom.getId()); order.setRoomTypeId(type.getId()); order.setCheckInDate(dto.getCheckInDate()); order.setCheckOutDate(dto.getCheckOutDate()); order.setNights((int) nights); order.setTotalAmount(totalAmount); order.setStatus(OrderStatus.PAID.getValue()); orderMapper.insert(order); return OrderConverter.toVO(order); }这段代码每个步骤都有明确的检查点。第一步和第二步是基本的时间约束注意这里对日期的比较用的是 isBefore 和 isAfter而不是比较字符串否则会因为格式问题出现时间边界错误。第三步的 selectAvailableRooms 是关键它调用的是前面那条 NOT EXISTS 的 SQL 的查询版本返回未被占用的房间列表。这里取第一个可用房间如果想要更精细可以加“优先安排低楼层房间”的排序逻辑。第四步计算金额用的 ChronoUnit.DAYS.between 方法会自动处理跨月、跨年的天数差异不需要自己写日期差计算逻辑。金额计算使用 BigDecimal 而不是 double是防止出现 0.10.2 精度问题这点在答辩时也可以作为细节亮点提出。注意Transactional 注解必须有 rollbackFor Exception.class 参数。如果只写 TransactionalSpring 默认只在遇到 RuntimeException 时才回滚而自定义的 BizException 如果继承自 Exception那么校验失败时事务不会回滚会出现“订单创建失败但数据插进去了”的严重事故。4.2 入住与退房状态流转中要锁定的关键字段退房结算的代码比预订更复杂因为它涉及两个部分更新订单状态、更新房间状态。同时还要计算可能的超时费或者额外消费。退房的核心代码如下Override Transactional(rollbackFor Exception.class) public void checkout(Long orderId) { Orders order orderMapper.selectByIdForUpdate(orderId); if (order null) { throw new BizException(订单不存在); } if (order.getStatus() ! OrderStatus.CHECKED_IN.getValue()) { throw new BizException(当前订单状态不可退房); } // 1. 计算超时费超过中午12点退房加收半天房费 BigDecimal extraFee BigDecimal.ZERO; LocalDateTime now LocalDateTime.now(); LocalDateTime noonLimit now.toLocalDate().atTime(12, 0); if (now.isAfter(noonLimit)) { RoomType type roomTypeMapper.selectById(order.getRoomTypeId()); extraFee type.getBasePrice().divide(BigDecimal.valueOf(2)); } // 2. 更新订单状态 order.setActualCheckOutTime(now); order.setStatus(OrderStatus.CHECKED_OUT.getValue()); order.setTotalAmount(order.getTotalAmount().add(extraFee)); orderMapper.updateById(order); // 3. 释放房间状态回到可售 roomMapper.updateRoomStatus(order.getRoomId(), RoomStatus.AVAILABLE.getValue()); }selectByIdForUpdate 是对订单行加悲观锁防止两个操作员同时对同一单操作导致重复结算。这在你做并发演示时非常关键不加这行的话连点两次退房按钮就能把订单金额翻倍。超时费的政策各酒店不同这里用的是“超过中午12点退房加收半天房费”的简单规则。实际做课程设计时建议把规则写进一个常量配置类并在答辩 PPT 里说明“这里的规则做了简化真实系统中应该做成可配置项”。这既展示了你懂扩展性又避免了因实现复杂带来的代码冗长。房间状态释放放在了订单更新之后。顺序不能反过来否则第二步报错时房间已经释放但订单还是入住状态就会产生“客人还在里面但房间已经显示可售”的脏状态。由于有事务保护任一步骤失败都会回滚全部更新这也正是事务在核心业务链路中的价值体现。4.3 前端调用Ajax 交互与状态刷新这套资源的前端页面采用了 Vue 2 Element UI 的管理端布局和后端接口通过 axios 交互。预订表单提交的核心 JavaScript 代码如下submitOrder() { if (!this.checkDates()) { this.$message.error(日期选择不合法离店日期必须晚于入住日期); return; } if (!this.selectedRoomTypeId) { this.$message.error(请先选择房型); return; } const submitData { roomTypeId: this.selectedRoomTypeId, checkInDate: this.formatDate(this.checkInDate), checkOutDate: this.formatDate(this.checkOutDate), customerId: this.selectedCustomerId }; this.loading true; axios.post(/api/order/create, submitData).then(res { if (res.data.code 200) { this.$message.success(订单创建成功订单号 res.data.data.orderNo); this.refreshRoomList(); // 刷新房间网格状态 this.dialogVisible false; } else { this.$message.error(res.data.msg); } }).finally(() { this.loading false; }); }前端的关键细节是 this.refreshRoomList() 必须在订单创建成功后立即调用。很多课程设计项目中房间列表的状态要手动刷新才变化演示时容易被老师质疑系统实时性。加载状态用 loading 变量控制防止用户在提交过程中重复点击导致产生多张相同订单这是前端层面的防重保护。后端接口返回统一封装为 { code, msg, data } 格式具体实现在资源中的 Result 类里。如果你抽到的答辩机器上网络带宽有限这种轻量 JSON 结构比 JSP 直接渲染整个 HTML 页面加载更快演示时会顺畅很多。前端路由要配合后端的 RestController 映射路径如果出现 404 或 405优先检查前端请求路径是否和后端 PostMapping(/api/order/create) 的注解路径完全一致。这里强烈建议在浏览器 F12 的 Network 面板里查看请求 URL而不是用眼睛对比代码。5. 常见问题排查酒店管理系统开发中的八个典型踩坑记录5.1 房间重复预订乐观锁 vs 悲观锁的选择现象连续快速点击“提交订单”按钮生成了两条订单对应的同一房间在同一时间段内被占用。原因前端虽然做了 loading 拦截但请求到达后端时如果两个请求分别在不同的线程中先后通过了 selectAvailableRooms 查询两个线程都发现房间可用然后各自 insert。查询和插入之间存在时间窗口这个窗口就是并发安全漏洞。解决在订单表增加乐观锁版本号字段 version更新时强制带上 where version 条件。通知前端在表单提交失败时刷新页面让客户看到房间已满的提示。更彻底的做法是把选房和插入放进一个带悲观锁的事务方法在共享资源上使用行锁。课程设计做并发演示建议用悲观锁因为效果直观源码中已通过 selectByIdForUpdate 及事务实现了行级锁但需要注意 selectByIdForUpdate 必须是在事务内执行才生效。5.2 日期跨天重叠导致房态判断失误现象预订 6 月 1 日到 6 月 3 日的订单和预订 6 月 3 日到 6 月 5 日的订单两单被系统判断为不冲突。原因SQL 判断用的是 check_in_date 新退房日期 或 check_out_date 新入住日期 这类边界判断没有用区间相交逻辑。6 月 3 日当天第一单还未退房第二单已经办理入住同一天两个订单占用同一房间。解决日期重叠判断必须统一使用“区间互斥”条件查询条件是 o.check_in_date #{newCheckOutDate} AND o.check_out_date #{newCheckInDate}。注意这里不能用 和 当退房日期等于入住日期时允许连续住客不然系统会拒绝当天退房当天入住的合法订单。5.3 MyBatis-Plus 逻辑删除配置不生效现象调用 selectList 查询房间时已删除的房间仍然出现在列表中。原因全局配置中只配置了 logic-delete-field但实体类的 TableLogic 没有加或者字段名不一致导致 MyBatis-Plus 无法识别哪个字段是逻辑删除标记。解决在实体 Room 的 deleted 字段上加 TableLogic 注解同时保证全局配置的 logic-delete-field 值为 deleted。如果实体类中用的是 isDeleted 字段名配置和注解都要改成对应字段三处必须完全一致。TableLogic TableField(deleted) private Integer deleted;5.4 前后端日期格式不一致导致查询结果偏一天现象前台选择入住日期 2025-06-01后端收到的日期变成了 2025-05-31。原因JSON 序列化时使用了 Jackson 默认格式浏览器发送“2025-06-01”时被按照 UTC 时区解释而本地时区是东八区日期倒退了 8 小时跨过午夜就减了一天。解决在 application.yml 中配置 spring.jackson.time-zone: GMT8 并且 date-format: yyyy-MM-dd。如果前端用的是 Element UI 的 date-picker确保 value-format 属性设为 yyyy-MM-dd。这一步配好日期边界问题直接消失不要在 controller 里手工做时区加减那只会让代码更混乱。5.5 金额字段使用 Double 导致结算误差现象两间房间各住三晚房价 0.1 元测试数据总额计算得到 0.6000000000000001。原因double 类型是浮点数无法精确表达十进制小数连续乘加操作会累积精度误差。虽然 0.10.2 看起来是常识但计算机底层用二进制表示小数确实做不到精确。解决数据库使用 DECIMAL(10,2)Java 代码中使用 BigDecimal并且乘除法必须传入 MathContext.DECIMAL64 指定精度。前端展示时使用 this.totalAmount.toFixed(2) 保留两位小数。改为 BigDecimal 后金额字段的所有运算都集中在 MoneyUtils 工具类中避免散落的四舍五入逻辑。5.6 启动报 “Table ‘xxx’ doesn’t exist”但明明导入过 SQL 文件现象Spring Boot 项目启动成功但首次访问订单列表时报错提示 orders 表不存在。原因项目配置的是 hotel_db 数据库但导入 SQL 文件时实际操作的是默认连接的 test 数据库。也可能是因为 MySQL 8.0 默认建的库是 utf8mb4 字符集而建表语句中部分字段回退到了 utf8。解决启动前确认 application.yml 的数据库名与 Navicat 中实际导入的库名一致切换数据库执行 USE hotel_db; 再导入 SQL 文件。导入完成后执行 SHOW TABLES; 查看是否包含 orders、room、room_type 等表。如果你改了数据库名所有表名前都不需要加库名前缀连接串已经指定了默认库。提示第一次跑项目不要直接用生产级数据造一组与演示脚本匹配的示例数据即可。房间编号按楼层分配比如 101、202、305这样演示时按楼层筛选的效果更直观。6. 答辩前必做的验证清单与演示脚本把系统讲出工程深度课程设计与毕业设计答辩的评判标准往往更关心“你会不会发现问题并且知道边界在哪里”而不是系统的商业完整度。我自己在做这类项目评审时通常用三张表看一个人的工程能力功能闭环是否完整、状态流转是否严格、异常路径是否有兜底。下面这份验证清单是每次本地部署后必须走完的测试用例集。验证场景操作步骤预期结果关键检查点创建订单-正常路径选房型、选日期、填客户订单状态变为已预订房间状态变为已锁订单号和日期正确金额 晚数 × 房价创建订单-无房选日期范围覆盖所有房间提示“该日期范围内没有可用房间”前端不崩溃SQL 查询返回空集合入住登记对已预订订单点击入住订单状态变为已入住房间状态变为已入住实际入住时间写入系统退房结算模拟入住后次日退房订单状态变为已退房房间状态变为可售总金额计算正确超时费规则生效取消订单对已预订订单点击取消订单状态变为已取消房间立即释放房间可被后续预订查询到逻辑删除删除一条客户记录列表不再查询到该客户数据库中 deleted 字段变为 1并发订房同时打开两个浏览器抢最后一间房只有一个订单成功验证是否需要悲观锁兜底演示脚本我建议按“三段式”来组织时间控制在八分钟以内。第一段展示首页的房态图直接点几个不同状态的房间说出每个颜色对应的状态。第二段走一遍完整流程创建订单 → 订单列表确认 → 入住 → 退房 → 查看房态图的变化。第三段打开数据库客户端现场做一条 SQL 查询对应刚才展示的订单记录证明前端展示的后台数据是真实存在的。这个流程看起来简单但能有效覆盖老师的三个高频问题订单状态字段存了什么值、房间状态怎么变化、数据存在哪个表里。应答时只要把字段值和状态机对应关系说清楚比背十页项目介绍都有效。最后再补充一个特别实用的习惯。从那以后我每次提交课程设计代码前都会强制自己在干净的 MySQL 环境下重新导入一次 SQL 文件再从零启动项目跑完整个演示流程。这一个习惯在答辩前帮我消除过至少三类低级事故SQL 文件缺少创建数据库语句、本地 MySQL 与提交文档中描述的环境版本不一致、演示数据不完整导致页面列表空白。这些坑单独看都不大但答辩现场遇到任何一个都会打乱节奏。希望这篇笔记能帮你把酒店的每一间房、每一笔订单都设计得清清楚楚也从这份资源里拿到真正能落地的东西祝你的课程设计顺利过关。本文还有配套的精品资源点击获取