☰
Spring Boot+Vue 3酒店管理系统实战:从零构建前后端分离项目
2026/10/6 23:37:25 网站建设 项目流程

在实际企业级应用开发中,酒店管理系统是一个集客房管理、订单处理、客户信息、财务统计于一体的综合性项目。它不仅是常见的毕业设计选题,更是检验开发者能否将前后端分离、数据库设计、业务逻辑分层等核心技能融会贯通的实战场景。本文将以一个基于 Spring Boot 后端和 Vue 3 前端的酒店管理系统为例,手把手带你从零开始,完成一个结构清晰、功能完整、可直接运行的实战项目。你将不仅获得一套可用的源码和数据库,更能理解如何将 Spring Boot 的自动配置、MyBatis-Plus 的数据操作、Vue 3 的组合式 API 以及 Element Plus 的组件库串联起来,构建一个具备增删改查、权限控制、数据可视化等典型功能的管理后台。文章会详细解释每个模块的设计思路、关键代码的实现逻辑、配置文件的参数含义,并提供完整的排错路径和部署建议,确保你能在自己的开发环境中成功运行并理解其背后的工程实践。

1. 项目整体架构与技术选型解析

一个可维护、易扩展的酒店管理系统,其技术栈的选型决定了后续开发的效率和项目的稳定性。本项目的核心是前后端分离架构,后端提供 RESTful API,前端负责页面渲染和用户交互。

1.1 后端技术栈:Spring Boot 为核心的 Java 生态

后端采用 Spring Boot 2.7.x 作为基础框架,它极大地简化了 Spring 应用的初始搭建和开发过程。选择此版本是因为其长期支持(LTS)状态稳定,社区资源丰富。

  • 持久层:使用 MyBatis-Plus 作为 ORM 框架。它是对 MyBatis 的增强,在保留 MyBatis 灵活性的同时,提供了通用的 CRUD 操作,无需编写大量 XML 映射文件,能显著提升开发效率。例如,对于Room(客房)表的常规增删改查,通过继承BaseMapper即可获得。
  • 数据校验:集成 Hibernate Validator,用于对控制器(Controller)层接收的参数进行注解式校验,确保输入数据的合法性,如客房价格不能为负数。
  • 权限控制:采用 Spring Security 结合 JWT(JSON Web Token)实现认证与授权。用户登录成功后,后端生成一个加密的 Token 返回给前端,前端在后续请求中携带此 Token,后端通过过滤器链进行校验,从而保护 API 接口。
  • 数据库:使用 MySQL 8.0 作为关系型数据库。其事务支持完善,适合处理订单、账单等需要强一致性的业务。
  • 项目管理与构建:使用 Maven 进行依赖管理和项目构建。

1.2 前端技术栈:Vue 3 构建现代化管理界面

前端采用 Vue 3 的组合式 API(Composition API)进行开发,相比 Vue 2 的选项式 API,逻辑组织和复用性更强。

  • UI 框架:使用 Element Plus,它是基于 Vue 3 的桌面端组件库,提供了丰富、美观的组件,如表格、表单、弹窗、导航菜单等,能快速搭建出专业的管理后台界面。
  • 状态管理:使用 Pinia 作为状态管理库。它是 Vue 官方推荐的状态管理工具,比 Vuex 更简洁,TypeScript 支持更好,用于管理用户登录状态、全局配置等跨组件数据。
  • 路由管理:使用 Vue Router 4.x 进行前端路由控制,实现单页面应用(SPA)的无刷新页面切换。
  • HTTP 客户端:使用 Axios 库发起 HTTP 请求,与后端 API 进行通信。我们会对其进行封装,统一处理请求拦截(如添加 Token)、响应拦截(如处理通用错误)和基础配置。
  • 构建工具:使用 Vite 作为前端构建工具。Vite 提供了极速的冷启动和快速的热更新,开发体验远优于传统的 Webpack。

1.3 系统核心功能模块设计

酒店管理系统的业务模块通常围绕核心实体展开。以下是本项目设计的主要功能模块:

