news 2026/9/2 7:55:33

SpringBoot+Vue3酒店管理系统:从零构建全栈开发认知框架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue3酒店管理系统:从零构建全栈开发认知框架

最近在帮几个学生看毕业设计项目,发现一个挺有意思的现象:很多人一上来就想找个“功能最全、技术栈最新”的管理系统源码,然后直接开改。结果往往是,项目跑起来了,但问到“为什么这里用这个注解”、“前后端数据怎么流转的”、“这个异常该怎么处理”,就卡壳了。项目成了简历上的一个“黑盒”,经不起任何追问。

这让我想起一个更普遍的问题:我们花时间“搭建”一个项目,到底是为了得到一个能运行的成品,还是为了理解一套能复用于未来工作的工程方法?今天,我们就以这个经典的“SpringBoot + Vue3 酒店管理系统”为例,来聊聊后者。我会带你走一遍从零开始的完整流程,但重点不在“敲完代码”,而在于把一次性的项目搭建,沉淀成一套可理解、可调试、可复用的全栈开发认知框架。两小时搭建是目标,但过程里的每一个选择、每一个配置、每一个报错的处理,才是真正值钱的经验。

1. 为什么“酒店管理系统”是理解全栈开发的绝佳样本?

你可能觉得酒店管理系统有点“老套”,不如电商、社交平台听起来酷。但恰恰是这种业务逻辑相对清晰、实体关系典型的项目,最适合用来剥离技术噪音,聚焦全栈的核心链路。它不像电商涉及复杂的秒杀、分布式事务,也不像社交平台有海量实时消息,但它完整覆盖了现代Web应用的基本要素:用户鉴权、数据增删改查、前后端分离、数据可视化、文件处理

更重要的是,它的业务模型(客房、订单、客户)映射到技术实现上,能让你清晰地看到:

  • 领域对象如何设计:一个Room实体包含哪些字段?状态(空闲、已预订、入住中)如何用枚举优雅地表示?
  • API设计如何反映业务:预订房间是POST /api/orders,查询空房是GET /api/rooms/available。URL和HTTP方法不是随便定的,它们本身就是业务语义的一部分。
  • 前后端职责如何划分:前端Vue3组件负责渲染客房列表、收集表单数据;后端SpringBoot Controller负责校验请求、调用Service处理业务逻辑、与数据库交互。这个边界在哪里,为什么这么划?

所以,我们的目标不是复制一个酒店管理系统,而是通过构建它,掌握一种“从业务需求推导出技术实现”的通用方法。这套方法,你下次做图书管理系统、设备管理系统、甚至内容管理平台,都能直接套用。

2. 环境准备与项目初始化:别在起点踩坑

很多教程会直接说“安装JDK、Node.js、Maven、IDEA、VSCode”,然后一笔带过。但这里恰恰是第一个分水岭:版本兼容性问题往往在这里埋下伏笔。

2.1 后端 (SpringBoot) 环境清单与版本锁定

SpringBoot的版本选择不是越新越好,而是要形成一个稳定的技术栈组合。对于学习项目,我建议选择一个经过广泛验证的、文档丰富的版本。

# 这是一个建议的稳定组合,请在 pom.xml 中锁定 spring-boot-starter-parent: 2.7.x 或 3.0.x (选一个并坚持) # 为什么是2.7或3.0?2.7是2.x的终结版本,极其稳定;3.0是新一代起点,支持JDK17+,但部分旧库可能未适配。 java: 11 (对应SpringBoot 2.7) 或 17+ (对应SpringBoot 3.0+) maven: 3.6+ 数据库: MySQL 8.0 或 PostgreSQL 14+ (建议用Docker快速拉起,避免污染本地环境) IDE: IntelliJ IDEA (社区版即可,对Spring支持最好)

