简介面向Java毕业设计及课程设计场景这套基于SSM框架与JSP技术的酒店客房预定管理系统项目包完整覆盖管理员与用户两类角色管理员可处理用户管理、客房类型与房间信息维护、客房预定、开房记录、退房结账以及系统公告发布用户端则包含客房浏览、在线预定、个人中心和后台管理入口。项目包共包含1253个文件压缩后约18.15MB以Java源码、JSP页面、JS脚本、CSS样式等为核心代码其中Java与JSP负责业务逻辑和页面展示JS和CSS用于前端交互与样式美化另配SQL数据库脚本、XML配置、LW文档和PPT答辩材料目录结构清晰方便按模块查阅和二次开发。目前已有208人学习下载适合需要从零搭建完整SSM项目、完成毕业设计或课程设计答辩的开发者能有效节省环境配置、权限划分和业务逻辑梳理的时间。1. 酒店客房预定管理系统 ssmjsp先搞清楚这个 zip 能帮你解决什么一套酒店客房预定管理系统ssmjsp 完整源码的压缩包加上说明文档、LW 和答辩 PPT是每届毕业设计里流通量最大的项目形态之一。它要解决的问题很直接让你在有限时间内拥有一套能跑、能讲、能应对追问的 Java Web 系统而不是从零搭框架、从零踩 Servlet 的坑。适合三类人还没定题、想找个稳妥方向的本科生定题后对 SSM 整合只有零散概念、需要照着源码理解三层架构的初学者以及接手别人代码、想在短期内改造出自己功能点的开发者。这套技术栈不新甚至偏老但正因如此它的排错路径全部是公开的你能把精力花在业务理解和答辩准备上而不是跟框架机制搏斗。2. 看代码前先看骨架ssm 整合方式与包结构对照2.1 为什么这类毕设选 ssm 而不是 Spring Boot很多人的第一反应是都什么年代了怎么还有项目用 SSM 加 JSP如果只是想把页面跑起来Spring Boot 加 Thymeleaf 确实更快。但毕业设计这个场景里SSM 有自己的优势Spring、Spring MVC、MyBatis 三者由三个独立的配置文件手动装配依赖关系是显式的你在答辩时能指着 XML 说清楚“这个 Bean 为什么在这里、这个扫描器扫到了什么”。Spring Boot 的自动装配一旦出了问题排查起来就像一个黑匣子对新手反而不友好。另一个实际原因是课程体系。很多学校的 Java Web 课程仍然以 Servlet、JSP 和 SSM 为主线毕设选题库里“基于 SSM 的某某管理系统”依然是主流。配套的说明文档和 LW 通常也是按 SSM 三层架构来写的你拿到源码后改造起来和论文结构能对上。迁移到 Spring Boot 意味着重写配置文件、重做页面渲染方案两周内不一定划算。所以我的建议是如果目标是顺利毕业并且能讲清原理保留 SSM 和 JSP 的原始骨架比换技术栈更聪明。2.2 解压后按 controller/service/mapper/entity 对号入座打开压缩包后不要急着启动先花十分钟把目录结构读一遍。一个标准的 SSM 酒店客房预定管理系统源码部分逃不出这张表目录 / 包名职责你大概什么时候会动它controller接收页面请求、组装参数、跳转视图加新功能页、改请求路径service / service.impl业务规则预定、取消、入住、退房、价格计算核心改动基本都在这层mapper / daoMyBatis 接口定义方法签名增删改查方法扩展resources/mapper存放 SQL 的 XML 文件改业务 SQL、调联表查询entity / pojo / model对应数据库表的实体类表结构变化时同步webapp/jspJSP 页面与静态资源改页面展示、加表单resourcesspring 配置、jdbc.properties、log4j 等改连接、改事务、改扫描路径读目录时重点看两个地方一是 entity 里有没有和酒店业务对应的实体比如 Room、Order、User二是 resources/mapper 里有没有一个以上的 SQL 文件。如果 SQL 文件存在但接口方法找不到对应 XML后面大概率会出现 MyBatis 绑定异常这个坑我在第五章会专门展开。目录熟悉之后再去读配置文件顺序感会强很多。2.3 启动三件套spring-mvc.xml、spring-mybatis.xml、web.xml 各管什么SSM 项目里最容易让人迷糊的就是“为什么有这么多 XML”。其实按职责划分非常简单web.xml 是 Web 容器Tomcat读的入口它声明了 DispatcherServlet、Spring 监听器和字符编码过滤器spring-mvc.xml 只管 Controller 层开启注解驱动、配置视图解析器spring-mybatis.xml 管数据源、SqlSessionFactory、事务和 Mapper 扫描。三者由 web.xml 中的 contextConfigLocation 串起来加载顺序错了项目就起不来。下面这段是 spring-mybatis.xml 里最核心的配置几乎所有酒店预定系统的源码都会有类似片段bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ !-- 实体类别名包之后 mapper.xml 里 resultType 可以写 Room 而不写全限定名 -- property nametypeAliasesPackage valuecom.hotel.entity/ !-- 扫描 classpath 下的 SQL XML路径错了运行期必报 Invalid bound statement -- property namemapperLocations valueclasspath:mapper/*.xml/ /bean !-- 扫描 Mapper 接口生成代理对象注入 Service -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.hotel.mapper/ /bean这里的三个配置各管一件事。typeAliasesPackage 目的是让你在 mapper XML 的 resultType 里写短类名mapperLocations 告诉 SqlSessionFactory 到哪找 XML 文件basePackage 指定哪个包下的接口会被自动代理。实际踩坑时最常见的是 mapperLocations 写的路径和 resources 下目录不一致比如 XML 在 resources/mapper/hotel 子目录里配置却写成 classpath:mapper/*.xml星号只匹配一层目录运行期直接找不到。参数修改后要重启 Tomcat因为 SqlSessionFactory 在容器启动时只会创建一次。2.4 数据一致性下单时事务放在 Service 而不是 Controller订房这个动作涉及两张表房间状态要改成锁定订单表要插入一条新记录。如果一步成功、一步失败系统里就会出现房间被占但订单不存在的数据不一致。很多新手在写这类业务时把多个数据操作写在 Controller 里然后问为什么数据对不上——因为事务根本没有包住它们。SSM 里正确做法是把事务注解放在 Service 实现类上Service public class OrderServiceImpl implements OrderService { Transactional(rollbackFor Exception.class) public void createOrder(OrderDTO dto) { int rows roomMapper.lockRoom(dto.getRoomId()); // 先把房间状态改为锁定 if (rows 0) { throw new BizException(房间已被抢占); } orderMapper.insert(dto); // 再插入订单记录 } }逻辑上这段代码解决的是“java 怎么保证数据一致性”这类问题的标准答案房间锁和订单插入在同一事务里任何一个抛异常都会回滚不会出现只锁房不落单的中间态。注意两个边界一是 Transactional 默认只在抛出 RuntimeException 时回滚对检查异常无效所以显式写 rollbackFor Exception.class 更稳妥二是同类内部通过 this 调用另一个带事务的方法事务会失效因为代理对象没经过 Spring 容器这也是答辩时容易被追问的细节。3. 数据库设计与业务状态机酒店预定系统的核心在这里3.1 五张核心表字段设计和能省则省的边界酒店客房预订系统的数据库设计看源码后会发现不同作者的表名、字段名各不相同但核心表逃不出这五类用户表、房间类型表、房间表、订单表、入住流水表。它们的职责边界很清楚表名常见写法关键字段取值说明t_useruser_id, username, password, phone, id_card, vip_level角色靠字段区分管理员与普通用户共用一张表t_room_typetype_id, name, price, bed_num, area, breakfast大床房、双床房、商务房、套房t_roomroom_id, type_id, room_no, statusstatus 0 空闲 / 1 锁定 / 2 入住 / 3 脏房t_orderorder_id, user_id, room_id, start_date, end_date, nights, total_price, statusstatus 0 待支付 / 1 已支付 / 2 已入住 / 3 已完成 / 4 已取消t_checkinid, order_id, room_id, checkin_time, checkout_time, operator入住退房流水部分项目用订单状态兼任设计时注意两点。一是用户表不要为管理员单开一张表用 user_type 或 is_admin 字段区分即可这样管理员登录和用户登录走同一套逻辑少一套 Session 处理。二是订单表必须单独存 start_date、end_date 和 nights不要在页面提交后再临时计算天数因为价格、超时取消、房间占用判断都要依赖这三个字段而且你要是把日期存成字符串格式后面 SQL 比较会很痛苦。3.2 建表语句表名千万别用 order下面这组建表语句是我按最常见的酒店预定项目整理出来的去掉了外键约束用逻辑关联代替物理外键。这样做的好处是录入测试数据时不用严格按依赖顺序执行对毕设演示更友好CREATE TABLE t_room_type ( type_id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 大床房/双床房/商务房, price decimal(10,2) NOT NULL COMMENT 每晚价格, bed_num tinyint(4) DEFAULT 1 COMMENT 床位数, area varchar(20) DEFAULT NULL COMMENT 面积如 25㎡, breakfast tinyint(4) DEFAULT 0 COMMENT 0无早 1含早, PRIMARY KEY (type_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房间类型表; CREATE TABLE t_room ( room_id int(11) NOT NULL AUTO_INCREMENT, type_id int(11) NOT NULL COMMENT 关联 t_room_type, room_no varchar(20) NOT NULL COMMENT 如 8801, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0空闲 1锁定 2入住 3脏房, PRIMARY KEY (room_id), KEY idx_type (type_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房间表; CREATE TABLE t_order ( order_id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL, room_id int(11) NOT NULL, start_date date NOT NULL COMMENT 入住日期, end_date date NOT NULL COMMENT 离店日期, nights tinyint(4) NOT NULL COMMENT 共几晚, total_price decimal(10,2) NOT NULL COMMENT 订单总价, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已入住 3已完成 4已取消, create_time datetime NOT NULL, PRIMARY KEY (order_id), KEY idx_user (user_id), KEY idx_room_date (room_id, start_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;为什么订单表要用 t_order 而不是 order因为 order 是 SQL 里的排序关键字直接建 order 表会带来一连串玄学问题查询时写 select * from order 直接报语法错误加反引号能勉强运行但 mapper XML 里如果还要写 order by拼接出来的 SQL 可读性极差。这是这类项目里高频出现的低级事故我见过不止一个同学的 database 导入失败原因就是这条。命名时在表名加 t_ 前缀或 _info 后缀是从业者规避保留字的常见做法。3.3 查可订房间的 SQL把日期重叠和状态过滤写对酒店预定系统的核心查询是“用户选择入住日期和离店日期后哪些房间还能订”。很多人会写成只判断房间当前状态是否为 0这其实不完整。正确逻辑必须同时满足两个条件房间当前没有被锁定或入住并且在目标时间段内没有已支付或已入住的订单。查询可订房间的 SQL 长这样SELECT r.room_id, r.room_no, t.name AS type_name, t.price FROM t_room r JOIN t_room_type t ON r.type_id t.type_id WHERE r.type_id #{typeId} AND r.status 0 AND r.room_id NOT IN ( SELECT o.room_id FROM t_order o WHERE o.status IN (1, 2) AND o.start_date #{endDate} AND o.end_date #{startDate} );这段 SQL 里两个参数值得细说。#{endDate} 是用户要离店的日期#{startDate} 是入住日期。子查询里用 o.start_date #{endDate} AND o.end_date #{startDate} 来判断日期区间是否有重叠而不是简单等值比较因为订单可能横跨多天。状态过滤 IN (1, 2) 的意思是只有已支付和已入住才算占用待支付订单是否锁房由业务决定如果怕超卖可以把 0 也加入如果希望留出支付窗口就不加。毕设项目通常选后者逻辑更简单演示时也更顺畅。这条 SQL 还有一个必须注意的边界如果业务把 end_date 定义为最后离店日而不是最后入住日那么“当天退房当天入住”是否允许就取决于子查询里是 还是 。你拿到源码后要先看页面下单时传的是哪一天再对照这条 SQL否则会出现“明明显示可订提交时又说房间被占”的自相矛盾。3.4 初始化数据要像真实酒店而不是三行测试数据很多毕设源码包自带的 SQL 脚本里只有三四个房间、两个用户演示时页面空得没法看答辩老师一打开就没了兴趣。我的建议是让初始化数据贴近真实酒店房间类型至少配五种价格分别是 268、328、458、688、1288 这种有梯度的数值每类房型生成三到五间房房号用楼层加序号模拟比如 8801、8802用户表里放一个名为 admin 的管理员账号和一个普通用户再预置一条“已入住”状态的订单和一条“待支付”状态的订单这样首页的订单统计、房间状态看板不会全是 0。插入测试数据时可以写成一个 init_data.sql和建表语句分开方便答辩前随时把数据库恢复到演示初始状态。4. 本地跑通从解压 zip 到 Tomcat 出页面的完整路径4.1 版本搭配表这套组合最不折腾SSM 加 JSP 这个组合对环境版本敏感版本搭错会浪费掉整个周末。先给出一套我验证过、周边同学用了也很少出问题的搭配组件推荐版本原因JDK1.8绝大多数老源码的 pom 按 1.8 编译语法兼容性最好Maven3.6 或 3.8IDEA 自带的也行别用过于新的版本跑老插件Tomcat8.5 或 9.0关键点不要用 10 以上细节见避坑章MySQL5.7 或 8.0驱动类名有差异后面单独讲IDEA2021 及以上高版本也能用关键在 SDK 设置解释一下为什么是这套组合。JDK 1.8 和 Tomcat 9 搭配覆盖了 javax.servlet 命名空间时代的所有 XML 和 JSP 写法源码不用改一行。MySQL 装新版没关系只要驱动版本跟上即可。相对容易翻车的是 IDEA 默认使用最新 JDK 编译而老项目的 pom 里写的却是 1.8结果就是你还没开始运行就先跟编译报错打了一架。所以拿到源码后第一步不是写代码是把环境统一到这张表上。4.2 用 IDEA 把项目跑起来的七个动作这套动作是我每次帮别人排毕设项目时固定的流程按顺序执行大部分问题都能在启动前暴露把压缩包解压到纯英文路径下例如 D:\hotel-project目录名不要带中文和空格。Tomcat 对带中文的部署路径兼容性参差不齐没必要赌。打开 IDEA选择 File → Open定位到解压目录下的 pom.xml 文件。注意这里不是用 New Project 新建而是打开 Maven 工程你要是像 idea 新建 jsp 项目那样去手动建 Web 工程会丢失所有 Maven 依赖关系。等 IDEA 右下角 Maven 导入完成后展开右侧 Maven 面板点击 Reload All Projects确认依赖全部下载。如果下载卡住或报错检查本机 Maven 的 settings.xml把中央仓库替换成阿里云公共仓库能明显提速。打开 Project Structure将 Project SDK 设为 1.8Project language level 设为 8。这是避免编译版本报错的关键一步。打开数据库客户端先创建数据库再执行解压目录里 db 文件夹下的 .sql 脚本。没有这个脚本的按第三章建表语句自己创建。找到 src/main/resources 下或 classpath 里的 jdbc.properties把用户名、密码改成你本机数据库的值。点击 Run → Edit Configurations新增 Tomcat Server → LocalDeployment 里添加 war explodedApplication context 填 /hotel然后启动。第 7 步很多人会漏掉 Deployment 配置直接启动 Tomcat 后访问 localhost:8080 只看到 Tomcat 默认首页以为项目失败了。实际上你要访问的地址是 http://localhost:8080/hotel/前面这个 /hotel 就是 Application context 里填的值它对应打包后的访问前缀。4.3 jdbc.properties 的几个参数改错就报连接失败数据库连接配置长这样不同源码包可能写成 db.properties 或 application.properties内容大同小异jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你自己的密码先看 driver 这一行。如果本地是 MySQL 5.7 或驱动包版本是 5.x这里要写成 com.mysql.jdbc.Driver如果是 MySQL 8.0 及以上用 com.mysql.cj.jdbc.Driver。驱动不对的报错往往是 ClassNotFoundException而不是连接失败所以先确认依赖里 mysql-connector-java 的版本。再看 urlserverTimezoneAsia/Shanghai 是 MySQL 8 的必填项不填会报时间时区错误useSSLfalse 是为了避免本地连接时出现 SSL 握手告警characterEncodingutf8 必须和后续的中文乱码问题联动检查。最后提醒一句改完 jdbc.properties 要重启 Tomcat 才会生效因为这个文件通常在 Spring 容器启动时被 PropertyPlaceholderConfigurer 加载一次热部署不一定触发重读。4.4 传统 jsp 项目打包 war脱离 IDEA 的部署方式IDEA 图形化部署适合开发调试但最后交项目时导师可能希望你在命令行里也能把系统跑起来。传统 JSP 项目的交付形态是 war 包pom.xml 里的 packaging 必须写 war打包命令如下mvn clean package -DskipTests cp target/hotel-1.0.war /path/to/tomcat/webapps/ /path/to/tomcat/bin/startup.sh打包后访问地址要带 war 包的前缀名比如 http://localhost:8080/hotel-1.0/。如果不想要这个前缀可以把 war 重命名为 ROOT.war 再放到 webapps 下。这里顺带澄清一个 Nginx 相关的常见误解Nginx 本身不解析 JSP直接把 war 包解压到 Nginx 的 html 目录下访问得到的是 404 或者纯文本下载正确做法是 Nginx 只负责静态资源动态请求通过 proxy_pass 转发到后端的 Tomcat 实例这样才能形成“Nginx 入口 Tomcat 业务”的经典拓扑。5. 避坑记录ssmjsp 毕设最容易翻车的五个现场5.1 编译报错源发行版 17 需要目标发行版 17现象用 IDEA 打开源码后Build 窗口直接输出“java: 警告: 源发行版 17 需要目标发行版 17”或者干脆报 Error: java: 无效的源发行版。明明代码改都没改就是编不过。原因IDEA 默认把项目 SDK 设为最新 JDK比如 17而 pom.xml 里的 maven.compiler.source 和 target 都是 1.8。两套设置不一致Maven 编译时按 pom 走、IDEA 编译时按 Project Structure 走报错随机出现。解决打开 File → Project Structure → Project把 SDK 换成 1.8Language Level 选 8再进入 Settings → Build, Execution, Deployment → Maven → Runner把 JRE 也指到 1.8。最后在 pom.xml 里补上这段锁定编译版本properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties这个配置改完记得 Reimport 一下 Maven 工程再 clean 一次。5.2 运行报错Invalid bound statement (not found)现象Tomcat 能启动页面也能打开但一点“查询可订房间”或“提交订单”页面立即 500控制台出现大段堆栈核心一句是 Invalid bound statement (not found): com.hotel.mapper.RoomMapper.findFreeRooms。原因MyBatis 在运行期没有把 Mapper 接口和对应的 SQL XML 绑定起来。常见原因有三个resources/mapper 下的 XML 没有被编译进 target/classesXML 的 namespace 写的类名与 Mapper 接口全限定名不一致mapperLocations 的路径写错扫不到文件。解决先按 CtrlShiftN 搜索你的 Mapper XML 文件名如果搜得到再展开 target/classes 看有没有对应文件没有就是 IDEA 的资源目录标记不对——右键 resources 目录选 Mark Directory as Resources Root然后 clean 再重启。如果 XML 已经打进去了打开 XML 看 namespace 是不是和接口路径一字不差。还有一种隐蔽情况是接口方法有两个参数但没有加 Param 注解XML 里写参数名就会解析失败报错同样指向 binding 异常。5.3 容器选错Tomcat 10 上 JSP 全部 500现象本地开发都正常把项目部署到 Tomcat 10 之后所有 JSP 页面访问都 500错误里带着 java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest或者 jakarta.servlet.ServletException。原因Tomcat 10 开始Servlet 规范把包名从 javax.* 迁移到了 jakarta.*。老 SSM 项目里的代码、JSP、web.xml 全部基于 javax 编写容器换了所有 Servlet API 引用全部失配这不是代码 bug是容器版本和项目年代不匹配。解决最省事的办法是卸掉 Tomcat 10安装 Tomcat 8.5 或 9.0 重新部署这两个版本仍然兼容 javax 命名空间。如果导师机器上已经装了 Tomcat 10 不方便换那需要把项目升级为 jakarta替换 pom 里的 servlet-api 依赖、把所有 import javax.servlet 改为 import jakarta.servlet、web.xml 根标签换成 jakarta 版本改动范围取决于代码里有多少处引用毕设时间紧张时不推荐走这条路。5.4 数据库MySQL 8 驱动与 serverTimezone 两行配置现象Tomcat 启动过程没有异常但第一个需要访问数据库的请求就报 Communications link failure或者是 java.sql.SQLException: The server time zone value ??? 不能识别。原因数据库连接串里缺少时区参数且驱动类名写的是老版本。MySQL 8 的驱动要求 url 里显式指定 serverTimezone否则驱动无法判断客户端和服务器之间如何换算时间。同时老代码里 jdbc.driver 若写的是 com.mysql.jdbc.Driver在 MySQL 8 的驱动版本里会给出已过时的提示甚至直接拒绝加载。解决把 url 改为 jdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghaidriver 改成 com.mysql.cj.jdbc.Driver。同时检查 pom.xml 中 mysql-connector-java 的版本如果是 5.1.x 就升级到 8.0.x依赖的 groupId 用 mysql 即可。改完记得重启 Tomcat这类参数只在启动时读取。5.5 页面中文乱码从库到 JSP 一层层查现象登录页显示正常但查询出来的房间名称、用户姓名全是问号或乱码数据库里看却是正常的。原因全链路中的某一环字符集不对。常见分布是数据库表默认 charset 是 latin1JSP 页面没有声明 UTF-8JDBC url 缺 characterEncoding或者 web.xml 里没配编码过滤器。四者缺一都可能产生乱码。解决按三层顺序处理。第一建表语句显式写成 CHARSETutf8mb4已有表用 ALTER TABLE t_room CONVERT TO CHARACTER SET utf8mb4 转换。第二JSP 页面头部写 % page contentTypetext/html;charsetUTF-8 languagejava %。第三确保 Spring 的 CharacterEncodingFilter 在 web.xml 中第一个注册filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping注意 forceEncoding 参数必须为 true否则只对请求生效、对响应不生效。每次改动后重启 Tomcat、用无缓存的浏览器窗口重新访问避免被旧页面误导。6. 答辩前的最后准备三个扩展点与一份验证清单6.1 三个不超过 30 行的扩展让系统从“会跑”变成“有想法”如果时间充裕可以在原有功能上做三个小而清晰的扩展。第一个是超时未支付订单自动取消用 Spring Task 的 Scheduled 注解定期扫描把创建超过 15 分钟且状态仍为 0 的订单置为取消并释放对应房间Scheduled(cron 0 0/5 * * * ?) public void timeoutCancel() { ListInteger ids orderMapper.listTimeoutOrders(15); for (Integer id : ids) { orderService.cancelByTimeout(id); } }这个扩展只需在 Spring 配置开启 EnableScheduling再写一个定时任务方法但它能引出“为什么用定时任务而不用死循环”“如果服务器宕机如何补偿”两个答辩问题属于投入产出比很高的亮点。第二个扩展是把订单总价计算抽成一个接口平时按挂牌价乘晚数周末或节假日走另一套计费策略用策略模式替代 Service 里写死的金额乘法。第三个扩展是给管理员页面加一个入住趋势统计用一张聚合查询按天统计订单数逻辑不复杂却能让系统看上去有“数据分析”的味道。6.2 十分钟跑完的功能验证清单答辩前必须完整过一遍核心流程我一般会按这张表逐项勾验证点操作期望结果用户注册前台注册一个账号提示成功数据库多一条记录登录与拦截未登录访问订单页被重定向到登录页查询可订房选入住和离店日期已占用房间不再展示提交订单选择房间提交订单状态为待支付模拟支付管理员后台确认或改成已支付房间状态变为锁定办理入住后台操作入住房间状态变为入住退房结算后台操作退房订单状态已完成房间恢复空闲超时取消用待支付订单等 15 分钟定时任务自动取消并释放房间每验证完一步截一张图存到答辩素材文件夹里。截图要带上浏览器地址栏和时间这比口头描述“我测过了”有说服力得多。6.3 守住基础八股答辩时值得反复讲的三个实现答辩环节被问的问题往往不是项目有多高级而是最基础的技术点。第一Transactional 为什么加在 Service 而不是 Controller——因为 Controller 是调度层多个请求可以并发进入事务边界的控制应该在业务层才能保证一个完整业务动作要么全部成功、要么全部回滚。第二JSP 和 Servlet 的关系——JSP 本质上是 Servlet 的模板形态第一次被访问时由容器编译成 Servlet 再执行这也是为什么“传统 JSP 项目打包 war”后页面第一次访问总会稍慢。第三MyBatis 里 #{} 和 ${} 的区别——前者走预编译占位符安全但不能动态拼表名后者直接拼接字符串灵活但有注入风险。这三段讲熟比背十几个框架 API 更有用。我自己的习惯是在交源码前把整个压缩包解压到一台空环境里完全按上面的版本表和部署命令重跑一遍确认从零到页面打开不超过二十分钟。这样做既是对自己项目的体检也相当于提前模拟了答辩现场的演示环境因为导师那台电脑永远和你开发机不一样。希望帮到你。本文还有配套的精品资源点击获取