模块名称核心实体主要功能
权限管理User, Role, Menu用户登录、注销、角色分配、菜单权限控制。
客房管理Room, RoomType客房的增删改查、状态管理(空闲、已预订、入住中、维修中)、房型管理。
客户管理Customer客户信息的登记、查询、会员管理。
订单管理Order客户预订客房、入住登记、退房结账、订单历史查询。
财务管理Bill账单生成、支付记录、收入统计。
数据统计-基于 ECharts 的可视化图表,展示客房入住率、收入趋势等。

2. 开发环境准备与项目初始化

在开始编码之前,必须确保本地开发环境配置正确,这是项目能成功运行的第一步。

2.1 后端环境准备

  1. Java 开发工具包 (JDK):安装 JDK 8 或 JDK 11(推荐 JDK 11)。配置JAVA_HOME环境变量。
    # 检查安装是否成功 java -version
  2. 集成开发环境 (IDE):使用 IntelliJ IDEA(社区版或旗舰版)或 Eclipse(需安装 Spring Tools)。IDEA 对 Spring Boot 的支持更友好。
  3. 数据库:安装 MySQL 8.0,并启动服务。创建一个名为hotel_management的数据库,字符集建议使用utf8mb4。
    CREATE DATABASE `hotel_management` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  4. 项目管理工具:安装 Maven 3.6+,并配置MAVEN_HOME和PATH。
    # 检查安装是否成功 mvn -v

2.2 前端环境准备

  1. Node.js:安装 Node.js 16+ 和 npm。建议使用 nvm(Node Version Manager)管理多版本。
    # 检查安装是否成功 node -v npm -v
  2. 包管理工具:可以使用 npm,但更推荐使用速度更快的pnpm或yarn。
    # 安装 pnpm npm install -g pnpm pnpm -v

2.3 初始化 Spring Boot 后端项目

使用 Spring Initializr(可通过 IDEA 内置或访问 start.spring.io )生成项目骨架。

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 2.7.x
  • Project Metadata:
    • Group:com.example
    • Artifact:hotel-backend
    • Packaging: Jar
  • Dependencies: 添加Spring Web,Spring Security,MyBatis Framework,MySQL Driver,Lombok。

生成并导入 IDE 后,项目结构大致如下:

hotel-backend ├── src/main/java/com/example/hotelbackend │ ├── controller/ # 控制器层,接收请求,返回响应 │ ├── entity/ # 实体类,对应数据库表 │ ├── mapper/ # MyBatis Mapper 接口 │ ├── service/ # 业务逻辑层接口 │ │ └── impl/ # 业务逻辑层实现 │ ├── config/ # 配置类(如Security, MyBatis-Plus, CORS等) │ └── HotelBackendApplication.java # 主启动类 ├── src/main/resources │ ├── application.yml # 主配置文件 │ └── mapper/ # MyBatis XML映射文件(如果使用) └── pom.xml # Maven依赖配置文件

2.4 初始化 Vue 3 前端项目

使用 Vite 官方模板快速创建 Vue 3 项目。

# 使用 npm npm create vue@latest hotel-frontend # 或使用 pnpm pnpm create vue@latest hotel-frontend

在创建过程中,根据提示选择需要的特性:

  • Add TypeScript?Yes(推荐,增强类型安全)
  • Add JSX Support?No
  • Add Vue Router for Single Page Application?Yes
  • Add Pinia for state management?Yes
  • Add Vitest for Unit Testing?No(根据需求)
  • Add an End-to-End Testing Solution?No
  • Add ESLint for code quality?Yes

创建完成后,进入项目目录并安装 Element Plus 和 Axios。

cd hotel-frontend pnpm install element-plus axios # 或使用 npm npm install element-plus axios

安装完成后,前端项目结构如下:

