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

发布时间:2026/9/2 7:55:40
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.02.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-java或postgresql: 数据库驱动。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 vuelatest # 执行上述命令后你会进入一个交互式命令行。对于学习项目我的建议是 # - 项目名: 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.vuesrc/components/: 存放可复用的展示组件如RoomCard.vue,BookingForm.vuesrc/router/index.ts: 路由配置定义了URL路径和组件的映射关系。src/stores/: Pinia状态管理用于跨组件共享数据如用户登录状态。src/apis/: 我们待会要创建的目录用于集中管理所有向后端发送的HTTP请求。为什么强调TypeScript和Pinia因为它们是Vue3生态里提升项目可维护性的关键。TypeScript能在编码阶段就发现潜在的类型错误Pinia让状态管理变得清晰可控避免在组件间用emit和props传来传去导致数据流混乱。3. 后端核心用SpringBoot构建稳健的API服务后端不是一堆Controller、Service、Repository的简单堆砌。每一层都有明确的职责形成一个清晰的“请求处理流水线”。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 }设计要点使用包装类型Long id而不是long id因为null可以表示未持久化的新对象。金额用BigDecimal永远不要用float或double表示金额会有精度损失。状态用枚举比直接用字符串如available更安全、更易维护。Lombok是帮手Data让代码简洁但你要知道它背后生成了什么。3.2 数据访问层Repository不是简单的DAOSpring 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 JpaRepositoryRoom, Long { // 方法名即查询Spring Data JPA会根据方法名自动生成SQL。 ListRoom 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)) ListRoom findAvailableRoomsByTypeAndDateRange( Param(type) RoomType type, Param(startDate) LocalDate startDate, Param(endDate) LocalDate endDate ); }理解这一层Repository是连接实体和数据库的桥梁。你声明“想要什么”方法签名JPA提供“如何获取”实现。这比手写JDBC或MyBatis的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 ResponseEntityBookingResultVO 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 ResponseEntityBookingResultVO getBooking(PathVariable Long id) { // ... } PutMapping(/{id}/cancel) public ResponseEntityVoid cancelBooking(PathVariable Long id) { // 使用PUT进行状态变更是一种RESTful实践 // ... } } // 专门用于接收请求的VOValue 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; }关键设计使用VO/DTO不要直接用Entity接收请求或返回响应。Entity包含数据库映射信息而VO只关注接口契约。这提高了安全性和灵活性。合理的HTTP状态码创建成功用201查询成功用200资源不存在用404参数错误用400。全局异常处理在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 refUser | null(null) const token refstring | 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 langts import { useUserStore } from /stores/user const userStore useUserStore() /script template div v-ifuserStore.isLoggedIn 欢迎{{ userStore.currentUser?.name }} button clickuserStore.logout()退出/button /div div v-else router-link to/login登录/router-link /div /templatePinia的好处状态变化是响应式的任何使用该状态的组件都会自动更新。逻辑集中易于测试和维护。4.2 组件化与路由构建单页应用骨架Vue3的script setup语法让组件编写更简洁。结合Vue Router我们可以构建一个典型的后台管理布局。!-- src/App.vue -- script setup langts import { RouterView } from vue-router import Sidebar from /components/layout/Sidebar.vue import Header from /components/layout/Header.vue /script template div classapp-container Header / div classmain-container Sidebar / div classcontent-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.getRoom(/rooms/${id}) }, // 创建房间 createRoom(roomData: PartialRoom) { return http.postRoom(/rooms, roomData) }, // 更新房间 updateRoom(id: number, roomData: PartialRoom) { return http.putRoom(/rooms/${id}, roomData) }, // 删除房间 deleteRoom(id: number) { return http.delete(/rooms/${id}) } }在组件中使用!-- src/views/room/RoomList.vue -- script setup langts import { ref, onMounted } from vue import { roomApi } from /apis/roomApi import type { Room } from /types/room const roomList refRoom[]([]) 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层的好处集中管理所有后端接口地址、参数、响应类型都在一个地方定义和修改。错误统一处理登录过期、网络错误等可以在拦截器里统一处理组件逻辑更干净。类型安全配合TypeScript调用API时能有完善的代码提示和类型检查。易于Mock在前后端并行开发时可以轻松替换为Mock数据。5. 前后端联调与部署让项目真正跑起来代码写完了但项目还跑不起来。联调和部署是让项目从“代码”变成“应用”的最后一步也是最容易出问题的一步。5.1 跨越CORS障碍解决前端访问后端的第一道坎由于浏览器同源策略运行在localhost:5173Vite开发服务器的前端无法直接访问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.properties或application.yml中配置数据库连接。# src/main/resources/application.yml spring: datasource: url: jdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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.sql和schema.sql但更优雅的方式是使用CommandLineRunner或ApplicationRunner。// 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部署时有两种常见方式前后端分离部署前端dist目录下的文件放到Nginx或Apache服务器上。后端jar包在服务器上用java -jar运行或放入Docker容器。Nginx需要配置反向代理将/api开头的请求转发到后端服务。前后端合并部署将前端打包后的静态文件复制到SpringBoot项目的src/main/resources/static/目录下然后一起打包。这样访问根路径就能看到前端页面API请求发到同源地址。这种方式更简单适合小型项目或演示。5.4 常见问题排查清单当项目跑不起来时按这个顺序检查端口冲突后端默认8080前端Vite默认5173。检查端口是否被占用。数据库连接失败检查application.yml中的数据库URL、用户名、密码。确认MySQL服务已启动且数据库hotel_db已创建。依赖下载失败检查网络尝试使用阿里云等国内Maven镜像。删除本地Maven仓库~/.m2/repository中对应依赖的目录重新下载。前端代理配置如果不想配置后端CORS可以在Vite中配置代理。// vite.config.ts export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, } } } })这样前端发往/api的请求会被Vite转发到localhost:8080规避了浏览器的同源限制。控制台报错仔细阅读IDE控制台和浏览器开发者工具F12Console标签页的错误信息。90%的问题都能从这里找到线索。6. 从“能运行”到“能展示”为你的简历和答辩加分项目能跑起来只是第一步。如何让它成为你简历和毕业答辩中的亮点关键在于理解深度和表达方式。6.1 理解层面准备回答这些“为什么”面试官或答辩老师不会只问你“做了什么”更会问“为什么这么做”。你需要能解释清楚为什么选择SpringBoot和Vue3可以说SpringBoot的“约定大于配置”和自动装配极大提升了开发效率Vue3的Composition API和更好的TypeScript支持让大型前端应用更易维护。为什么用JPA而不是MyBatisJPA的Repository模式更面向对象能减少大量重复的SQL编写在业务逻辑简单的CRUD场景下开发更快。但也要知道MyBatis在复杂、定制化SQL场景下的优势。前后端分离的好处是什么前后端职责清晰可以并行开发前端可以独立部署用户体验更好后端API可以被多种客户端Web、App、小程序复用。你的API设计遵循了什么原则RESTful风格使用合适的HTTP方法和状态码使用DTO/VO进行数据传递避免暴露实体细节。如何保证数据一致性在Service层使用Transactional注解管理事务。前端状态管理为什么用Pinia相比Vuex更简洁支持Composition APITypeScript集成更好。如果用户量增大你会从哪些方面优化这是一个开放性问题可以分层次回答数据库索引优化、缓存Redis、静态资源CDN、后端服务集群部署、前后端负载均衡等。6.2 展示层面让项目“活”起来准备一份清晰的项目结构说明在项目根目录放一个README.md写明技术栈、功能模块、如何启动、账号密码。录制一个简短的功能演示视频2-3分钟。展示核心流程登录 - 查看客房 - 预订 - 查看订单。这比干讲有效得多。准备一页核心代码讲解挑一两个你最熟悉的、能体现技术点的代码片段比如上面的BookingService的Transactional方法或者前端的Pinia Store准备解释清楚。思考项目的不足与展望没有项目是完美的。你可以说“当前是单体架构如果业务复杂可以考虑微服务”“前端使用了组件库但样式可以进一步优化”“目前是内存Session可以考虑集成Spring Security和JWT做更完善的安全控制”。这体现了你的思考深度。6.3 代码之外工程化思维的体现最终这个项目的价值不在于它实现了酒店管理的所有功能而在于它完整地走通了一个现代Web应用从设计、开发、联调到部署的闭环。你在这个过程中遇到的问题环境配置、跨域、事务、类型错误、打包部署和解决方案才是你未来面对任何一个新项目时最宝贵的经验。不要满足于“代码能跑”。去理解每一行配置背后的原因每一个设计模式带来的好处每一次报错指向的底层原理。当你能够清晰地解释“我从业务需求出发如何一步步推导出这样的技术实现和架构选择”时这个项目才真正成为了你技术能力的一部分而不仅仅是简历上的一行字。