关键动作:不要直接用IDE的Spring Initializr生成完就了事。打开生成的pom.xml,检查并理解几个核心依赖:

  • spring-boot-starter-web: 提供了嵌入式Tomcat和Spring MVC,这是Web应用的基石。
  • spring-boot-starter-data-jpa: 简化数据库操作。这里会引入Hibernate。
  • mysql-connector-javapostgresql: 数据库驱动。
  • spring-boot-starter-test: 单元测试支持。
  • lombok: 可选但强烈推荐。用注解(@Data,@Getter)替代冗长的getter/setter,让实体类更清晰。

注意:如果你看到spring-boot-starter-security,先注释掉。一上来就搞安全配置,会凭空增加复杂度。我们先把核心业务流程跑通,再考虑加锁。

2.2 前端 (Vue3) 环境与工具链选择

前端生态迭代快,工具链选择比后端更让人眼花缭乱。我们的原则是:用官方推荐、社区主流、能最小化配置负担的工具

# 1. 安装Node.js (版本请与后端Java版本一样重视) # 建议使用LTS版本,如18.x或20.x。安装后验证: node -v npm -v # 2. 使用Vite创建项目,它比传统的Vue CLI更快、更现代。 npm create vue@latest # 执行上述命令后,你会进入一个交互式命令行。对于学习项目,我的建议是: # - 项目名: hotel-management-frontend # - TypeScript: **强烈建议选择 Yes**。虽然初期有点门槛,但它能提供更好的类型安全和开发体验,是现代前端开发的趋势。 # - JSX: No (除非你熟悉React) # - Vue Router: Yes (单页应用必需) # - Pinia: Yes (替代Vuex的状态管理库,更简单) # - ESLint: Yes (代码规范) # - Prettier: Yes (代码格式化)