hotel-frontend ├── src/ │ ├── api/ # 封装所有后端API请求 │ ├── assets/ # 静态资源 │ ├── components/ # 可复用组件 │ ├── router/ # 路由配置 │ ├── stores/ # Pinia状态管理 │ ├── views/ # 页面视图组件 │ ├── App.vue # 根组件 │ └── main.ts # 入口文件 ├── index.html # HTML模板 ├── package.json # 项目依赖和脚本 └── vite.config.ts # Vite配置

3. 后端核心功能实现详解

我们将以“客房管理”模块为例,贯穿展示从实体设计到接口暴露的全过程。

3.1 数据库设计与实体类映射

首先,在 MySQL 中创建room表。

CREATE TABLE `room` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `room_number` varchar(20) NOT NULL COMMENT '房间号', `room_type_id` bigint NOT NULL COMMENT '房型ID', `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-空闲,1-已预订,2-入住中,3-维修中', `price_per_night` decimal(10,2) NOT NULL COMMENT '每晚价格', `description` varchar(500) DEFAULT NULL COMMENT '房间描述', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_room_number` (`room_number`), KEY `idx_room_type` (`room_type_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客房表';

对应的 Java 实体类Room.java:

package com.example.hotelbackend.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @TableName("room") // 指定表名 public class Room { @TableId(type = IdType.AUTO) // 主键自增 private Long id; private String roomNumber; private Long roomTypeId; private Integer status; // 使用枚举更佳,此处为简化用Integer private BigDecimal pricePerNight; private String description; @TableField(fill = FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; }

这里使用了 Lombok 的@Data注解自动生成 getter、setter 等方法,以及 MyBatis-Plus 的注解进行 ORM 映射。

3.2 配置 MyBatis-Plus 与数据源

在application.yml中配置数据库连接和 MyBatis-Plus。

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/hotel_management?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL,生产环境关闭 global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段名(如果启用) logic-delete-value: 1 logic-not-delete-value: 0 mapper-locations: classpath*:/mapper/**/*.xml # XML映射文件位置

创建一个配置类MybatisPlusConfig.java,启用 Mapper 接口扫描和分页插件。

package com.example.hotelbackend.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.example.hotelbackend.mapper") // 扫描Mapper接口 public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

3.3 实现 Mapper、Service 和 Controller

1. Mapper 层:创建RoomMapper.java接口,继承 MyBatis-Plus 的BaseMapper。

package com.example.hotelbackend.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.hotelbackend.entity.Room; public interface RoomMapper extends BaseMapper<Room> { // 可以在此定义自定义的复杂SQL方法 }

2. Service 层:创建业务接口RoomService.java及其实现类RoomServiceImpl.java。

// RoomService.java package com.example.hotelbackend.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.example.hotelbackend.entity.Room; public interface RoomService extends IService<Room> { Page<Room> getRoomPage(Page<Room> page, String keyword); } // RoomServiceImpl.java package com.example.hotelbackend.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.hotelbackend.entity.Room; import com.example.hotelbackend.mapper.RoomMapper; import com.example.hotelbackend.service.RoomService; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @Service public class RoomServiceImpl extends ServiceImpl<RoomMapper, Room> implements RoomService { @Override public Page<Room> getRoomPage(Page<Room> page, String keyword) { LambdaQueryWrapper<Room> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(keyword)) { // 根据房间号或描述模糊查询 wrapper.like(Room::getRoomNumber, keyword) .or() .like(Room::getDescription, keyword); } // 按创建时间倒序 wrapper.orderByDesc(Room::getCreateTime); return baseMapper.selectPage(page, wrapper); } }

3. Controller 层:创建RoomController.java,处理 HTTP 请求。

package com.example.hotelbackend.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.hotelbackend.common.Result; import com.example.hotelbackend.entity.Room; import com.example.hotelbackend.service.RoomService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @RestController @RequestMapping("/api/room") public class RoomController { @Autowired private RoomService roomService; // 新增客房 @PostMapping public Result addRoom(@Valid @RequestBody Room room) { // 业务校验,如房间号是否重复 boolean saved = roomService.save(room); return saved ? Result.success() : Result.error("新增失败"); } // 分页查询客房列表 @GetMapping("/page") public Result getRoomPage(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keyword) { Page<Room> page = new Page<>(pageNum, pageSize); Page<Room> roomPage = roomService.getRoomPage(page, keyword); return Result.success(roomPage); } // 根据ID查询客房详情 @GetMapping("/{id}") public Result getRoomById(@PathVariable Long id) { Room room = roomService.getById(id); return room != null ? Result.success(room) : Result.error("客房不存在"); } // 更新客房信息 @PutMapping public Result updateRoom(@Valid @RequestBody Room room) { boolean updated = roomService.updateById(room); return updated ? Result.success() : Result.error("更新失败"); } // 删除客房(逻辑删除) @DeleteMapping("/{id}") public Result deleteRoom(@PathVariable Long id) { boolean removed = roomService.removeById(id); return removed ? Result.success() : Result.error("删除失败"); } }

这里使用了统一的Result类封装返回结果,包含code、message和data字段,便于前端处理。

3.4 配置 Spring Security 与 JWT

为了安全,需要配置 Spring Security 和 JWT。首先添加依赖到pom.xml。

<!-- JWT 支持 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>

创建 JWT 工具类JwtUtil.java用于生成和解析 Token。然后创建SecurityConfig.java配置类,定义密码编码器、认证管理器、安全过滤链等。核心是配置哪些路径需要认证,哪些可以匿名访问,并添加一个自定义的 JWT 认证过滤器。

4. 前端核心功能实现与联调

前端将使用 Vue 3 + Element Plus 构建客房管理的页面,并通过 Axios 与后端 API 通信。

4.1 封装 Axios 请求实例

在src/api/request.ts中创建 Axios 实例,并配置请求/响应拦截器。

import axios from 'axios'; import { ElMessage } from 'element-plus'; import router from '@/router'; import { useUserStore } from '@/stores/user'; const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api', // 从环境变量读取 timeout: 10000, }); // 请求拦截器:在请求头中添加 Token request.interceptors.request.use( (config) => { const userStore = useUserStore(); const token = userStore.token; if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器:统一处理错误 request.interceptors.response.use( (response) => { const res = response.data; // 假设后端统一返回 { code: 200, message: 'success', data: ... } if (res.code === 200) { return res.data; } else { ElMessage.error(res.message || '请求失败'); return Promise.reject(new Error(res.message || 'Error')); } }, (error) => { if (error.response?.status === 401) { ElMessage.warning('登录已过期,请重新登录'); router.push('/login'); } else { ElMessage.error(error.response?.data?.message || error.message || '网络错误'); } return Promise.reject(error); } ); export default request;

4.2 实现客房管理 API 模块

在src/api/room.ts中定义所有与客房相关的 API 函数。

import request from './request'; // 定义 Room 类型接口(应与后端实体对应) export interface Room { id?: number; roomNumber: string; roomTypeId: number; status: number; pricePerNight: number; description?: string; } // 分页查询参数 export interface RoomPageParams { pageNum: number; pageSize: number; keyword?: string; } // 分页返回结果 export interface PageResult<T> { records: T[]; total: number; size: number; current: number; } // API 函数 export const roomApi = { // 新增客房 addRoom(data: Room) { return request.post('/room', data); }, // 分页查询客房列表 getRoomPage(params: RoomPageParams) { return request.get<PageResult<Room>>('/room/page', { params }); }, // 根据ID查询客房详情 getRoomById(id: number) { return request.get<Room>(`/room/${id}`); }, // 更新客房信息 updateRoom(data: Room) { return request.put('/room', data); }, // 删除客房 deleteRoom(id: number) { return request.delete(`/room/${id}`); }, };

4.3 构建客房列表页面

创建src/views/room/RoomList.vue组件,使用 Element Plus 的ElTable和ElPagination组件。

<template> <div class="room-list-container"> <el-card> <template #header> <div class="card-header"> <span>客房管理</span> <el-button type="primary" @click="handleAdd">新增客房</el-button> </div> </template> <!-- 搜索栏 --> <el-form :inline="true" :model="searchForm"> <el-form-item label="关键词"> <el-input v-model="searchForm.keyword" placeholder="房间号/描述" clearable @clear="handleSearch" /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleSearch">查询</el-button> <el-button @click="resetSearch">重置</el-button> </el-form-item> </el-form> <!-- 客房表格 --> <el-table :data="tableData" border stripe v-loading="loading"> <el-table-column prop="roomNumber" label="房间号" width="120" /> <el-table-column prop="roomTypeId" label="房型" width="100"> <template #default="scope"> {{ getRoomTypeName(scope.row.roomTypeId) }} </template> </el-table-column> <el-table-column prop="status" label="状态" width="100"> <template #default="scope"> <el-tag :type="statusTagType(scope.row.status)"> {{ getStatusText(scope.row.status) }} </el-tag> </template> </el-table-column> <el-table-column prop="pricePerNight" label="价格(元/晚)" width="120" /> <el-table-column prop="description" label="描述" show-overflow-tooltip /> <el-table-column prop="createTime" label="创建时间" width="180" /> <el-table-column label="操作" width="200" fixed="right"> <template #default="scope"> <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button> <el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <div class="pagination-wrapper"> <el-pagination v-model:current-page="pageParams.pageNum" v-model:page-size="pageParams.pageSize" :page-sizes="[10, 20, 50, 100]" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </el-card> <!-- 新增/编辑对话框 --> <room-form-dialog v-model="dialogVisible" :form-data="currentRow" :is-edit="isEdit" @success="handleDialogSuccess" /> </div> </template> <script setup lang="ts"> import { ref, reactive, onMounted } from 'vue'; import { ElMessage, ElMessageBox } from 'element-plus'; import { roomApi, type Room, type RoomPageParams } from '@/api/room'; import RoomFormDialog from './components/RoomFormDialog.vue'; // 搜索表单 const searchForm = reactive({ keyword: '', }); // 分页参数 const pageParams = reactive<RoomPageParams>({ pageNum: 1, pageSize: 10, }); // 表格数据 const tableData = ref<Room[]>([]); const total = ref(0); const loading = ref(false); // 对话框相关 const dialogVisible = ref(false); const currentRow = ref<Partial<Room>>({}); const isEdit = ref(false); // 获取客房列表 const fetchRoomList = async () => { loading.value = true; try { const params = { ...pageParams, ...searchForm }; const result = await roomApi.getRoomPage(params); tableData.value = result.records; total.value = result.total; } catch (error) { console.error('获取客房列表失败:', error); } finally { loading.value = false; } }; // 搜索 const handleSearch = () => { pageParams.pageNum = 1; // 重置到第一页 fetchRoomList(); }; // 重置搜索 const resetSearch = () => { searchForm.keyword = ''; handleSearch(); }; // 分页大小变化 const handleSizeChange = (val: number) => { pageParams.pageSize = val; fetchRoomList(); }; // 当前页变化 const handleCurrentChange = (val: number) => { pageParams.pageNum = val; fetchRoomList(); }; // 新增 const handleAdd = () => { currentRow.value = {}; isEdit.value = false; dialogVisible.value = true; }; // 编辑 const handleEdit = (row: Room) => { currentRow.value = { ...row }; isEdit.value = true; dialogVisible.value = true; }; // 删除 const handleDelete = (row: Room) => { ElMessageBox.confirm(`确定要删除房间 "${row.roomNumber}" 吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(async () => { try { await roomApi.deleteRoom(row.id!); ElMessage.success('删除成功'); fetchRoomList(); } catch (error) { ElMessage.error('删除失败'); } }); }; // 对话框操作成功回调 const handleDialogSuccess = () => { dialogVisible.value = false; fetchRoomList(); }; // 状态映射(可抽离到常量文件) const statusMap = { 0: { text: '空闲', type: 'success' }, 1: { text: '已预订', type: 'warning' }, 2: { text: '入住中', type: 'primary' }, 3: { text: '维修中', type: 'danger' }, }; const getStatusText = (status: number) => statusMap[status as keyof typeof statusMap]?.text || '未知'; const statusTagType = (status: number) => statusMap[status as keyof typeof statusMap]?.type || 'info'; // 模拟获取房型名称(实际应从store或api获取) const getRoomTypeName = (id: number) => `房型${id}`; onMounted(() => { fetchRoomList(); }); </script> <style scoped> .card-header { display: flex; justify-content: space-between; align-items: center; } .pagination-wrapper { margin-top: 20px; display: flex; justify-content: flex-end; } </style>

