在实际 Java Web 项目开发中,一个完整的、前后端分离的管理系统是检验技术综合运用能力的绝佳场景。酒店客房管理系统正是这样一个典型项目,它涵盖了用户管理、客房信息维护、订单处理、数据统计等核心业务模块,非常适合用于学习 Spring Boot 后端开发与 Vue 3 前端框架的集成。很多开发者拿到源码后,往往卡在环境配置、依赖版本、前后端联调等环节,导致项目无法成功运行。本文将围绕一个基于 Spring Boot 和 Vue 3 的酒店客房管理系统,从零开始,详细拆解其搭建过程、核心代码逻辑、常见问题排查以及生产环境部署的注意事项,目标是让你不仅能成功运行项目,更能理解其背后的设计思路和工程实践。
1. 理解项目架构与技术选型
在动手搭建之前,必须先理清整个项目的技术栈和架构设计。这决定了后续环境准备、代码编写和问题排查的方向。
1.1 为什么选择 Spring Boot + Vue 3
Spring Boot 以其“约定大于配置”的理念,极大地简化了传统 Spring 应用的初始搭建和开发过程。它内嵌了 Tomcat 等 Web 服务器,无需部署 WAR 包,通过一个main方法就能启动一个独立的、生产级别的应用。对于酒店客房管理系统这类需要快速迭代、包含复杂业务逻辑的后台服务,Spring Boot 提供了完善的 Web、数据访问、安全、缓存等模块支持。
Vue 3 作为现代前端框架,其组合式 API 提供了更好的逻辑复用和类型推断能力,配合 TypeScript 使用,能构建出更健壮、可维护性更高的前端应用。前后端分离的架构使得后端可以专注于提供 RESTful API,前端则独立开发、部署,两者通过 HTTP 协议进行数据交互,职责清晰,便于团队协作。
1.2 项目核心模块划分
一个典型的酒店客房管理系统通常包含以下模块:
- 后端 (Spring Boot):
- 实体层 (Entity/Model):对应数据库表,如
User、Room、Order。 - 数据访问层 (Repository/DAO):使用 Spring Data JPA 或 MyBatis-Plus 进行数据库操作。
- 业务逻辑层 (Service):封装核心业务规则,如客房预订逻辑、价格计算。
- 控制层 (Controller):接收前端 HTTP 请求,调用 Service,返回 JSON 数据。
- 配置与工具类:数据库连接、跨域配置、权限拦截、工具函数等。
- 实体层 (Entity/Model):对应数据库表,如
- 前端 (Vue 3):
- 视图组件 (Components):如登录页、客房列表页、订单表单页。
- 状态管理 (Pinia):集中管理用户登录状态、客房数据等应用级状态。
- 路由管理 (Vue Router):定义页面路径与组件的映射关系。
- HTTP 客户端 (Axios):封装与后端 API 的通信。
- UI 组件库:如 Element Plus,用于快速构建界面。
1.3 开发环境与工具准备
在开始编码前,请确保你的本地开发环境已就绪。以下是推荐的环境清单:
| 工具/环境 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 11 或 17 | Spring Boot 3.x 需要 JDK 17+,Spring Boot 2.x 兼容 JDK 8/11。建议使用 JDK 17 以获得长期支持。 |
| Maven | 3.6+ | 用于管理 Java 项目依赖、构建和打包。 |
| Node.js | 18+ | Vue 3 的运行和构建环境。同时会安装 npm 包管理器。 |
| IDE | IntelliJ IDEA / VS Code | IDEA 用于后端 Java 开发,VS Code 用于前端 Vue 开发。 |
| 数据库 | MySQL 5.7+ / 8.0 | 项目的主要数据存储。也可根据项目源码适配 PostgreSQL。 |
| Git | 最新版 | 用于版本控制和获取项目源码。 |
注意:版本兼容性是项目能否成功启动的关键。如果项目源码未明确说明,建议优先使用上述推荐版本,避免因版本过高或过低导致依赖冲突。
2. 后端 Spring Boot 项目搭建与配置
我们将从创建一个标准的 Spring Boot 项目开始,逐步集成必要的依赖和配置。
2.1 使用 Spring Initializr 初始化项目
最便捷的方式是使用 start.spring.io 或 IDE 内置的 Spring Initializr 创建项目。
选择参数:
- Project: Maven Project
- Language: Java
- Spring Boot: 选择稳定版本(如 3.1.x 或 2.7.x,需与源码匹配)
- Project Metadata:
Group:com.example(可改为你的组织名,如com.hotel)Artifact:hotel-managementName:hotel-managementPackage name:com.example.hotelmanagement
- Packaging: Jar
- Java: 17
添加依赖:根据酒店管理系统的需求,通常需要添加以下依赖:
- Spring Web: 构建 RESTful API。
- Spring Data JPA: 简化数据库操作。
- MySQL Driver: 连接 MySQL 数据库。
- Lombok: 通过注解减少 getter/setter 等样板代码。
- Spring Boot DevTools: 开发时热加载。
- Validation: 参数校验。
生成项目后,使用 IDE 导入 Maven 项目。
2.2 核心配置文件详解
项目根目录下的application.properties或application.yml是 Spring Boot 的核心配置文件。对于酒店管理系统,典型配置如下:
# application.yml server: port: 8080 # 后端服务端口 spring: application: name: hotel-management datasource: url: jdbc:mysql://localhost:3306/hotel_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发环境可用 update,生产环境务必改为 validate 或 none show-sql: true # 开发时显示 SQL 语句 properties: hibernate: format_sql: true # 格式化输出的 SQL jackson: time-zone: GMT+8 # 处理 JSON 时间序列化时的时区问题 date-format: yyyy-MM-dd HH:mm:ss # 自定义配置,如 JWT 密钥(生产环境应从环境变量或配置中心读取) jwt: secret: your-jwt-secret-key-here-must-be-very-long-and-secure expiration: 86400000 # token 过期时间(毫秒),例如 24 小时关键配置解释:
spring.datasource.url: 指定数据库连接地址和参数。serverTimezone=Asia/Shanghai是解决中文时区问题的关键。spring.jpa.hibernate.ddl-auto:update模式会在应用启动时根据实体类自动更新表结构,适合开发。生产环境必须禁用此功能,应使用validate仅校验,或通过 Flyway/Liquibase 管理数据库变更。spring.jpa.show-sql: 开发时开启,便于调试 SQL。jwt.secret: 用于生成和验证 JWT Token 的密钥,必须足够复杂且保密。
2.3 数据库设计与实体类映射
假设系统需要用户表、客房表、订单表。以下以Room(客房)实体为例:
// src/main/java/com/example/hotelmanagement/entity/Room.java package com.example.hotelmanagement.entity; import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; @Entity @Table(name = "room") @Data // Lombok 注解,自动生成 getter, setter, toString, equals, hashCode public class Room { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String roomNumber; // 房号,如 101 @Column(nullable = false) private String type; // 房型,如 “标准间”、“豪华套房” @Column(nullable = false) private BigDecimal price; // 价格,使用 BigDecimal 避免精度丢失 @Column(nullable = false) private String status; // 状态,如 “空闲”、“已预订”、“入住中”、“维修中” private String description; // 客房描述 private String imageUrl; // 客房图片链接 // 省略构造方法,Lombok @Data 会生成 }关键点:
@Entity和@Table将类映射到数据库表。@Id和@GeneratedValue定义主键及其生成策略。@Column定义字段属性,如是否可为空、是否唯一。- 金额字段使用
BigDecimal,这是金融计算的最佳实践,避免使用float或double导致的精度问题。 - 使用
Lombok的@Data注解可以极大减少样板代码。
2.4 实现数据访问层与业务逻辑层
1. 仓库接口 (Repository): Spring Data JPA 只需定义接口,无需实现。
// src/main/java/com/example/hotelmanagement/repository/RoomRepository.java package com.example.hotelmanagement.repository; import com.example.hotelmanagement.entity.Room; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface RoomRepository extends JpaRepository<Room, Long> { // 根据房号查询 Room findByRoomNumber(String roomNumber); // 根据状态查询客房列表 List<Room> findByStatus(String status); // 根据房型和状态查询 List<Room> findByTypeAndStatus(String type, String status); }2. 服务层 (Service): 服务层封装业务逻辑,是系统的核心。
// src/main/java/com/example/hotelmanagement/service/RoomService.java package com.example.hotelmanagement.service; import com.example.hotelmanagement.entity.Room; import com.example.hotelmanagement.repository.RoomRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service public class RoomService { @Autowired private RoomRepository roomRepository; public List<Room> findAll() { return roomRepository.findAll(); } public Optional<Room> findById(Long id) { return roomRepository.findById(id); } @Transactional // 确保保存操作在事务中执行 public Room saveOrUpdate(Room room) { // 这里可以加入业务校验,例如房号是否已存在 if (room.getId() == null) { Room existing = roomRepository.findByRoomNumber(room.getRoomNumber()); if (existing != null) { throw new RuntimeException("房号 " + room.getRoomNumber() + " 已存在"); } } return roomRepository.save(room); } @Transactional public void deleteById(Long id) { // 删除前可检查是否有关联订单等业务规则 roomRepository.deleteById(id); } public List<Room> findAvailableRooms() { return roomRepository.findByStatus("空闲"); } }3. 控制层 (Controller): 控制层负责接收 HTTP 请求,调用服务,并返回 JSON 响应。
// src/main/java/com/example/hotelmanagement/controller/RoomController.java package com.example.hotelmanagement.controller; import com.example.hotelmanagement.entity.Room; import com.example.hotelmanagement.service.RoomService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; @RestController @RequestMapping("/api/rooms") // API 路径前缀 public class RoomController { @Autowired private RoomService roomService; @GetMapping public ResponseEntity<List<Room>> getAllRooms() { return ResponseEntity.ok(roomService.findAll()); } @GetMapping("/{id}") public ResponseEntity<Room> getRoomById(@PathVariable Long id) { return roomService.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @PostMapping public ResponseEntity<Room> createRoom(@Valid @RequestBody Room room) { // @Valid 会触发实体类上的校验注解(如@NotBlank) Room savedRoom = roomService.saveOrUpdate(room); return ResponseEntity.ok(savedRoom); } @PutMapping("/{id}") public ResponseEntity<Room> updateRoom(@PathVariable Long id, @Valid @RequestBody Room room) { if (!roomService.findById(id).isPresent()) { return ResponseEntity.notFound().build(); } room.setId(id); // 确保更新的是指定ID的对象 Room updatedRoom = roomService.saveOrUpdate(room); return ResponseEntity.ok(updatedRoom); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteRoom(@PathVariable Long id) { if (!roomService.findById(id).isPresent()) { return ResponseEntity.notFound().build(); } roomService.deleteById(id); return ResponseEntity.noContent().build(); } @GetMapping("/available") public ResponseEntity<List<Room>> getAvailableRooms() { return ResponseEntity.ok(roomService.findAvailableRooms()); } }至此,一个具备 CRUD 功能的客房管理后端 API 就完成了。你可以通过http://localhost:8080/api/rooms来测试接口。
3. 前端 Vue 3 项目搭建与页面开发
后端 API 就绪后,我们开始构建前端用户界面。
3.1 使用 Vite 创建 Vue 3 项目
Vite 是现在 Vue 项目推荐的构建工具,速度远快于传统的 Webpack。
# 在合适目录下执行 npm create vue@latest hotel-management-frontend按照提示选择项目特性:推荐添加TypeScript、Vue Router、Pinia、ESLint。 创建完成后,进入项目并安装依赖:
cd hotel-management-frontend npm install # 安装 UI 库和 HTTP 客户端 npm install element-plus axios npm install --save-dev @types/node # 解决 TypeScript 路径别名问题3.2 配置 Axios 与 API 封装
为了统一管理 API 请求,我们创建一个axios实例并封装请求拦截器(用于添加 Token)和响应拦截器(用于统一处理错误)。
// src/utils/request.ts import axios from 'axios'; import { ElMessage } from 'element-plus'; import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import { useUserStore } from '@/stores/user'; // 假设使用 Pinia 管理用户状态 const service: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api', // 从环境变量读取 timeout: 10000, // 请求超时时间 }); // 请求拦截器 service.interceptors.request.use( (config: AxiosRequestConfig) => { const userStore = useUserStore(); if (userStore.token) { // 如果存在 token,则在请求头中携带 config.headers = config.headers || {}; config.headers['Authorization'] = `Bearer ${userStore.token}`; } return config; }, (error) => { console.error('Request Error:', error); return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( (response: AxiosResponse) => { const res = response.data; // 这里根据后端统一的响应格式进行调整,例如 { code: 200, data: {}, message: 'success' } if (res.code && res.code !== 200) { ElMessage.error(res.message || 'Error'); return Promise.reject(new Error(res.message || 'Error')); } return res; // 直接返回 data 部分 }, (error) => { console.error('Response Error:', error); if (error.response) { // 有响应但状态码不在 2xx 范围 switch (error.response.status) { case 401: ElMessage.error('未授权,请重新登录'); // 清除 token 并跳转到登录页 const userStore = useUserStore(); userStore.logout(); window.location.href = '/login'; break; case 403: ElMessage.error('拒绝访问'); break; case 404: ElMessage.error('请求的资源不存在'); break; case 500: ElMessage.error('服务器内部错误'); break; default: ElMessage.error(error.response.data?.message || `请求失败: ${error.response.status}`); } } else if (error.request) { // 请求已发出但没有收到响应 ElMessage.error('网络错误,请检查网络连接'); } else { // 请求配置出错 ElMessage.error('请求配置错误'); } return Promise.reject(error); } ); export default service;然后在.env.development和.env.production中配置后端 API 地址:
# .env.development VITE_API_BASE_URL=http://localhost:8080/api3.3 实现客房列表页面
使用 Element Plus 的表格组件来展示客房列表。
<!-- src/views/RoomListView.vue --> <template> <div class="room-list"> <div class="header"> <h2>客房管理</h2> <el-button type="primary" @click="handleCreate">新增客房</el-button> </div> <el-table :data="roomList" border style="width: 100%"> <el-table-column prop="roomNumber" label="房号" width="120" /> <el-table-column prop="type" label="房型" width="150" /> <el-table-column prop="price" label="价格(元)" width="120"> <template #default="scope"> {{ scope.row.price.toFixed(2) }} </template> </el-table-column> <el-table-column prop="status" label="状态" width="100"> <template #default="scope"> <el-tag :type="getStatusTagType(scope.row.status)"> {{ scope.row.status }} </el-tag> </template> </el-table-column> <el-table-column prop="description" label="描述" /> <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.id)">删除</el-button> </template> </el-table-column> </el-table> <!-- 新增/编辑对话框 --> <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px"> <el-form :model="form" :rules="rules" ref="formRef" label-width="80px"> <el-form-item label="房号" prop="roomNumber"> <el-input v-model="form.roomNumber" placeholder="请输入房号" /> </el-form-item> <el-form-item label="房型" prop="type"> <el-select v-model="form.type" placeholder="请选择房型"> <el-option label="标准间" value="标准间" /> <el-option label="大床房" value="大床房" /> <el-option label="豪华套房" value="豪华套房" /> </el-select> </el-form-item> <el-form-item label="价格" prop="price"> <el-input-number v-model="form.price" :min="0" :precision="2" :step="10" /> </el-form-item> <el-form-item label="状态" prop="status"> <el-select v-model="form.status" placeholder="请选择状态"> <el-option label="空闲" value="空闲" /> <el-option label="已预订" value="已预订" /> <el-option label="入住中" value="入住中" /> <el-option label="维修中" value="维修中" /> </el-select> </el-form-item> <el-form-item label="描述" prop="description"> <el-input v-model="form.description" type="textarea" :rows="3" /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="submitForm">确认</el-button> </span> </template> </el-dialog> </div> </template> <script setup lang="ts"> import { ref, reactive, onMounted } from 'vue'; import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'; import request from '@/utils/request'; import type { Room } from '@/types/room'; // 定义好的 TypeScript 类型 const roomList = ref<Room[]>([]); const dialogVisible = ref(false); const dialogTitle = ref(''); const formRef = ref<FormInstance>(); const form = reactive<Partial<Room>>({ roomNumber: '', type: '', price: 0, status: '空闲', description: '', }); const rules: FormRules = { roomNumber: [{ required: true, message: '请输入房号', trigger: 'blur' }], type: [{ required: true, message: '请选择房型', trigger: 'change' }], price: [{ required: true, message: '请输入价格', trigger: 'blur' }], status: [{ required: true, message: '请选择状态', trigger: 'change' }], }; // 获取客房列表 const fetchRooms = async () => { try { const response = await request.get('/rooms'); roomList.value = response.data || response; // 根据后端返回格式调整 } catch (error) { console.error('获取客房列表失败:', error); } }; // 状态标签样式 const getStatusTagType = (status: string) => { const map: Record<string, 'success' | 'info' | 'warning' | 'danger'> = { '空闲': 'success', '已预订': 'warning', '入住中': 'info', '维修中': 'danger', }; return map[status] || 'info'; }; // 打开新增对话框 const handleCreate = () => { dialogTitle.value = '新增客房'; Object.keys(form).forEach(key => { (form as any)[key] = ''; }); form.status = '空闲'; form.price = 0; dialogVisible.value = true; }; // 打开编辑对话框 const handleEdit = (row: Room) => { dialogTitle.value = '编辑客房'; Object.assign(form, row); dialogVisible.value = true; }; // 提交表单(新增/编辑) const submitForm = async () => { if (!formRef.value) return; await formRef.value.validate(async (valid) => { if (valid) { try { const url = form.id ? `/rooms/${form.id}` : '/rooms'; const method = form.id ? 'put' : 'post'; await request({ url, method, data: form }); ElMessage.success(form.id ? '更新成功' : '创建成功'); dialogVisible.value = false; fetchRooms(); // 刷新列表 } catch (error) { console.error('操作失败:', error); } } }); }; // 删除客房 const handleDelete = (id: number) => { ElMessageBox.confirm('确定要删除这条客房信息吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(async () => { try { await request.delete(`/rooms/${id}`); ElMessage.success('删除成功'); fetchRooms(); } catch (error) { console.error('删除失败:', error); } }).catch(() => {}); }; onMounted(() => { fetchRooms(); }); </script> <style scoped> .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } </style>这个页面实现了客房的增删改查功能,并与后端 API 进行了对接。通过类似的模式,可以继续开发用户管理、订单管理等其他页面。
4. 前后端联调与项目运行
代码编写完成后,最关键的一步是让前后端协同工作。
4.1 启动后端服务
- 确保 MySQL 服务已启动,并创建了配置文件中的数据库(如
hotel_db)。 - 在 IDE 中找到
HotelManagementApplication主类,直接运行main方法。 - 观察控制台日志,确认没有错误,并看到类似
Tomcat started on port(s): 8080的日志。 - 打开浏览器,访问
http://localhost:8080/api/rooms,如果返回空数组[]或提示Whitelabel Error Page(因为没数据),说明后端 API 服务已正常启动。可以通过 Postman 等工具测试 POST、PUT 等接口。
4.2 启动前端开发服务器
- 在终端中进入前端项目根目录 (
hotel-management-frontend)。 - 运行
npm run dev。 - 控制台会输出本地开发服务器地址,通常是
http://localhost:5173。 - 打开该地址,应该能看到 Vue 应用的默认页面。
4.3 解决跨域问题
在开发阶段,前端运行在localhost:5173,后端运行在localhost:8080,端口不同,浏览器会因同源策略而阻止请求。有两种主流解决方案:
方案一:后端配置 CORS(推荐)在 Spring Boot 后端添加一个全局 CORS 配置类。
// src/main/java/com/example/hotelmanagement/config/WebConfig.java package com.example.hotelmanagement.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") // 允许跨域的路径 .allowedOrigins("http://localhost:5173") // 允许的前端地址 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的请求方法 .allowedHeaders("*") // 允许的请求头 .allowCredentials(true); // 允许携带凭证(如 Cookie) } }; } }方案二:前端代理(Vite 配置)在前端项目的vite.config.ts中配置代理,将 API 请求转发到后端。
// vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true, // rewrite: (path) => path.replace(/^\/api/, '') // 如果后端路径没有 /api 前缀,可以重写 } } } })配置后,前端代码中请求/api/rooms会被 Vite 开发服务器代理到http://localhost:8080/api/rooms,从而避免跨域。
注意:方案一更接近生产环境,因为生产环境前后端可能部署在同一域名下或由 Nginx 反向代理。方案二仅用于开发环境。
4.4 功能验证流程
- 登录/认证:如果系统有登录功能,先完成登录流程,确保 Token 能被正确存储和携带。
- 数据列表:访问客房列表页,确认能成功从后端获取并展示数据。
- 数据操作:尝试新增、编辑、删除一条客房记录,观察前端提示和后端数据库变化是否一致。
- 表单验证:测试表单的必填项、格式校验(如价格不能为负数)是否生效。
- 错误处理:尝试触发一些错误(如删除不存在的记录),查看前端拦截器是否能正确捕获并显示错误信息。
5. 常见问题排查与解决方案
在搭建和运行过程中,你可能会遇到以下典型问题。
5.1 后端启动失败
| 问题现象 | 可能原因 | 检查与解决 |
|---|---|---|
APPLICATION FAILED TO START | 1. 数据库连接失败。 2. 端口被占用。 3. 依赖冲突或缺失。 | 1. 检查application.yml中的数据库 URL、用户名、密码,确认 MySQL 服务已启动。2. 检查 8080 端口是否被其他程序占用,可修改 server.port。3. 运行 mvn clean compile检查依赖,或尝试删除本地 Maven 仓库(~/.m2/repository)重新下载。 |
BeanCreationException | 1. Bean 注入失败。 2. 配置类扫描路径问题。 | 1. 检查@Service,@Repository,@Autowired注解使用是否正确,包路径是否在@SpringBootApplication主类所在包或其子包下。2. 确认 @ComponentScan注解(如有)配置正确。 |
JPA相关错误 | 1. 实体类与数据库表映射错误。 2. ddl-auto策略导致。 | 1. 检查实体类字段名、类型与数据库表是否匹配。 2. 开发环境可暂时设为 create-drop或update,但生产环境必须改为validate或none,并通过 SQL 脚本管理表结构。 |
5.2 前端编译或运行错误
| 问题现象 | 可能原因 | 检查与解决 |
|---|---|---|
npm install失败 | 1. 网络问题。 2. Node.js 版本不兼容。 3. package-lock.json冲突。 | 1. 切换 npm 镜像源:npm config set registry https://registry.npmmirror.com。2. 使用 nvm管理 Node.js 版本,确保版本符合要求。3. 删除 node_modules和package-lock.json,重新执行npm install。 |
Vue组件无法导入 | 1. 路径别名@未配置。2. TypeScript 类型声明缺失。 | 1. 检查vite.config.ts和tsconfig.json中的paths配置。2. 为第三方库安装类型声明包,如 @types/node。 |
页面空白或控制台报404 | 1. 路由配置错误。 2. 静态资源路径错误。 3. 代理配置未生效。 | 1. 检查router/index.ts中的路由定义和组件导入。2. 使用 import.meta.env.BASE_URL处理基础路径。3. 确认 vite.config.ts中的代理配置正确,并重启开发服务器。 |
5.3 前后端联调问题
| 问题现象 | 可能原因 | 检查与解决 |
|---|---|---|
前端请求后端返回404 | 1. 后端 API 路径错误。 2. 后端 Controller 未正确映射。 3. 跨域请求被浏览器拦截。 | 1. 核对前端请求的 URL 和后端@RequestMapping定义的路径是否完全一致(包括大小写)。2. 在后端控制台查看映射日志,或访问 /actuator/mappings(需引入 actuator 依赖)查看所有端点。3. 按4.3节配置 CORS 或代理。 |
前端请求返回401/403 | 1. 未登录或 Token 过期。 2. Token 未正确携带或格式错误。 3. 后端权限拦截器配置问题。 | 1. 检查登录流程,确保成功获取 Token 并存储(如 localStorage 或 Pinia)。 2. 检查 Axios 请求拦截器是否正确在请求头中添加了 Authorization: Bearer <token>。3. 检查后端 Security 或自定义拦截器的配置,确保对应接口路径未被错误拦截。 |
| 请求成功但数据不显示 | 1. 前端组件数据绑定错误。 2. 后端返回的数据结构与前端期望不符。 3. 控制台有 JavaScript 错误。 | 1. 使用 Vue Devtools 检查组件的数据和状态。 2. 在浏览器开发者工具的 Network 面板查看 API 返回的实际 JSON 结构,并与前端代码中定义的 TypeScript 接口或变量进行比对。 3. 解决控制台报错,可能是未定义的变量或方法。 |
6. 项目打包与生产环境部署建议
本地开发完成后,需要将项目部署到服务器。
6.1 后端打包与运行
- 打包:在项目根目录执行
mvn clean package -DskipTests。成功后会在target目录生成hotel-management-0.0.1-SNAPSHOT.jar。 - 运行:将 JAR 包上传至服务器,使用命令运行:
需要提前在服务器安装好对应版本的 JDK。# 最简单方式(前台运行,关闭终端即停止) java -jar hotel-management-0.0.1-SNAPSHOT.jar # 后台运行,并将日志输出到文件 nohup java -jar hotel-management-0.0.1-SNAPSHOT.jar > app.log 2>&1 & # 指定配置文件(生产环境推荐) java -jar hotel-management-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
6.2 前端打包与部署
- 打包:在前端项目根目录执行
npm run build。产物会生成在dist目录。 - 部署:将
dist目录下的所有文件,放到任何静态文件服务器(如 Nginx、Apache)或对象存储(如 OSS)上。 - Nginx 配置示例(同时代理前端静态文件和后端 API):
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 模式 } # 反向代理后端 API location /api/ { proxy_pass http://localhost:8080; # 后端服务地址 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
6.3 生产环境关键检查清单
将项目部署到生产环境前,务必完成以下检查:
- 数据库:
- [ ] 使用独立的、高权限的生产数据库账号,而非
root。 - [ ] 关闭 JPA 的
ddl-auto(设为validate或none),使用 Flyway/Liquibase 管理数据库变更。 - [ ] 对敏感配置(如数据库密码、JWT 密钥)进行加密或从环境变量读取。
- [ ] 使用独立的、高权限的生产数据库账号,而非
- 应用配置:
- [ ] 创建
application-prod.yml,配置生产环境的数据库、日志级别、端口等。 - [ ] 确保日志已配置并输出到文件,便于排查问题。
- [ ] 关闭开发工具(如
spring-boot-devtools)和调试信息。
- [ ] 创建
- 安全:
- [ ] 实现完整的用户认证与授权(如使用 Spring Security + JWT)。
- [ ] 对用户输入进行严格的校验和过滤,防止 SQL 注入和 XSS 攻击。
- [ ] API 接口根据需要实施限流和防刷策略。
- 运维:
- [ ] 使用进程管理工具(如 systemd, Supervisor)来管理 JAR 包进程,实现开机自启和自动重启。
- [ ] 设置监控和告警,关注应用 CPU、内存、磁盘和关键接口响应时间。
- [ ] 制定数据库备份策略。
通过以上步骤,你不仅能够成功搭建并运行一个 Spring Boot + Vue 3 的酒店客房管理系统,更能掌握全栈开发的核心流程、联调技巧和部署要点。这个项目可以作为深入学习微服务、分布式缓存、消息队列等更高级技术的基础框架。