创建完成后,用VSCode打开项目。花5分钟浏览一下目录结构:

  • src/views/: 存放页面组件(如HomeView.vue,RoomManagement.vue
  • src/components/: 存放可复用的展示组件(如RoomCard.vue,BookingForm.vue
  • src/router/index.ts: 路由配置,定义了URL路径和组件的映射关系。
  • src/stores/: Pinia状态管理,用于跨组件共享数据(如用户登录状态)。
  • src/apis/: 我们待会要创建的目录,用于集中管理所有向后端发送的HTTP请求。

为什么强调TypeScript和Pinia?因为它们是Vue3生态里提升项目可维护性的关键。TypeScript能在编码阶段就发现潜在的类型错误,Pinia让状态管理变得清晰可控,避免在组件间用emitprops传来传去导致数据流混乱。

3. 后端核心:用SpringBoot构建稳健的API服务

后端不是一堆ControllerServiceRepository的简单堆砌。每一层都有明确的职责,形成一个清晰的“请求处理流水线”。

3.1 领域模型设计:从数据库表到Java实体

不要一上来就建表。先想清楚业务核心对象和它们之间的关系。对于酒店系统:

  • 用户 (User): 系统使用者(管理员、前台)。
  • 客房 (Room): 有房型、价格、状态等属性。
  • 客户 (Customer): 住客信息。
  • 订单 (BookingOrder): 连接客户、客房、时间、状态的核心业务实体。

我们用JPA注解来把对象映射到数据库表。以Room实体为例:

// src/main/java/com/yourdomain/hotel/entity/Room.java package com.yourdomain.hotel.entity; import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; @Entity @Data // Lombok注解,自动生成getter, setter, toString等 @Table(name = "room") // 指定表名 public class Room { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增 private Long id; @Column(nullable = false, unique = true) private String roomNumber; // 房号,如 "101" @Enumerated(EnumType.STRING) // 将枚举按字符串存储 @Column(nullable = false) private RoomType type; // 枚举:SINGLE, DOUBLE, SUITE @Column(nullable = false) private BigDecimal pricePerNight; // 用BigDecimal处理金额,避免精度问题 @Enumerated(EnumType.STRING) @Column(nullable = false) private RoomStatus status = RoomStatus.AVAILABLE; // 状态:AVAILABLE, BOOKED, OCCUPIED, MAINTENANCE private String description; } // 枚举定义在同一文件或单独文件中 public enum RoomType { SINGLE, DOUBLE, SUITE } public enum RoomStatus { AVAILABLE, BOOKED, OCCUPIED, MAINTENANCE }

设计要点

  1. 使用包装类型Long id而不是long id,因为null可以表示未持久化的新对象。
  2. 金额用BigDecimal:永远不要用floatdouble表示金额,会有精度损失。
  3. 状态用枚举:比直接用字符串(如"available")更安全、更易维护。
  4. Lombok是帮手@Data让代码简洁,但你要知道它背后生成了什么。

3.2 数据访问层:Repository不是简单的DAO

Spring Data JPA的Repository接口威力巨大。你只需要定义一个接口,Spring就会在运行时为你实现基本的CRUD操作。

// src/main/java/com/yourdomain/hotel/repository/RoomRepository.java package com.yourdomain.hotel.repository; import com.yourdomain.hotel.entity.Room; import com.yourdomain.hotel.entity.RoomStatus; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.time.LocalDate; import java.util.List; public interface RoomRepository extends JpaRepository<Room, Long> { // 方法名即查询!Spring Data JPA会根据方法名自动生成SQL。 List<Room> findByStatus(RoomStatus status); // 更复杂的查询,可以使用@Query注解写JPQL(类似面向对象的SQL) @Query("SELECT r FROM Room r WHERE r.type = :type AND r.status = 'AVAILABLE' AND r.id NOT IN " + "(SELECT b.room.id FROM BookingOrder b WHERE b.checkInDate <= :endDate AND b.checkOutDate >= :startDate)") List<Room> findAvailableRoomsByTypeAndDateRange( @Param("type") RoomType type, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate ); }

理解这一层Repository是连接实体和数据库的桥梁。你声明“想要什么”(方法签名),JPA提供“如何获取”(实现)。这比手写JDBCMyBatis的XML要简洁安全得多,但也要求你对方法命名约定和JPQL有一定了解。

3.3 业务逻辑层:Service是业务规则的守护者

Controller应该尽可能“薄”,只处理HTTP协议相关的事情(参数绑定、格式转换)。真正的业务逻辑,比如“预订房间时要检查房间是否可用、计算总价、生成订单号”,应该放在Service层。

// src/main/java/com/yourdomain/hotel/service/BookingService.java package com.yourdomain.hotel.service; import com.yourdomain.hotel.entity.BookingOrder; import com.yourdomain.hotel.entity.Room; import com.yourdomain.hotel.entity.RoomStatus; import com.yourdomain.hotel.repository.BookingOrderRepository; import com.yourdomain.hotel.repository.RoomRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.temporal.ChronoUnit; @Service @RequiredArgsConstructor // Lombok注解,为final字段生成构造函数,实现依赖注入 public class BookingService { private final RoomRepository roomRepository; private final BookingOrderRepository bookingOrderRepository; @Transactional // 这是一个事务方法!如果中途失败,所有数据库操作会回滚。 public BookingOrder createBooking(Long roomId, LocalDate checkIn, LocalDate checkOut, Customer customer) { // 1. 业务校验 Room room = roomRepository.findById(roomId) .orElseThrow(() -> new RuntimeException("房间不存在")); if (room.getStatus() != RoomStatus.AVAILABLE) { throw new RuntimeException("房间当前不可用"); } if (checkIn.isAfter(checkOut) || checkIn.isBefore(LocalDate.now())) { throw new RuntimeException("日期无效"); } // 2. 核心业务逻辑:计算价格 long nights = ChronoUnit.DAYS.between(checkIn, checkOut); BigDecimal totalPrice = room.getPricePerNight().multiply(BigDecimal.valueOf(nights)); // 3. 创建订单 BookingOrder order = new BookingOrder(); order.setRoom(room); order.setCustomer(customer); order.setCheckInDate(checkIn); order.setCheckOutDate(checkOut); order.setTotalPrice(totalPrice); order.setStatus(OrderStatus.CONFIRMED); // 4. 更新房间状态 room.setStatus(RoomStatus.BOOKED); roomRepository.save(room); // 实际上,由于room是JPA管理的实体,在事务提交时会自动更新 return bookingOrderRepository.save(order); } }

Service层的核心价值

  • 事务管理@Transactional注解确保“预订”这个动作,要么完全成功(扣库存、创建订单),要么完全失败回滚,不会出现“钱扣了但订单没生成”的中间状态。
  • 业务规则集中:所有关于“如何预订”的规则都在这里,不会散落在Controller或前端。
  • 可测试性Service是纯Java对象,不依赖HTTP,可以很方便地编写单元测试。

3.4 控制层:RESTful API设计与全局异常处理

Controller是前后端的契约。设计良好的API,能让前端开发者一看就懂。

// src/main/java/com/yourdomain/hotel/controller/BookingController.java package com.yourdomain.hotel.controller; import com.yourdomain.hotel.service.BookingService; import com.yourdomain.hotel.vo.BookingRequestVO; import com.yourdomain.hotel.vo.BookingResultVO; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @RestController @RequestMapping("/api/bookings") @RequiredArgsConstructor public class BookingController { private final BookingService bookingService; @PostMapping public ResponseEntity<BookingResultVO> createBooking(@Valid @RequestBody BookingRequestVO request) { // @Valid 会触发对BookingRequestVO里注解(如@NotNull, @Future)的校验 BookingResultVO result = bookingService.createBooking(request); return ResponseEntity.status(HttpStatus.CREATED).body(result); // 返回201 Created状态码 } @GetMapping("/{id}") public ResponseEntity<BookingResultVO> getBooking(@PathVariable Long id) { // ... } @PutMapping("/{id}/cancel") public ResponseEntity<Void> cancelBooking(@PathVariable Long id) { // 使用PUT进行状态变更是一种RESTful实践 // ... } } // 专门用于接收请求的VO(Value Object),与Entity分离 // src/main/java/com/yourdomain/hotel/vo/BookingRequestVO.java package com.yourdomain.hotel.vo; import lombok.Data; import javax.validation.constraints.Future; import javax.validation.constraints.NotNull; import java.time.LocalDate; @Data public class BookingRequestVO { @NotNull private Long roomId; @NotNull @Future private LocalDate checkInDate; @NotNull @Future private LocalDate checkOutDate; @NotNull private Long customerId; }

关键设计

  1. 使用VO/DTO:不要直接用Entity接收请求或返回响应。Entity包含数据库映射信息,而VO只关注接口契约。这提高了安全性和灵活性。
  2. 合理的HTTP状态码:创建成功用201,查询成功用200,资源不存在用404,参数错误用400
  3. 全局异常处理:在Controller里到处写try-catch是灾难。应该用@ControllerAdvice创建一个全局异常处理器,将不同的异常(如RuntimeException,ValidationException)转换成结构化的错误信息({“code”: 400, “message”: “...”})返回给前端。

4. 前端核心:用Vue3 + TypeScript构建响应式管理界面

前端不再是简单的“切页面”,而是构建一个状态驱动、组件化、与后端API清晰交互的客户端应用。

4.1 状态管理:用Pinia管理共享数据

酒店管理系统中,当前登录用户的信息是一个典型的全局状态。多个组件(导航栏、个人中心、权限按钮)都需要访问它。用Pinia管理比用provide/inject或事件总线更清晰。

// src/stores/user.ts import { defineStore } from 'pinia' import { ref } from 'vue' import type { User } from '@/types/user' // 定义好的TypeScript类型 export const useUserStore = defineStore('user', () => { // 状态 const currentUser = ref<User | null>(null) const token = ref<string | null>(localStorage.getItem('token')) // 计算属性 const isLoggedIn = computed(() => !!token.value) // Actions (同步或异步方法,用于修改状态) async function login(username: string, password: string) { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }) if (response.ok) { const data = await response.json() currentUser.value = data.user token.value = data.token localStorage.setItem('token', data.token) // 将token添加到后续请求的header中 // 通常我们会用axios拦截器统一做这件事 } else { throw new Error('登录失败') } } function logout() { currentUser.value = null token.value = null localStorage.removeItem('token') } return { currentUser, token, isLoggedIn, login, logout } })

在组件中使用这个store:

<!-- src/components/UserNav.vue --> <script setup lang="ts"> import { useUserStore } from '@/stores/user' const userStore = useUserStore() </script> <template> <div v-if="userStore.isLoggedIn"> 欢迎,{{ userStore.currentUser?.name }} <button @click="userStore.logout()">退出</button> </div> <div v-else> <router-link to="/login">登录</router-link> </div> </template>

Pinia的好处:状态变化是响应式的,任何使用该状态的组件都会自动更新。逻辑集中,易于测试和维护。

4.2 组件化与路由:构建单页应用骨架

Vue3的<script setup>语法让组件编写更简洁。结合Vue Router,我们可以构建一个典型的后台管理布局。

<!-- src/App.vue --> <script setup lang="ts"> import { RouterView } from 'vue-router' import Sidebar from '@/components/layout/Sidebar.vue' import Header from '@/components/layout/Header.vue' </script> <template> <div class="app-container"> <Header /> <div class="main-container"> <Sidebar /> <div class="content-wrapper"> <RouterView /> <!-- 这里是页面内容动态渲染的地方 --> </div> </div> </div> </template>
// src/router/index.ts import { createRouter, createWebHistory } from 'vue-router' import { useUserStore } from '@/stores/user' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', redirect: '/dashboard' }, { path: '/dashboard', name: 'Dashboard', component: () => import('@/views/Dashboard.vue'), meta: { requiresAuth: true } // 路由元信息,标记需要登录 }, { path: '/rooms', name: 'RoomManagement', component: () => import('@/views/room/RoomList.vue'), meta: { requiresAuth: true } }, { path: '/bookings', name: 'BookingManagement', component: () => import('@/views/booking/BookingList.vue'), meta: { requiresAuth: true } }, { path: '/login', name: 'Login', component: () => import('@/views/auth/Login.vue'), meta: { guestOnly: true } // 仅允许未登录用户访问 } ] }) // 全局路由守卫:实现权限控制 router.beforeEach((to, from) => { const userStore = useUserStore() if (to.meta.requiresAuth && !userStore.isLoggedIn) { // 如果需要登录但未登录,跳转到登录页 return { name: 'Login', query: { redirect: to.fullPath } } } if (to.meta.guestOnly && userStore.isLoggedIn) { // 如果只允许游客访问但已登录,跳转到首页 return { name: 'Dashboard' } } })

路由设计的逻辑:将不同的功能模块(仪表盘、客房管理、订单管理)映射到不同的路由和组件。路由守卫是集中进行权限校验的最佳位置。

4.3 API层封装:用Axios处理HTTP请求

直接在组件里写fetch会导致代码重复、难以管理错误和请求头。我们需要一个统一的HTTP客户端。

// src/apis/axios.ts import axios from 'axios' import { useUserStore } from '@/stores/user' import router from '@/router' // 1. 创建axios实例 const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api', // 从环境变量读取 timeout: 10000 }) // 2. 请求拦截器:统一添加token http.interceptors.request.use( (config) => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config }, (error) => Promise.reject(error) ) // 3. 响应拦截器:统一处理错误(如401跳转登录) http.interceptors.response.use( (response) => response.data, // 直接返回data,简化调用 (error) => { if (error.response?.status === 401) { const userStore = useUserStore() userStore.logout() router.push({ name: 'Login' }) } // 可以在这里处理其他错误,如提示消息 return Promise.reject(error) } ) export default http
// src/apis/roomApi.ts import http from './axios' import type { Room, RoomQueryParams } from '@/types/room' // 所有房间相关的API请求都集中在这里 export const roomApi = { // 分页查询房间列表 getRoomList(params: RoomQueryParams) { return http.get<{ list: Room[]; total: number }>('/rooms', { params }) }, // 获取单个房间详情 getRoomById(id: number) { return http.get<Room>(`/rooms/${id}`) }, // 创建房间 createRoom(roomData: Partial<Room>) { return http.post<Room>('/rooms', roomData) }, // 更新房间 updateRoom(id: number, roomData: Partial<Room>) { return http.put<Room>(`/rooms/${id}`, roomData) }, // 删除房间 deleteRoom(id: number) { return http.delete(`/rooms/${id}`) } }

在组件中使用:

<!-- src/views/room/RoomList.vue --> <script setup lang="ts"> import { ref, onMounted } from 'vue' import { roomApi } from '@/apis/roomApi' import type { Room } from '@/types/room' const roomList = ref<Room[]>([]) const loading = ref(false) async function fetchRooms() { loading.value = true try { const response = await roomApi.getRoomList({ page: 1, size: 10 }) roomList.value = response.list } catch (error) { console.error('获取房间列表失败:', error) // 这里应该触发一个全局的消息提示组件 } finally { loading.value = false } } onMounted(() => { fetchRooms() }) </script>

封装API层的好处

  1. 集中管理:所有后端接口地址、参数、响应类型都在一个地方定义和修改。
  2. 错误统一处理:登录过期、网络错误等可以在拦截器里统一处理,组件逻辑更干净。
  3. 类型安全:配合TypeScript,调用API时能有完善的代码提示和类型检查。
  4. 易于Mock:在前后端并行开发时,可以轻松替换为Mock数据。

5. 前后端联调与部署:让项目真正跑起来

代码写完了,但项目还跑不起来。联调和部署是让项目从“代码”变成“应用”的最后一步,也是最容易出问题的一步。

5.1 跨越CORS障碍:解决前端访问后端的第一道坎

由于浏览器同源策略,运行在localhost:5173(Vite开发服务器)的前端无法直接访问localhost:8080的后端。你需要在后端配置CORS。

// src/main/java/com/yourdomain/hotel/config/WebConfig.java package com.yourdomain.hotel.config; 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 implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") // 只对/api开头的路径允许跨域 .allowedOrigins("http://localhost:5173") // 你的前端开发服务器地址 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); // 如果请求需要携带cookie/token,此项为true } }

注意:生产环境时,allowedOrigins应该设置为你的前端生产域名,而不是*(允许所有来源),这是基本的安全要求。

5.2 数据库连接与数据初始化

application.propertiesapplication.yml中配置数据库连接。

# src/main/resources/application.yml spring: datasource: url: jdbc:mysql://localhost:3306/hotel_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发环境可以用update,自动更新表结构。生产环境务必设为none或validate。 show-sql: true # 在控制台打印SQL,方便调试 properties: hibernate: format_sql: true # 格式化打印的SQL

为了让项目第一次就能运行,我们通常需要一些初始数据(如管理员账号、房间类型)。Spring Boot提供了data.sqlschema.sql,但更优雅的方式是使用CommandLineRunnerApplicationRunner

// src/main/java/com/yourdomain/hotel/runner/DataInitializer.java @Component @RequiredArgsConstructor public class DataInitializer implements CommandLineRunner { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; @Override @Transactional public void run(String... args) { // 检查是否已存在管理员用户 if (userRepository.findByUsername("admin").isEmpty()) { User admin = new User(); admin.setUsername("admin"); admin.setPassword(passwordEncoder.encode("admin123")); // 密码必须加密存储! admin.setRole(UserRole.ADMIN); userRepository.save(admin); System.out.println("初始化管理员账号完成: admin/admin123"); } } }

5.3 打包与部署:从开发环境到可运行产物

后端打包

# 在SpringBoot项目根目录下 mvn clean package # 会在target目录下生成一个可执行的jar文件,如 hotel-system-0.0.1-SNAPSHOT.jar # 运行:java -jar target/hotel-system-0.0.1-SNAPSHOT.jar

前端打包

# 在前端项目根目录下 npm run build # 会在dist目录下生成静态文件(HTML, JS, CSS)

部署时,有两种常见方式:

  1. 前后端分离部署:前端dist目录下的文件放到Nginx或Apache服务器上。后端jar包在服务器上用java -jar运行或放入Docker容器。Nginx需要配置反向代理,将/api开头的请求转发到后端服务。
  2. 前后端合并部署:将前端打包后的静态文件复制到SpringBoot项目的src/main/resources/static/目录下,然后一起打包。这样访问根路径就能看到前端页面,API请求发到同源地址。这种方式更简单,适合小型项目或演示。

5.4 常见问题排查清单

当项目跑不起来时,按这个顺序检查:

  1. 端口冲突:后端默认8080,前端Vite默认5173。检查端口是否被占用。
  2. 数据库连接失败:检查application.yml中的数据库URL、用户名、密码。确认MySQL服务已启动,且数据库hotel_db已创建。
  3. 依赖下载失败:检查网络,尝试使用阿里云等国内Maven镜像。删除本地Maven仓库(~/.m2/repository)中对应依赖的目录,重新下载。
  4. 前端代理配置:如果不想配置后端CORS,可以在Vite中配置代理。
    // vite.config.ts export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, } } } })
    这样,前端发往/api的请求会被Vite转发到localhost:8080,规避了浏览器的同源限制。
  5. 控制台报错:仔细阅读IDE控制台和浏览器开发者工具(F12)Console标签页的错误信息。90%的问题都能从这里找到线索。

6. 从“能运行”到“能展示”:为你的简历和答辩加分

项目能跑起来只是第一步。如何让它成为你简历和毕业答辩中的亮点?关键在于理解深度表达方式

6.1 理解层面:准备回答这些“为什么”

面试官或答辩老师不会只问你“做了什么”,更会问“为什么这么做”。你需要能解释清楚:

  • 为什么选择SpringBoot和Vue3?可以说SpringBoot的“约定大于配置”和自动装配极大提升了开发效率;Vue3的Composition API和更好的TypeScript支持让大型前端应用更易维护。
  • 为什么用JPA而不是MyBatis?JPA的Repository模式更面向对象,能减少大量重复的SQL编写,在业务逻辑简单的CRUD场景下开发更快。但也要知道MyBatis在复杂、定制化SQL场景下的优势。
  • 前后端分离的好处是什么?前后端职责清晰,可以并行开发;前端可以独立部署,用户体验更好;后端API可以被多种客户端(Web、App、小程序)复用。
  • 你的API设计遵循了什么原则?RESTful风格,使用合适的HTTP方法和状态码;使用DTO/VO进行数据传递,避免暴露实体细节。
  • 如何保证数据一致性?在Service层使用@Transactional注解管理事务。
  • 前端状态管理为什么用Pinia?相比Vuex更简洁,支持Composition API,TypeScript集成更好。
  • 如果用户量增大,你会从哪些方面优化?这是一个开放性问题,可以分层次回答:数据库索引优化、缓存(Redis)、静态资源CDN、后端服务集群部署、前后端负载均衡等。

6.2 展示层面:让项目“活”起来

  1. 准备一份清晰的项目结构说明:在项目根目录放一个README.md,写明技术栈、功能模块、如何启动、账号密码。
  2. 录制一个简短的功能演示视频(2-3分钟)。展示核心流程:登录 -> 查看客房 -> 预订 -> 查看订单。这比干讲有效得多。
  3. 准备一页核心代码讲解:挑一两个你最熟悉的、能体现技术点的代码片段(比如上面的BookingService@Transactional方法,或者前端的Pinia Store),准备解释清楚。
  4. 思考项目的不足与展望:没有项目是完美的。你可以说“当前是单体架构,如果业务复杂可以考虑微服务”;“前端使用了组件库但样式可以进一步优化”;“目前是内存Session,可以考虑集成Spring Security和JWT做更完善的安全控制”。这体现了你的思考深度。

6.3 代码之外:工程化思维的体现

最终,这个项目的价值不在于它实现了酒店管理的所有功能,而在于它完整地走通了一个现代Web应用从设计、开发、联调到部署的闭环。你在这个过程中遇到的问题(环境配置、跨域、事务、类型错误、打包部署)和解决方案,才是你未来面对任何一个新项目时最宝贵的经验。

不要满足于“代码能跑”。去理解每一行配置背后的原因,每一个设计模式带来的好处,每一次报错指向的底层原理。当你能够清晰地解释“我从业务需求出发,如何一步步推导出这样的技术实现和架构选择”时,这个项目才真正成为了你技术能力的一部分,而不仅仅是简历上的一行字。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 7:54:41

Undertale同人模组运行指南:从环境配置到机制解析

1. 先搞清楚这个项目到底是什么&#xff1a;一个同人游戏模组&#xff0c;还是音画体验&#xff1f; 看到这个标题&#xff0c;第一反应可能会有点懵。 [Undertale:Karmas A B1#ch Inc.]Phase 1.5 -Karmic Epilogue 这个命名方式&#xff0c;一看就带着强烈的同人创作和社区梗…

作者头像 李华
网站建设 2026/9/2 7:54:36

不想装软件又要字幕?5 款在线音频转写工具实测对比

做开发的人&#xff0c;录个技术分享要出纪要&#xff1b;做自媒体的&#xff0c;剪视频得挂字幕&#xff1b;学生上网课&#xff0c;想把老师讲的变成笔记。音视频转文字这事&#xff0c;几乎人人都躲不掉。但市面上的工具&#xff0c;槽点一个接一个&#xff1a;有的必须下载…

作者头像 李华
网站建设 2026/9/2 7:52:34

2026年横评10款AI智能降重工具:一键锁定高效助手!

随着AI技术的快速发展&#xff0c;越来越多的学生和职场人士开始借助AI写作工具提升论文撰写和内容创作的效率。这些工具不仅节省了大量时间&#xff0c;还让复杂的学术写作变得简单易行。然而&#xff0c;随着AI生成内容的普及&#xff0c;高校、平台和期刊对AIGC内容的检测标…

作者头像 李华
网站建设 2026/9/2 7:45:43

LLM Weekly(2026.8.17-2026.8.23)

😎 网络新闻 NVIDIA 的 AVO 智能体在 ARC-AGI-3 上斩获满分,且未使用新模型。 NVIDIA 将 Claude Opus 5 封装在“智能体变分算子”(Agentic Variation Operators)——一种结合了记忆、监督、工具和反馈的套件(harness)中,并以此清除了 25 个公开 ARC-AGI-3 环境中全部 …

作者头像 李华
网站建设 2026/9/2 7:44:17

第27章 投资者沟通与资本市场策略

某快消品上市公司发布年报&#xff0c;首次披露数据资产2.3亿元。财报发布后的分析师电话会议上&#xff0c;第一个接入的提问者没有问估值方法&#xff0c;没有问摊销政策&#xff0c;而是直接抛出一个问题&#xff1a;“你们是不是为了美化报表才入表的&#xff1f;”CFO努力…

作者头像 李华