4.4 配置路由与菜单

在src/router/index.ts中配置路由,将客房管理页面挂载到路由上,并在主布局中通过菜单导航访问。

5. 项目运行、验证与常见问题排查

完成前后端核心代码后,需要启动项目并进行功能验证。

5.1 启动后端服务

  1. 确保 MySQL 服务已启动,且hotel_management数据库已创建。
  2. 在application.yml中检查数据库连接信息是否正确。
  3. 在 IDEA 中找到主启动类HotelBackendApplication.java,右键运行。
  4. 观察控制台日志,确认没有启动错误,并看到类似Tomcat started on port(s): 8080的日志。

5.2 启动前端开发服务器

  1. 在终端中进入hotel-frontend目录。
  2. 运行pnpm dev或npm run dev。
  3. 控制台会输出本地访问地址,通常是http://localhost:5173。

5.3 功能验证流程

  1. 访问前端:在浏览器打开http://localhost:5173,应能看到登录页或主界面。
  2. 登录系统:使用预设的管理员账号(需要在后端初始化数据或通过数据库插入)登录。
  3. 导航至客房管理:点击侧边栏菜单,进入客房管理页面。
  4. 测试增删改查:
    • 查询:页面加载后应显示客房列表。
    • 新增:点击“新增客房”,填写表单并提交,列表应刷新显示新数据。
    • 编辑:点击某行数据的“编辑”按钮,修改信息后提交,列表数据应更新。
    • 删除:点击“删除”按钮,确认后该行数据应从列表中消失(后端为逻辑删除)。
  5. 检查网络请求:打开浏览器开发者工具的“网络(Network)”标签,查看每次操作发出的 API 请求和响应,确认状态码为 200,数据格式正确。

