做酒店预订系统这个项目其实不是拍脑袋决定的。大多数人练手习惯写图书管理、学生选课但那种项目只能证明你会写CRUD证明不了你对业务场景的理解。酒店预订系统不一样——它天生就有用户端、管理端、权限控制、房间库存状态变化、订单状态流转这些真实业务里绕不开的东西而且每一块都能和Spring Boot Vue这套主流栈对得上。正好手里有一套完整的源码加数据库和文档我在这篇里把整个系统的设计思路、核心实现、跑通过程和踩坑经验完整拆一遍。这篇内容适合三类人看正在做Java课程设计或毕业设计的学生想转全栈但缺乏完整项目经验的开发者以及想在简历上放一个“有业务深度”的项目、但不想写烂大街商城系统的人。我会尽量把“为什么这样做”讲透而不只是贴代码。1. 项目整体设计与技术选型思路1.1 为什么偏偏是Spring Boot Vue这个组合在今天已经不算什么新鲜搭配但要说清楚它为什么能扛起一个酒店预订系统得回到前后端分离的本质上来。先看后端。Spring Boot的核心价值在于它把Spring生态里那堆繁琐的XML配置全部收编成了自动配置。你用spring-boot-starter-web就能起一个内嵌Tomcat的Web服务用spring-boot-starter-data-jpa或MyBatis去操作MySQL用spring-boot-starter-security做权限控制——这些starter的存在意味着开发时脑子里想的是业务逻辑而不是“我应该在哪一行写bean标签”。对于酒店预订这种业务流程相对标准的系统Spring Boot能让我在最短时间内把接口层、业务层、数据访问层搭起来。再看前端。Vue的优势是渐进式——你不需要一开始就把全家桶全上。先拿Vue 3的Composition API搭页面组件路由用Vue Router状态管理用PiniaHTTP请求用Axios这一套下来前端代码的组织方式非常清晰。酒店预订系统的页面数量不算特别多首页、房间列表、详情、下单、订单列表、后台管理但页面之间的状态共享和路由跳转一点不少Vue的组件化开发恰好能把这些东西拆得明明白白。我把这套项目拆开看的时候有一个很直观的体会前后端分离意味着一个人的精力可以聚焦在接口契约上而不是纠缠在模板渲染里。后端只管返回JSON前端只管消费JSON联调的时候只要把接口文档对齐开发效率会快很多。1.2 功能模块拆解用户看到的和后台管的是什么酒店预订系统的功能模块不是随便堆几个页面就完事的。我按“用户端”和“管理端”两个维度把功能拆了一下这样你在看源码时能有个整体地图模块用户端管理端账号体系注册、登录、退出、个人信息管理员登录、拦截器校验房间模块房间列表、房型筛选、房间详情房型维护、房间新增/编辑/上下架预订模块在线选房、提交订单、取消订单订单查看、订单状态处理评价模块订单完成后发表评价评价审核管理数据统计无订单量、营业额等基础统计这个功能划分看起来不复杂但它的隐藏含义是数据库的表结构、接口的权限控制、前端的路由守卫都要围绕这个划分去设计。很多新手项目挂在“功能能跑但权限一塌糊涂”上就是因为在设计阶段没把用户端和管理端边界切清楚。这套系统的做法是后端接口用拦截器校验登录状态和管理员角色前端路由用meta.requiresAdmin做守卫两端互相兜底而不是只靠前端隐藏按钮来防越权。1.3 数据库设计一张表就是一类业务实体数据库是这个项目里最值得花时间看的部分。我打开SQL脚本扫了一圈表设计基本遵循了常规酒店业务的实体关系核心表包括user用户表存账号、密码加密存储、昵称、手机号。room_type房型表存房型名称、价格、面积、床型、可住人数。room房间表取房型ID关联具体的房间号、楼层、状态空闲/已入住/维修。order订单表关联用户ID和房间ID记录入住日期、离店日期、订单金额、订单状态。comment评价表关联用户和订单存评分与评论内容。admin管理员表与前台用户表分离。这里有个设计细节值得专门拿出来说为什么要把room_type和room拆成两张表因为你新建一个“豪华大床房”是房型层面的操作而“3楼的301房间是豪华大床房”是房间实例层面的操作。如果不拆同一个房型的多个房间在表里会存在大量冗余字段而且改价格时要改N行数据。拆开后改房型价格只需改room_type一张表房间表里通过外键自动感知。这就是表结构设计里的“先分析实体关系再建表”原则。订单表的状态字段是这个系统的业务核心。我用的是0待支付、1已支付、2已入住、3已完成、4已取消、5已退款这类整数状态比直接用字符串更节省空间也方便后续做统计时用CASE WHEN分组。真正的生产系统还会加状态机约束但课程设计级别的项目里把状态字段的流转逻辑写清楚就够了。1.4 源码目录结构拿到项目先别急着跑先看地图我第一次拿到一套陌生源码时不会直接双击运行而是先看目录结构因为目录结构能告诉你作者的代码组织习惯。这套项目的后端结构大概是hotel-backend ├── src/main/java/com/example/hotel │ ├── controller # 控制层接收前端请求 │ ├── service # 业务层核心逻辑 │ ├── mapper # MyBatis的Mapper接口 │ ├── entity # 数据库实体类 │ ├── config # 配置类如拦截器、跨域 │ ├── common # 通用返回结果、异常处理 │ └── utils # 工具类如JWT工具 └── src/main/resources ├── mapper # MyBatis的XML映射文件 └── application.yml # 数据源等配置前端的结构则是典型的Vue CLI或Vite工程hotel-frontend ├── src │ ├── api # 封装axios请求的接口层 │ ├── router # 路由配置 │ ├── store # 全局状态 │ ├── views # 页面组件 │ ├── components # 通用组件 │ ├── utils # 前端工具函数 │ └── App.vue这个结构的价值在于“按职责分层”排查问题时能顺着调用链走页面组件 - api层 - controller - service - mapper - SQL。很多新手项目把所有代码都堆在Controller里看起来能跑但一旦业务复杂一点维护就是灾难。这套源码的分层方式是我推荐学习者重点模仿的地方。2. 核心功能实现与实操细节2.1 登录注册与权限控制JWT是怎么嵌入项目里的账号这块前端Vue负责收集用户输入后端Controller接收后调用Service。密码存储是很多新手忽略的重点——明文存库是绝对不能出现在简历上的低级错误。这套项目用的是BCryptPasswordEncoder去做哈希加密你可以在源码里看到注册时encode()登录时matches()的用法。BCrypt的好处是每次哈希的结果都带随机盐即使两个用户密码相同存储的密文也不同能有效对抗彩虹表攻击。登录成功后的核心是生成Token。这里用了JWTJSON Web Token做法是用户登录成功后后端把用户ID和角色塞进Token里签名返回前端把它存到localStorage或sessionStorage里之后每次请求在axios拦截器中加上Authorization: Bearer token。后端再通过一个拦截器HandlerInterceptor统一解析Token把用户信息放进ThreadLocal或Request域里供后续业务使用。这里有个实际开发中很关键的坑JWT天然无状态所以它无法在服务端主动让一个Token失效。如果你想做“修改密码后强制退出所有设备”只能靠引入Token黑名单或改用Redis存储会话但在这个级别的项目里用JWT配合过期时间完全够用。我建议你在读源码时重点看一下拦截器的排除路径比如登录接口、注册接口、房间列表这类公开接口必须放行否则前端还没登录就寸步难行。2.2 房间搜索与预订闭环库存状态的前台到后台链路酒店预订系统最有趣的业务逻辑是“搜索→选择→下单→改状态”这条链路。前端搜索页通常有几个维度入住日期、离店日期、房型、价格区间。Vue组件里用v-model绑定表单数据提交时通过this.$router.push({ path: /search, query: {...}})把参数带到列表页或者直接用Axios GET请求把搜索参数发给后端。后端接收后在Service里组装查询条件注意这里通常要处理“空条件”的情况——用户可能没选日期就点搜索你给SQL拼条件时要判断参数是否为空。真正复杂的是“库存”判断。酒店的房间不像电商商品那样有一个独立的库存数字而是“某日期区间内是否空闲”。这套系统的处理方式是查询房间时把订单表里状态为已支付/已入住、并且日期范围与用户搜索范围重叠的订单对应的房间ID排除掉。用SQL来表达大致是SELECT r.* FROM room r WHERE r.status 1 -- 房间本身未停用 AND r.id NOT IN ( SELECT o.room_id FROM order o WHERE o.status IN (1, 2) -- 已支付或已入住 AND o.check_in_date #{checkOut} AND o.check_out_date #{checkIn} );这个check_in_date 离店日期 AND check_out_date 入住日期的判断逻辑是日期区间重叠检测的标准写法我强烈建议你把它抄进自己的笔记里。需要说明的是这只是常见实现思路具体SQL以你手里的roomMapper.xml为准因为我见过有项目用BETWEEN AND来做效果等价的背后原理一样但写法略有差异。下单时对应的操作是事务开启 - 插入订单记录 - 如果设置了房间状态同步则更新房间状态。这里务必要理解事务的意义——如果插入订单成功但更新房间状态失败事务回滚能让数据回到一致状态。源码里Service层的Transactional注解就是干这个的。2.3 订单状态机从待支付到完成每一步都要有据可查订单状态是这个系统里最有“业务味”的部分。学生在做课设时最容易把订单做成“下单就结束”但真实的酒店业务里订单一定是一路变化过来的。我建议你把订单状态理解成一个有限状态机每一条状态迁移都对应一个用户动作或系统动作创建订单 -待支付用户提交预订。支付成功 -已支付模拟支付接口返回成功。到店办理 -已入住管理员在后台操作确认。退房结算 -已完成管理员操作也可做成系统按离店日期自动更新。用户取消 -已取消只能取消待支付和已支付状态的订单。支付后退款 -已退款比已取消更进一步涉及资金退回。为什么这个设计重要因为在写controller时你能根据当前状态判断“这个操作是否合法”。一个已完成订单不能被取消一个已取消订单不能支付——这种业务规则如果靠前端按钮隐藏来保证那后端接口只要有一个人拿Postman乱调就全崩了。正确做法是在Service层最前面做状态校验if (!order.getStatus().equals(0)) { throw new ServiceException(当前订单状态不可支付); }另外我还看到这套源码里在订单表加了create_time和update_time这虽然是最基础的审计字段但也是很多人写项目时会漏掉的。任何一张业务表都建议带上创建时间和更新时间MySQL里可以直接用DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP来维护省去业务代码里手工赋值。2.4 后台管理表格、弹窗、状态联动的一套组合拳后台管理模块的核心是维护类页面房间管理、房型管理、订单管理、评论管理。用Vue做这些页面套路比较固定进入页面时调接口拉列表数据渲染el-table增删改用el-dialog弹窗承载表单保存时再调接口刷新表格。这套源码里Vue组件的高度复用方式值得你学一下——比如把分页组件、搜索表单都抽成公共组件而不是每个页面复制粘贴。后台的权限控制是另一个观察点。前端路由配置里用meta.requiresAdmin标记受保护页面Vue Router的beforeEach全局守卫里判断用户角色不是管理员就重定向到登录页。但这里我想强调一个观点前端路由守卫只是“体验优化”真正的安全防线在后端拦截器。这套源码的后端有一个AdminInterceptor对所有/admin/**路径做Token解析和角色校验没有管理员身份直接返回401。前端和后端各做一层才能叫完整的权限体系。3. 从源码到跑通环境搭建与部署全过程3.1 环境准备清单别让版本成为第一道坎跑Spring Boot Vue项目环境版本是最容易出问题的第一关。先把自己电脑上的环境确认好JDK推荐8或11。Spring Boot 2.x用8没问题Spring Boot 3.x必须17。看清你的pom.xml用的是哪个版本。Maven3.6建议配好阿里云镜像不然下载依赖能卡到你怀疑人生。MySQL5.7或8.x都行注意8.x的驱动和连接URL与5.7有区别。Node.js14以上。Vue CLI项目一般14能跑Vite需要16。IDE后端用IDEA前端用VS Code分离开发是很舒服的配合。3.2 后端启动实操从导入到第一个接口返回我一般按下面这个顺序操作每一步都有明确目的用IDEA打开hotel-backend目录等待Maven依赖下载完毕。首次导入如果依赖下载很慢检查settings.xml的镜像配置。用Navicat或命令行创建数据库。执行hotel.sql导入表结构和初始化数据。打开application.yml修改三处数据库URL、用户名、密码。这里最常见的错是把jdbc:mysql://localhost:3306/hotel?serverTimezoneAsia/Shanghai里的hotel写成自己的库名不一致导致连接失败。找到启动类HotelApplication.java右键运行。启动成功后浏览器访问http://localhost:8080/看到Spring Boot的错误页或接口文档说明后端正常。如果后端启动时报端口占用用netstat -ano | findstr 8080找到占用进程然后taskkill /F /PID 进程号或者在application.yml里改server.port。我个人的习惯是开发环境的端口一律用8080以外的比如8088能少很多冲突。3.3 前端启动实操依赖安装与跨域配置是两大命门前端启动的流程相对简单但命门有两个依赖安装和跨域。先打开hotel-frontend目录执行npm install如果卡在node-sass这类老依赖上大概率是Node版本不匹配。这里的解决思路有两个方向一是切到项目作者用的Node大版本比如nvm use 14二是改用镜像源安装。具体用哪种取决于你手里源码用的依赖版本package.json里看一眼就知道大概要配哪个Node版本。启动开发服务器npm run serve这是Vue CLI项目的常见启动命令。如果是Vite项目则可能是npm run dev以package.json里scripts为准。跨域问题是前后端分离项目绕不开的坎。开发环境下Vue跑在localhost:8080或3000/5173后端跑在8080或其他端口前端请求后端必然触发跨域。解决方式通常有两种一种是在Vue配置里加代理推荐vue.config.js里这样写module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }意思是前端请求/api开头的地址时由Node开发服务器转发到后端绕过浏览器的同源限制。前端请求路径写/api/login就不要写成http://localhost:8080/api/login否则代理不会生效。另一种是后端开启CORS用CrossOrigin注解或配置类全局处理。我在实际部署时更倾向后端同时开启CORS因为生产环境Nginx反代后前端和后端域名不同CORS能帮你更灵活地控制允许来源。3.4 全栈联调从页面到数据库的完整链路前后端都跑起来后建议按“登录 → 查房间 → 下单 → 后台看订单 → 处理订单”这个主线走一遍全流程联调。联调时的核心工具是浏览器的F12 Network面板和后端控制台日志。我联调时的习惯是先在Network面板里确认请求URL、请求方法、请求头和响应状态。如果接口返回500切到后端IDEA控制台看异常堆栈如果返回400多半是参数格式对不上比如后端要JSON格式而前端params传成了query参数。用Postman或Apifox直接调后端接口能快速定位问题到底是前端传参错还是后端逻辑错。这里分享一个排查思路前后端联调出问题时先不谈谁的错先明确“后端接口裸调是否正常”。如果Postman调接口正常而前端调失败问题大概率在前端请求配置如果Postman也报错则后端接口本身有问题。这个二分法能帮你快速缩小故障范围。4. 常见问题与排查技巧实录4.1 数据库连接失败90%是配置问题这个项目的初学者最常见的报错是Access denied for user或Communications link failure前者是用户名密码错误或权限不足后者是URL写错或服务没启动。排查时按顺序检查MySQL服务是否在运行 - URL里的库名和账号密码是否匹配 - 驱动版本是否兼容MySQL版本。还有一个容易踩的坑是时区问题。Java 8以上的MySQL连接URL如果不带serverTimezone参数经常会报The server time zone valueÖйú±ê׼ʱ¼ä is unrecognized。解决方案就在URL后面加上serverTimezoneAsia/Shanghai顺便把useUnicodetruecharacterEncodingutf8也加上一劳永逸解决中文乱码。4.2 跨域报错CORS和代理的边界在哪里浏览器报Access-Control-Allow-Origin相关错误时先判断你用的是开发环境还是生产环境。开发环境用代理最方便因为代理转发是服务端行为不经过浏览器CORS检查生产环境则靠Nginx反代或在后端统一配置CORS。如果后端已经用CrossOrigin注解或CorsFilter配置了允许跨域前端再用代理可能出现“双重跨域”的怪异问题因为代理转发后的请求来源变成了代理服务器。这个问题的解决办法是用一种方式不要混用。我的约定是开发环境只用Vue代理生产环境只用后端CORS或Nginx反代永远不要两套同时开。4.3 中文乱码从数据库到前后端的全链路排查中文乱码是JavaWeb项目里的常青树问题。排查链路是数据库表字符集 - JDBC连接URL - 服务器响应编码 - 前端页面编码。MySQL建表时推荐统一用utf8mb4它比utf8多支持表情符号排序规则用utf8mb4_general_ci即可。JDBC连接加上characterEncodingutf8。Spring Boot的server.servlet.encoding默认就是UTF-8一般不用改。前端HTML文件meta charsetUTF-8必须写Vue项目的index.html里这一行别删。如果你发现数据库里存的中文正常但接口返回的JSON中文变??那基本可以锁定在JDBC连接URL没加characterEncoding。如果前端页面显示乱码而接口返回正常查一下是不是Axios没设置responseType或浏览器的默认编码被干扰了。4.4 打包部署时的坑前端打包后的路径与路由模式项目接近完成时总要面对部署问题。前端执行npm run build后生成dist目录后端用mvn package打成jar包。部署的简单方案是把dist里的文件扔进Spring Boot的src/main/resources/static目录然后直接java -jar hotel.jar启动这样一个端口就能同时跑前端静态资源和后端API。但要注意Vue Router如果使用history模式直接访问/admin这类深链会404因为Spring Boot默认没有为前端路由做fallback解决办法要么改用hash模式要么在后端加一个WebMvcConfigurer把非/api路径全部转发到index.html。生产环境更规范的方案是用Nginx托管前端dist目录location /api反向代理到后端端口。我推荐这个方案的原因是前后端彻底分离、各自独立升级后端流量压力变大时还能横向扩展。5. 源码改造与二次开发的进阶思路5.1 把“能用”变成“好看”几个低成本改造点如果你手里这套源码跑通了接下来最忌讳的就是直接交差。想让它变成简历上的亮点项目我有几个低成本但效果显著的改造建议第一把模拟支付改成接入一个真实的沙箱支付平台或做一个二维码生成的效果。参考实现的思路是前端提交订单后弹出一个支付二维码后端生成二维码图片后返回给前端前端轮询支付结果接口超时后自动把订单置为已取消。这一块代码量不大但能体现你对异步任务和状态轮询的理解。第二给房间图片加上传功能。房间表里目前可能只有图片路径字段但没有上传接口。你可以学习用MultipartFile接收文件、把文件保存到本地磁盘或对象存储、把返回的URL存进数据库。上传功能几乎是每个真实后端项目的必修课一定要自己动手过一次。第三把密码找回、验证码发送这些辅助功能加上。用JavaMailSender或阿里云短信SDK都能实现但注意沙箱环境不需要真的发短信可以把验证码打日志里模拟发送。5.2 业务维度再深一层从预订系统到“管理思维”如果你想让项目在面试时能聊出真正的深度不要只停留在“功能都实现了”而是往业务约束上多想一想。库存并发问题是最值得聊的。两个人同时预订同一间房如果代码只做了“查询空闲 - 插入订单”这两步就可能出现超卖。解决方案是把查询和插入放到一个事务里并对房间号加锁或者用数据库的SELECT ... FOR UPDATE锁定行记录。你在简历上写“我通过数据库锁机制解决了并发订房问题”面试官听到这个点会比听你报菜名感兴趣得多。数据统计也是可以深挖的维度。后台可以增加一个统计页用ECharts展示每日订单量、房型销售占比、月度营业额。SQL层面就是GROUP BY配合日期函数做聚合前端用ECharts的组件接数据。这块代码量不大但展示效果非常好尤其适合答辩和简历截图。5.3 源码文档的正确阅读姿势不止是跑起来最后想说的是拿到一套带源码和文档的项目正确的学习姿势不是跑起来就完事而是“跑通 - 提问 - 改造”三步走。跑通之后一定要给自己提三个问题为什么数据库要这么设计为什么这个接口要返回这种结构为什么权限要写在这里当你发现某个设计“不太合理”的时候恰恰是你最有收获的时刻——因为你可以尝试把它改掉然后对比改动前后的代码差异。我认为看别人的源码最重要的产出不是“看懂了”而是“看出了可以改的地方”。能动手改出一个比原版更合理的版本这套源码才算真正吃透了。至于文档部分我建议你把项目里自带的README或设计文档作为索引但不要迷信它。自己跑通后亲手写一份属于自己的部署文档和接口清单既能加深理解又是一份可以直接放到简历附件里的材料。面试官看到你连部署文档都写得清清楚楚他会默认你是一个有工程素养的候选人而不是只会抄代码的复读机。我做过的几个全栈项目里酒店预订这套系统的业务完整度算是比较靠前的尤其适合作为走向全栈的第一块跳板。它不会让你成为架构大师但足以帮你建立对前后端分离、表设计、权限控制、事务处理、部署运维这些核心概念的直观认知。而这些认知恰恰是光看文档学不来的东西。
阅读完成 · 觉得有帮助?