温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系名片!
一、项目背景与意义
随着短途旅行和个性化住宿需求的增长,民宿已经成为旅游住宿市场的重要组成部分。与传统酒店相比,民宿房源分散、房型差异大、房东多为个人经营者,房源信息维护、房态管理、订单确认和结算等环节往往依赖人工沟通,容易出现超卖、漏单、对账困难等问题。
本文设计并实现一套基于 Spring Boot + Vue3 的民宿预订管理系统,面向房东、房客和平台管理员三类角色,提供房源发布、房态日历、在线预订、订单流转和后台统计等能力,帮助中小民宿经营者以较低成本完成数字化管理。
二、系统需求分析
2.1 角色与权限
| 角色 | 核心职责 | 主要功能 |
|---|---|---|
| 房客 | 浏览与预订房源 | 房源搜索、详情查看、日期选择、下单支付、订单查询、评价 |
| 房东 | 维护房源与订单 | 房源发布与上下架、房态日历维护、订单确认与拒单、收入统计 |
| 管理员 | 平台运营管理 | 用户管理、房源审核、订单监管、评价管理、数据看板 |
2.2 核心业务流程
flowchart TD A[房客搜索房源] --> B[选择入住日期与房型] B --> C{房态是否可用} C -->|否| D[提示日期已被预订] C -->|是| E[创建订单并锁定房态] E --> F[支付订单] F --> G{支付结果} G -->|成功| H[通知房东确认] G -->|失败| I[释放房态并关闭订单] H --> J[房东确认接单] J --> K[房客入住] K --> L[订单完成并生成评价入口]2.3 非功能性需求
- 并发安全:同一房源同一日期区间不允许重复预订,需要通过数据库唯一约束或分布式锁保证一致性。
- 响应性能:房源列表与详情接口在常规数据量下响应时间控制在 300ms 以内,热点数据使用 Redis 缓存。
- 可维护性:前后端分离,接口遵循 RESTful 风格,统一返回结构与异常处理。
三、技术选型与整体架构
3.1 技术栈
| 层次 | 技术选型 | 说明 |
|---|---|---|
| 前端 | Vue3 + Vite + TypeScript + Element Plus + Pinia | 组合式 API 开发,Pinia 管理登录态与全局状态 |
| 后端 | Spring Boot 3 + Spring Security + MyBatis-Plus | JWT 无状态认证,MyBatis-Plus 简化单表 CRUD |
| 数据库 | MySQL 8.0 | 存储用户、房源、房态、订单、评价等业务数据 |
| 缓存 | Redis | 缓存房源详情、房态日历,实现下单防重与分布式锁 |
| 文件存储 | MinIO / 阿里云 OSS | 存储房源图片与用户头像 |
3.2 系统分层架构
flowchart LR A[Vue3 前端应用] --> B[网关与鉴权层 JWT] B --> C[Controller 接口层] C --> D[Service 业务层] D --> E[Mapper 数据访问层] E --> F[(MySQL)] D --> G[(Redis)] D --> H[对象存储 OSS]四、数据库设计
系统核心表包括用户表、房源表、房态表、订单表和评价表。房态表按「房源 + 日期」维度存储,是防止超卖的关键。
CREATE TABLE t_house ( id BIGINT PRIMARY KEY AUTO_INCREMENT, owner_id BIGINT NOT NULL COMMENT '房东ID', title VARCHAR(100) NOT NULL COMMENT '房源标题', city VARCHAR(50) NOT NULL COMMENT '所在城市', address VARCHAR(255) NOT NULL COMMENT '详细地址', price DECIMAL(10,2) NOT NULL COMMENT '每晚价格', cover_url VARCHAR(500) COMMENT '封面图', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待审核 1已上架 2已下架', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_city_status (city, status) ) COMMENT '房源表'; CREATE TABLE t_room_calendar ( id BIGINT PRIMARY KEY AUTO_INCREMENT, house_id BIGINT NOT NULL COMMENT '房源ID', calendar_date DATE NOT NULL COMMENT '日期', status TINYINT NOT NULL DEFAULT 0 COMMENT '0可预订 1已锁定 2已预订', order_id BIGINT COMMENT '关联订单ID', UNIQUE KEY uk_house_date (house_id, calendar_date) ) COMMENT '房态日历表'; CREATE TABLE t_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT '订单号', house_id BIGINT NOT NULL, user_id BIGINT NOT NULL, check_in DATE NOT NULL, check_out DATE NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1待确认 2已确认 3已完成 4已取消', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no), KEY idx_user_status (user_id, status) ) COMMENT '订单表';五、后端核心功能实现
5.1 项目结构与统一返回
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BizException.class) public Result<Void> handleBiz(BizException e) { return Result.fail(e.getCode(), e.getMessage()); } @ExceptionHandler(Exception.class) public Result<Void> handleOther(Exception e) { log.error("系统异常", e); return Result.fail(500, "系统繁忙,请稍后重试"); } }5.2 下单与房态锁定
下单时先校验日期区间内房态是否全部可预订,再通过 Redis 分布式锁按房源维度串行化处理,最后批量更新房态并写入订单,保证不超卖。
@Service @RequiredArgsConstructor public class OrderService { private final RoomCalendarMapper calendarMapper; private final OrderMapper orderMapper; private final RedissonClient redissonClient; @Transactional(rollbackFor = Exception.class) public Long createOrder(OrderCreateDTO dto, Long userId) { RLock lock = redissonClient.getLock("lock:house:" + dto.getHouseId()); lock.lock(10, TimeUnit.SECONDS); try { List<LocalDate> dates = dto.getCheckIn() .datesUntil(dto.getCheckOut()) .toList(); int available = calendarMapper.countAvailable(dto.getHouseId(), dates); if (available != dates.size()) { throw new BizException("所选日期已被预订,请重新选择"); } String orderNo = OrderNoGenerator.next(); Order order = Order.builder() .orderNo(orderNo) .houseId(dto.getHouseId()) .userId(userId) .checkIn(dto.getCheckIn()) .checkOut(dto.getCheckOut()) .totalAmount(dto.getTotalAmount()) .status(OrderStatus.WAIT_PAY) .build(); orderMapper.insert(order); calendarMapper.lockDates(dto.getHouseId(), dates, order.getId()); return order.getId(); } finally { lock.unlock(); } } }5.3 房源查询与缓存
@Service @RequiredArgsConstructor public class HouseQueryService { private final HouseMapper houseMapper; private final RedisTemplate<String, Object> redisTemplate; public HouseDetailVO detail(Long id) { String key = "house:detail:" + id; HouseDetailVO cached = (HouseDetailVO) redisTemplate.opsForValue().get(key); if (cached != null) { return cached; } HouseDetailVO vo = houseMapper.selectDetailById(id); if (vo == null) { throw new BizException("房源不存在"); } redisTemplate.opsForValue().set(key, vo, 30, TimeUnit.MINUTES); return vo; } }六、前端实现要点
6.1 请求封装与登录态
import axios from 'axios' import { useUserStore } from '@/stores/user' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE, timeout: 10000 }) request.interceptors.request.use((config) => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config }) request.interceptors.response.use( (res) => { const { code, data, message } = res.data if (code !== 0) { ElMessage.error(message) return Promise.reject(new Error(message)) } return data }, (error) => { if (error.response?.status === 401) { useUserStore().logout() router.push('/login') } return Promise.reject(error) } ) export default request6.2 房态日历组件
房态日历使用日期网格渲染,按接口返回的可预订日期集合标记状态,用户选择入住与离店日期后实时计算总价。
<template> <div class="calendar"> <div v-for="day in days" :key="day.date" class="day" :class="{ disabled: !day.available, selected: isSelected(day.date) }" @click="pick(day)" > {{ day.label }} </div> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue' const props = defineProps<{ days: CalendarDay[]; price: number }>() const emit = defineEmits<['change']>() const checkIn = ref<string>() const checkOut = ref<string>() const total = computed(() => { if (!checkIn.value || !checkOut.value) return 0 const nights = (new Date(checkOut.value).getTime() - new Date(checkIn.value).getTime()) / 86400000 return nights * props.price }) function pick(day: CalendarDay) { if (!day.available) return if (!checkIn.value || checkOut.value) { checkIn.value = day.date checkOut.value = undefined } else { checkOut.value = day.date emit('change', { checkIn: checkIn.value, checkOut: checkOut.value, total: total.value }) } } </script>七、系统部署与运行
- 准备 MySQL 8.0 与 Redis,执行初始化 SQL 脚本创建库表。
- 修改后端
application.yml中的数据源、Redis 与对象存储配置。 - 执行
mvn clean package打包后端,使用java -jar启动服务。 - 前端执行
npm install与npm run build,将产物部署到 Nginx,并配置接口反向代理。
八、总结与展望
本文完成了基于 Spring Boot + Vue3 的民宿预订管理系统的需求分析、架构设计、数据库设计与核心功能实现,重点解决了房态一致性与下单防超卖问题。后续可继续扩展在线支付对接、消息通知、房源地图检索和多房东分账结算等能力,进一步提升系统的实用性与商业价值。