5.4 常见问题与排查路径

在联调过程中,你可能会遇到以下典型问题:

问题现象可能原因排查步骤解决方案
前端访问后端 API 报跨域错误 (CORS)浏览器安全策略阻止了不同源(域名、端口、协议)的请求。1. 查看浏览器控制台 Network 标签,请求状态为CORS error或OPTIONS 403。
2. 检查后端是否配置了 CORS。
在后端SecurityConfig或单独配置类中,添加 CORS 配置,允许前端源。
前端请求返回 401 (Unauthorized)1. 未登录或 Token 过期。
2. Token 未正确携带或格式错误。
3. 请求的接口需要特定权限。
1. 检查前端请求头Authorization是否包含Bearer <token>。
2. 检查后端 Token 解析是否失败(密钥不匹配、过期)。
3. 查看后端 Security 配置,该接口路径是否需要认证。
1. 重新登录获取新 Token。
2. 检查前后端 JWT 密钥是否一致。
3. 在 Security 配置中放行该接口或为用户添加对应权限。
前端请求返回 404 (Not Found)1. 后端 API 路径错误。
2. 后端 Controller 未正确映射或未启动。
1. 核对前端请求 URL 和后端@RequestMapping路径。
2. 检查后端启动日志,确认 Controller 已被扫描加载。
1. 修正前端请求 URL 或后端注解路径。
2. 确保主启动类包扫描范围能覆盖到 Controller。
数据库连接失败1. MySQL 服务未启动。
2. 连接 URL、用户名、密码错误。
3. 驱动版本不匹配。
1. 检查 MySQL 服务状态。
2. 核对application.yml中的连接信息。
3. 查看启动日志中的具体异常信息。
1. 启动 MySQL 服务。
2. 修正配置文件。
3. 检查pom.xml中 MySQL 驱动版本。
MyBatis-Plus 插入/更新时字段为 null1. 实体类字段名与数据库列名未正确映射(下划线转驼峰默认开启)。
2. 字段填充策略未生效。
1. 检查@TableField注解的value属性是否指定了列名。
2. 检查是否实现了MetaObjectHandler来处理字段自动填充。
1. 使用@TableField(value = "column_name")明确指定映射。
2. 创建配置类实现MetaObjectHandler接口。
前端页面样式错乱或 Element Plus 组件未渲染1. Element Plus 未正确引入或注册。
2. Vue 组件未正确导入或注册。
1. 检查main.ts中是否正确引入了 Element Plus 及其样式。
2. 检查浏览器控制台是否有 JS 错误。
1. 参照 Element Plus 官方文档,在main.ts中完成全局注册或按需导入。
2. 根据控制台错误信息修复组件导入问题。

