简介:这是一套面向计算机专业本科生的毕业设计级酒店管理系统实战项目,聚焦Java全栈开发能力培养,特别适合SpringBoot与Vue.js双技术栈入门到进阶的学习者。资源包含完整前后端源码、数据库脚本、开题报告与任务书等教学支撑材料,覆盖需求分析、系统设计、编码实现到部署演示全流程,助力学生高效完成课程设计或毕业答辩。压缩包共7个文件,含3个核心代码包(含前后端源码及返修版本)、2份Word文档(开题报告与任务书)、1个SQL建库脚本和1个B站实操录屏MP4,总大小77.48MB,结构清晰、开箱即用。已有69人下载学习,配套启动教程与系统演示视频双轨并行,显著降低环境搭建与功能验证门槛;数据库设计规范、模块划分明确(前台预订/后台管理双端分离),便于理解业务逻辑与工程化实践要点。
1. 为什么用 SpringBoot3 + Vue.js3 做酒店管理系统,是2025届计算机/软件工程毕业设计里「踩坑少、过审稳、演示亮」的务实选择?
不是所有毕业设计都值得花三个月肝。去年带了6个本科生做毕设,其中4个选了「基于SpringBoot的XX系统」,但只有用 SpringBoot3 + Vue.js3 搭建酒店管理系统的两个同学,在答辩前一周没改接口、没重写前端路由、没被导师问“你这个登录怎么没加JWT鉴权”——因为他们从第一天就踩在了技术栈的「确定性」上:SpringBoot3(2022年正式GA,JDK17+成为硬门槛)已彻底取代2.x,Vue.js3(Composition API + Pinia + Vite)也早已不是“尝鲜”,而是企业级中后台事实标准。酒店管理场景天然具备「模块边界清晰」(房态、预订、入住、账务、报表)、「业务逻辑可验证」(超订拦截、押金冻结、退房结账状态机)、「数据关系不爆炸」(无社交图谱、无海量实时消息),恰好避开SpringBoot3对响应式编程的强依赖和Vue.js3对复杂状态同步的玄学调试。它不像物联网毕设要折腾硬件联调,也不像知识图谱项目得啃NLP论文,更不涉及PLC或STM32的物理层黑匣子。你不需要造轮子,但必须把轮子装得严丝合缝:比如用SpringBoot3的@Validated做多层校验链,用Vue.js3的defineAsyncComponent做菜单级路由懒加载,用Axios拦截器统一处理401跳转。这恰恰是评审老师最想看到的——不是炫技,而是对主流技术栈边界的清醒认知与落地控制力。
2. 从零初始化:用官方脚手架搭出可运行骨架,绕开SpringBoot3与Vue.js3的版本耦合雷区
2.1 创建 SpringBoot3 后端工程:必须用 start.spring.io 官方生成器,禁用 IDE 内置向导
SpringBoot3 的起步陷阱在于「JDK 版本错配」和「Starter 依赖污染」。IntelliJ IDEA 或 Eclipse 的内置新建向导默认仍指向 SpringBoot2.x 的 parent POM,且可能引入 spring-boot-starter-webflux(响应式Web)这类非必要依赖——而酒店管理系统99%的接口都是阻塞式HTTP请求,强行上WebFlux只会让事务管理(@Transactional)失效、让MyBatis操作变黑匣子。
提示:务必访问 https://start.spring.io ,手动设置:
- Project:Maven
- Spring Boot:3.2.12(截至2025年3月最新稳定版,兼容 JDK17~21)
- Packaging:Jar
- Java:17(不要选21,部分国产数据库驱动尚未完全适配)
- Dependencies:勾选
Spring Web、Spring Data JPA、MySQL Driver、Validation、Lombok(禁用Spring Boot DevTools,毕设演示环境禁用热部署)
生成后解压,用命令行验证基础启动:
# 进入项目根目录 cd hotel-backend # 清理并编译(跳过测试,避免H2数据库初始化失败干扰) mvn clean compile -DskipTests # 启动 mvn spring-boot:run成功日志应含Tomcat started on port(s): 8080 (http)。若报错java.lang.NoClassDefFoundError: jakarta/servlet/ServletContainerInitializer,说明JDK版本低于17或IDE未正确识别SpringBoot3的Jakarta EE 9+命名空间——此时必须检查pom.xml中<parent>标签是否为spring-boot-starter-parent:3.2.12,且<properties>下明确声明<java.version>17</java.version>。
2.2 初始化 Vue.js3 前端:Vite 5.4 + Vue Router 4.3 + Pinia 2.2 是当前最稳组合
Vue.js3 的混乱点在于生态碎片化:有人用 Vue CLI(已停止维护),有人硬套 Vue2 的 Vuex(不兼容Composition API),还有人盲目上 Taro 跨端——毕设只需一个能跑通增删改查的单页应用(SPA),Vite 是唯一理性选择。它启动快(毫秒级HMR)、构建产物小(酒店系统前端资源<1MB)、且与 Vue Router 4 和 Pinia 2 深度协同。
执行以下命令创建前端工程:
# 在 hotel-backend 同级目录执行 npm create vite@latest hotel-frontend -- --template vue cd hotel-frontend npm install # 安装核心依赖(注意版本号!) npm install vue-router@4.3.2 pinia@2.2.3 axios@1.6.7 element-plus@2.7.8 # 启动开发服务器 npm run dev关键验证点:打开http://localhost:5173应显示默认Vue欢迎页;检查src/main.js是否已用createApp替代new Vue();确认src/router/index.js使用createRouter而非new Router()。若npm run dev报错Cannot find module 'vue',说明vite.config.js中resolve.alias未正确映射,需手动添加:
// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': '/src', 'vue': 'vue/dist/vue.esm-bundler.js' // 强制使用ESM构建版,避坑Vite 5.4的SSR兼容问题 } } })2.3 前后端联调第一步:配置跨域代理,让 Vue 开发服务器直连 SpringBoot3 接口
毕设演示时,你不可能现场部署Nginx反向代理。Vite 的server.proxy是唯一可靠方案,但必须严格匹配 SpringBoot3 的server.servlet.context-path(默认为/)和spring.mvc.servlet.path(默认也为/)。常见错误是前端axios请求写成http://localhost:8080/api/login,却忘了后端实际路径是http://localhost:8080/login。
在vite.config.js中配置代理:
// vite.config.js export default defineConfig({ // ...其他配置 server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', // SpringBoot3 默认端口 changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') // 把 /api/login → /login } } } })然后在src/utils/request.js封装 Axios 实例:
// src/utils/request.js import axios from 'axios' const request = axios.create({ baseURL: '/api', // 所有请求自动加 /api 前缀 timeout: 10000 }) // 请求拦截器:自动携带 token(后续扩展用) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) config.headers.Authorization = `Bearer ${token}` return config }) export default request此时调用request.post('/login', { username, password }),实际发送到http://localhost:8080/login——这是联调成功的第一个硬指标。
3. 核心模块落地:用 SpringBoot3 的 JPA + Vue.js3 的 Composition API 实现「房态看板」闭环
3.1 后端:Room 实体与 Repository 层设计,用 @Query 注解精准控制房态统计SQL
酒店管理系统的核心是「房态实时性」,不能靠前端轮询或WebSocket硬推(毕设复杂度超标)。SpringBoot3 + JPA 的最优解是:后端提供 REST 接口返回结构化房态数据,前端用 ECharts 渲染。关键在于 SQL 的可控性——JPA 的@NamedQuery或findAll()无法满足「按楼栋+楼层分组统计空房/维修/占用数」的聚合需求,必须手写原生SQL。
定义Room实体(省略 getter/setter):
// src/main/java/com/example/hotel/entity/Room.java @Entity @Table(name = "t_room") public class Room { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String roomNumber; // 房号,如 A-101 private String floor; // 楼层,如 A-1 private String building; // 楼栋,如 A座 private String status; // 状态:VACANT(空闲)、OCCUPIED(入住)、CLEANING(清洁)、MAINTENANCE(维修) private BigDecimal price; // 价格 }在RoomRepository中定义自定义查询:
// src/main/java/com/example/hotel/repository/RoomRepository.java public interface RoomRepository extends JpaRepository<Room, Long> { // 按楼栋+楼层分组统计各状态房间数 @Query(value = """ SELECT building AS building, floor AS floor, SUM(CASE WHEN status = 'VACANT' THEN 1 ELSE 0 END) AS vacantCount, SUM(CASE WHEN status = 'OCCUPIED' THEN 1 ELSE 0 END) AS occupiedCount, SUM(CASE WHEN status = 'CLEANING' THEN 1 ELSE 0 END) AS cleaningCount, SUM(CASE WHEN status = 'MAINTENANCE' THEN 1 ELSE 0 END) AS maintenanceCount FROM t_room GROUP BY building, floor ORDER BY building, floor """, nativeQuery = true) List<Object[]> findRoomStatusByFloor(); }参数说明:
nativeQuery = true绕过Hibernate HQL解析,直接执行MySQL语句;返回List<Object[]>因为结果含多个字段,需在Service层手动映射为DTO。此处不用@SqlResultSetMapping是因毕设无需极致性能,手动转换更易调试。
3.2 前端:用 Vue.js3 的 Composition API + Pinia 构建房态状态管理
Vue.js3 的 Composition API 不是语法糖,而是解决「逻辑复用」和「响应式穿透」的刚需。房态看板需同时支持:1)实时刷新按钮;2)楼栋筛选下拉框;3)点击房间跳转详情。若用 Options API,这些逻辑会散落在data、methods、watch中,答辩时被问「这个筛选是怎么触发刷新的?」很难说清。
在src/stores/roomStore.js中定义 Pinia store:
// src/stores/roomStore.js import { defineStore } from 'pinia' import request from '@/utils/request' export const useRoomStore = defineStore('room', { state: () => ({ roomStatusData: [], // [{building: 'A座', floor: 'A-1', vacantCount: 5, ...}] loading: false, selectedBuilding: '' // 用于筛选 }), actions: { async fetchRoomStatus() { this.loading = true try { const res = await request.get('/room/status') // 对应后端 @GetMapping("/room/status") // 将 Object[] 映射为对象数组 this.roomStatusData = res.data.map(row => ({ building: row[0], floor: row[1], vacantCount: row[2], occupiedCount: row[3], cleaningCount: row[4], maintenanceCount: row[5] })) } catch (error) { console.error('获取房态失败:', error) } finally { this.loading = false } }, // 筛选方法:仅过滤,不触发API请求 filterByBuilding(building) { this.selectedBuilding = building } }, getters: { filteredRoomStatus: (state) => { if (!state.selectedBuilding) return state.roomStatusData return state.roomStatusData.filter(item => item.building === state.selectedBuilding) } } })在src/views/RoomDashboard.vue中使用:
<!-- src/views/RoomDashboard.vue --> <script setup> import { onMounted } from 'vue' import { useRoomStore } from '@/stores/roomStore' import { ElLoading } from 'element-plus' const roomStore = useRoomStore() // 页面加载时获取数据 onMounted(() => { roomStore.fetchRoomStatus() }) // 刷新方法 const handleRefresh = () => { roomStore.fetchRoomStatus() } // 楼栋筛选变更 const handleBuildingChange = (value) => { roomStore.filterByBuilding(value) } </script> <template> <div class="room-dashboard"> <el-row :gutter="20"> <el-col :span="6"> <el-select v-model="roomStore.selectedBuilding" @change="handleBuildingChange" placeholder="选择楼栋"> <el-option label="全部" value="" /> <el-option label="A座" value="A座" /> <el-option label="B座" value="B座" /> </el-select> </el-col> <el-col :span="4"> <el-button type="primary" @click="handleRefresh" :loading="roomStore.loading">刷新</el-button> </el-col> </el-row> <!-- ECharts 图表区域(此处省略具体渲染代码,重点在数据来源) --> <div id="roomChart" style="width: 100%; height: 400px;"></div> </div> </template>逻辑说明:
filteredRoomStatus作为 getter,自动响应selectedBuilding变化,无需手动watch;fetchRoomStatus的try/catch/finally结构确保loading状态必然关闭,避免UI卡死;onMounted钩子保证页面初始化即加载数据——这三点是答辩时展示「工程规范性」的关键细节。
4. 避坑指南:SpringBoot3 + Vue.js3 在毕设场景下的5个高频翻车点及血泪解法
4.1 现象:SpringBoot3 启动时报错Failed to configure a DataSource,即使已配置 application.yml
原因:SpringBoot3 默认启用spring-boot-starter-data-jpa的自动配置,但若application.yml中spring.datasource.url未正确填写(如漏写jdbc:mysql://前缀),或 MySQL 驱动版本与 SpringBoot3 不兼容(如用mysql-connector-java:8.0.28),JPA 会尝试连接 H2 内存数据库并失败。
解决:
- 检查
application.yml中spring.datasource配置是否完整:
spring: datasource: url: jdbc:mysql://localhost:3306/hotel_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver- 在
pom.xml中强制指定 MySQL 驱动版本(SpringBoot3.2.x 兼容8.3.0):
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.3.0</version> </dependency>4.2 现象:Vue.js3 页面空白,控制台报错Uncaught ReferenceError: require is not defined
原因:误将 Node.js 的 CommonJS 语法(如const fs = require('fs'))写入浏览器端代码,或在vite.config.js中错误启用了legacy插件。
解决:
- 删除所有
require()、module.exports语法,前端代码必须用 ES Module(import/export); - 检查
vite.config.js是否包含legacy()插件(毕设无需兼容IE,必须删除); - 若使用
element-plus的按需导入,确保unplugin-vue-components插件已正确安装并配置,否则ElButton等组件无法注册。
4.3 现象:登录成功后,前端localStorage.setItem('token', res.data.token),但后续请求 Header 中Authorization为空
原因:Axios 拦截器未生效,常见于main.js中未正确app.config.globalProperties.$http = request,或拦截器注册位置错误(如在createApp之前调用)。
解决:
- 确保
src/main.js中拦截器注册在app.use(store)之后、app.mount('#app')之前; - 在拦截器内
console.log(config.headers)验证 Header 是否注入; - 若用
provide/inject方式传递 request 实例,需确保inject调用在setup()中,而非mounted钩子。
4.4 现象:SpringBoot3 接口返回 JSON,但 Vue 接收时res.data为字符串而非对象
原因:后端未设置@ResponseBody或@RestController,或 Controller 方法返回类型为String但未加@ResponseBody,导致 Spring MVC 将其视为视图名而非JSON响应体。
解决:
- Controller 类必须加
@RestController(而非@Controller); - 若方法返回
String,必须显式加@ResponseBody; - 检查
pom.xml是否遗漏spring-boot-starter-web(它是@RestController的基础)。
4.5 现象:毕设演示时,修改房间状态后,房态看板未实时更新,需手动刷新页面
原因:前端未监听后端状态变更事件,或后端未提供 WebSocket 推送能力(毕设不推荐),但学生常误以为「只要API返回成功就算完成」。
解决:
- 务实方案:在关键操作(如「办理入住」、「退房结算」)后,主动调用
roomStore.fetchRoomStatus()刷新数据,而非依赖轮询; - 进阶方案:用 SpringBoot3 的
SseEmitter实现服务端事件推送(SSE),比 WebSocket 简单,且 Vue 端只需new EventSource('/room/events')监听,适合毕设展示「实时性」亮点。
5. 演示提效技巧:用 Swagger UI + Postman + Element Plus 表单自动生成,3小时搞定答辩材料包
毕设答辩不是代码审查,而是「让老师3分钟看懂你做了什么」。与其花两天写Word文档,不如用工具链自动生成可交互的演示资产。我带的学生里,用这套组合的人,答辩平均得分高出0.8分——因为老师能亲手点击按钮、看到真实数据流转。
5.1 后端:用 SpringDoc OpenAPI 3 自动生成 Swagger UI,替代手写接口文档
SpringBoot3 官方推荐springdoc-openapi-starter-webmvc-api(非旧版springfox),它零配置即可生成符合 OpenAPI 3.0 规范的文档,且支持 Kotlin/Java 混合项目。
在pom.xml添加依赖:
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-api</artifactId> <version>2.4.0</version> </dependency>启动应用后,访问http://localhost:8080/swagger-ui.html即可看到交互式API文档。关键技巧:
- 在 Controller 方法上加
@Operation(summary = "办理入住")和@ApiResponses注解,让文档自带业务语义; - 用
@Schema(description = "房间ID")标注 DTO 字段,生成文档时自动显示字段说明; - 导出
openapi.json文件(右上角「Export」按钮),后续可导入 Postman。
5.2 前端:用 Element Plus 表单 +v-model双向绑定,5分钟生成可提交的预订表单
酒店预订是高频操作,手写 HTML 表单易出错。Element Plus 的el-form组件配合v-model,能自动生成校验逻辑和提交数据结构。
<!-- src/views/BookingForm.vue --> <template> <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> <el-form-item label="入住日期" prop="checkInDate"> <el-date-picker v-model="form.checkInDate" type="date" value-format="YYYY-MM-DD" /> </el-form-item> <el-form-item label="离店日期" prop="checkOutDate"> <el-date-picker v-model="form.checkOutDate" type="date" value-format="YYYY-MM-DD" /> </el-form-item> <el-form-item label="房型" prop="roomType"> <el-select v-model="form.roomType" placeholder="请选择"> <el-option label="标准间" value="STANDARD" /> <el-option label="豪华套房" value="SUITE" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm">提交预订</el-button> <el-button @click="resetForm">重置</el-button> </el-form-item> </el-form> </template> <script setup> import { ref } from 'vue' import request from '@/utils/request' const form = ref({ checkInDate: '', checkOutDate: '', roomType: '' }) const rules = { checkInDate: [{ required: true, message: '请输入入住日期', trigger: 'change' }], checkOutDate: [{ required: true, message: '请输入离店日期', trigger: 'change' }], roomType: [{ required: true, message: '请选择房型', trigger: 'change' }] } const formRef = ref(null) const submitForm = async () => { await formRef.value.validate(async (valid) => { if (valid) { try { await request.post('/booking', form.value) alert('预订成功!') } catch (error) { alert('预订失败:' + error.response?.data?.message || '网络错误') } } }) } const resetForm = () => { formRef.value.resetFields() } </script>参数说明:
value-format="YYYY-MM-DD"确保日期传给后端的是字符串格式(SpringBoot3 的@DateTimeFormat(pattern = "yyyy-MM-dd")可直接接收);formRef.value.validate()触发 Element Plus 内置校验,比手写if (!form.checkInDate)更专业。
5.3 打包交付:用 Maven + Vite 构建生产包,生成单文件部署包
毕设演示环境通常是老师笔记本,不可能装Node.js和MySQL。必须提供「开箱即用」的部署包:
- 后端打包:
mvn clean package -DskipTests,生成target/hotel-backend-0.0.1-SNAPSHOT.jar; - 前端构建:
cd hotel-frontend && npm run build,生成dist/目录; - 整合部署:将
dist/内容复制到src/main/resources/static/,重新mvn package,此时 JAR 包内含前端静态资源,双击即可运行(java -jar hotel-backend-0.0.1-SNAPSHOT.jar); - 数据库脚本:在
src/main/resources下放init.sql,内容为建表语句和初始测试数据(如10间房、3个员工),启动时通过spring.sql.init.mode=always自动执行。
最后交付物只有3个文件:hotel-backend-0.0.1-SNAPSHOT.jar、init.sql、README.md(含启动命令和默认账号密码)。老师双击JAR,打开浏览器输入http://localhost:8080,就能看到完整的酒店管理系统——这才是毕设该有的交付质感。
我带过的最后一届学生,有个姑娘把init.sql里的测试数据做成「某大学招待所」的真实信息(房间号对应真实楼层、价格按市场价设定),答辩时老师笑着说「这系统我们后勤处真能用」,当场给了优秀。技术是骨架,细节才是灵魂。希望帮到你。
本文还有配套的精品资源,点击获取