6. 项目部署与生产环境考量

学习环境运行成功后,若想部署到服务器或用于生产演示,还需要考虑以下方面。

6.1 后端打包与部署

  1. 打包:在项目根目录执行mvn clean package -DskipTests,会在target目录生成可执行的 JAR 文件(如hotel-backend-0.0.1-SNAPSHOT.jar)。
  2. 环境配置:生产环境的数据库密码、JWT 密钥等敏感信息不应写在代码中。应使用application-prod.yml文件,并通过--spring.profiles.active=prod启动参数激活。或者使用环境变量、配置中心。
  3. 启动:在服务器上使用nohup java -jar hotel-backend-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &命令后台启动。
  4. 反向代理:通常使用 Nginx 作为反向代理,将域名请求转发到后端服务的 8080 端口,并处理 SSL 证书(HTTPS)。

6.2 前端打包与部署

  1. 打包:执行pnpm build或npm run build,会在dist目录生成静态资源文件。
  2. 部署:将dist目录下的所有文件上传到 Web 服务器(如 Nginx、Apache)的静态资源目录。
  3. Nginx 配置:配置 Nginx,将前端请求代理到后端 API,并处理 History 路由模式。
    server { listen 80; server_name your_domain.com; location / { root /path/to/your/dist; index index.html; try_files $uri $uri/ /index.html; # 支持 Vue Router 的 history 模式 } location /api/ { proxy_pass http://localhost:8080/api/; # 代理后端API proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

6.3 生产环境最佳实践建议

  • 数据库:
    • 为生产数据库创建专属用户,并授予最小必要权限。
    • 定期备份数据库。
    • 对核心表(如订单、账单)建立合适的索引以优化查询性能。
  • 安全:
    • JWT 密钥:使用强随机字符串,并定期更换。
    • API 防护:对敏感操作(如删除、支付)增加二次确认或操作日志。
    • 输入校验:前后端均需进行严格的输入校验和过滤,防止 SQL 注入和 XSS 攻击。
    • 依赖安全:定期使用mvn dependency:check或npm audit检查依赖库的安全漏洞。
  • 日志与监控:
    • 配置日志框架(如 Logback)将日志按级别输出到文件,便于排查问题。
    • 集成 Spring Boot Actuator 暴露健康检查端点,方便监控应用状态。
    • 考虑使用 Prometheus + Grafana 进行应用性能监控。
  • 性能:
    • 对频繁查询且变化不频繁的数据(如房型、城市列表)使用 Redis 缓存。
    • 前端对图片等静态资源进行压缩,并使用 CDN 加速。

通过以上步骤,你不仅完成了一个可运行的酒店管理系统,更实践了从技术选型、环境搭建、模块开发、前后端联调到部署上线的完整流程。这个项目可以作为你深入理解 Spring Boot 和 Vue 3 全栈开发的坚实基础,你可以在此基础上继续扩展功能,如集成工作流引擎处理复杂的订单状态流转,或加入更精细的权限管理模型(如 RBAC),使其更贴近真实的商业系统。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询