☰
基于SpringBoot+Vue的景区民宿预约系统开发实战
2026/9/30 4:08:04 网站建设 项目流程

1. 为什么我要把这个民宿预约系统拆给你看

很多人在做毕业设计或者课程项目时,需求文档写得洋洋洒洒,到了真正动手写代码才发现无从下手。尤其是"景区民宿预约系统"这种典型的业务系统——听起来模块不多,但真做起来,涉及用户端、管理端、订单状态流转、房间库存控制、支付对接等多个维度,任何一个环节做得粗糙,答辩时都会被老师一句话问住。

我这次完整实现的项目,是基于SpringBoot + Vue + MySQL + MyBatis的开发景区民宿预约系统。系统覆盖了民宿信息展示、在线选房、预约下单、订单管理、后台房源维护、数据统计等核心业务闭环。整套源码我已经完整跑通,前端、后端、数据库脚本齐全,可以直接作为毕业设计、课程设计或者个人练手项目的参考骨架,在此基础上扩展自己的业务逻辑也很方便。

这套系统的核心价值在于:它不是一个"玩具项目"。预约系统的难点通常不在增删改查,而在"同一间房在同一个时间段不能重复被预约"这种业务规则的落地,以及订单状态在多步骤操作下的稳定性。下文我会从需求拆解、技术选型、数据库设计、后端实现、前端页面、部署踩坑六个维度,把整个项目的设计思路和实现细节讲清楚。

2. 需求边界划清楚:民宿预约系统的"黑话"和真实业务

2.1 系统角色与核心流程

景区民宿预约和普通酒店预订有一个显著区别:民宿通常按"整栋/整间"售卖,房间数量少、房型差异大,而且很多景区民宿存在"旺季一房难求,淡季空置"的情况。所以系统设计的第一步,不是急着建表,而是把角色和流程理清楚。

这个项目里我划分了三个角色:

  • 游客(未登录用户):可以浏览民宿列表、查看房型详情、查看日历价格,但不能下单。
  • 注册用户(登录用户):可以提交预约订单、在线支付(模拟)、查看自己的订单记录、取消未确认的订单。
  • 系统管理员(后台):维护民宿信息、管理房型、设置价格日历、确认/拒绝订单、查看统计报表。

核心业务流程是一个典型的"提交预约 → 管理员确认 → 用户支付 → 入住 → 退房"五步链路。不过实际编码时,我发现一个容易被忽略的问题:业务规则表面上简单,但"提交预约"和"确认订单"这中间存在一个时间窗口,处理不好就会出现超卖。

2.2 把"预约"这件事拆成状态机

我一开始直接用一两个字段去表示订单状态(比如0待确认、1已确认、2已入住、3已完成),后来发现前端展示、后端判断、统计报表全都需要区分状态,硬编码数字后期根本维护不了。我最后用了一个独立的订单状态枚举,将整个生命周期定义为:

待确认 → 已确认(待支付) → 已支付(待入住) → 已入住 → 已完成 └→ 已取消(用户主动取消或管理员拒绝)

每个状态之间的流转,我在后端服务层做了统一校验。比如只有"待确认"状态的订单,用户才能取消;只有"已确认"状态的订单,才能进入支付流程。将所有状态判断收敛到服务层,而不是让前端页面各自判断,是我在这类项目里最深刻的一个体会——否则前端改一版,后端就要跟着改一版,状态逻辑散落得到处都是。

2.3 从需求到模块划分

整个系统我划分成了六大功能模块:

模块主要功能涉及角色
民宿信息展示列表页、详情页、房型展示游客/用户
注册登录JWT认证、用户信息管理游客
预约下单日期选择、房型选择、订单生成用户
订单管理订单列表、详情、取消用户/管理员
后台管理民宿管理、房型管理、订单审核、每日价格设置管理员
数据统计订单量、营收、入住率概览管理员

这里有个小细节:价格日历。民宿和酒店最大的不同在于价格浮动大,同一个房型周末、节假日、淡旺季价格完全不同。所以我没有把价格字段直接挂在房型表上,而是单独设计了一张"每日价格表",按日期存储每个房型的房价。这样前端日历组件直接查这张表即可,后端也不用手动改价。

3. 技术选型:为什么是SpringBoot+Vue+MySQL+MyBatis这套组合

3.1 后端:SpringBoot的"约定优于配置"有多省事

老实说,这个项目如果用SSH(Spring + Struts + Hibernate)做,光配置文件就能绕晕人。SpringBoot的核心价值在于自动配置和起步依赖。我只需要引入spring-boot-starter-web、mybatis-spring-boot-starter,再加上MySQL驱动,配置一个application.yml,项目就能跑起来。整个后端工程没有一行XML配置(MyBatis的Mapper XML除外)。

我还用了一个小技巧:SpringBoot的spring-boot-devtools热部署插件。修改代码后不用手动重启Tomcat,这在开发调试阶段非常提升效率。不过要提醒一句,这个插件在生产环境一定要排除掉。

3.2 持久层:MyBatis的灵活性和可控性

在选MyBatis还是Spring Data JPA时,我最终选了MyBatis。原因很简单:民宿预约系统里有大量自定义的统计查询和多表关联查询(比如查某个房型在某个时间段是否可预约,查订单量的月度趋势),MyBatis直接用SQL控制一切,写起来直觉、调优也方便。

我这个项目的Mapper层分两层写:

  • 单表基础操作:直接用MyBatis-Plus(注意是MyBatis的增强工具)提供的BaseMapper,避免手写通用CRUD。这一点能省掉大概30%的样板代码。
  • 复杂统计查询:手写SQL放在Mapper XML里,涉及多表 JOIN 和聚合函数时,自己掌控SQL更放心。

另外,关于 MyBatis 的驼峰映射,新手经常踩坑:数据库字段是create_time,实体类是createTime,如果不打开map-underscore-to-camel-case: true,查询结果就会全部为null。这个配置在application.yml里加上就好,我遇到过不下10次有人问我这个问题。

3.3 前端:Vue2还是Vue3?

这个项目我用的Vue2 + Element UI。我知道现在Vue3 + Element Plus已经是主流,但这里想多说一句我的考量和理由:Vue2的生态资料极其丰富,教程、现成组件、踩坑帖随便一搜就是一大堆,对于毕业设计和课程设计这种追求稳定、快速出结果的项目,Vue2反而比Vue3更好上手。当然,如果你想顺便把Vue3练熟,技术栈完全可以平移,核心业务代码的差异并不大——项目整体的数据交互模式(axios请求 → Vue组件渲染)在Vue2和Vue3中是一致的。

前端工程我用的是vue-cli脚手架初始化的项目。路由用vue-router,状态管理用vuex,HTTP请求统一封装了一个request.js工具类。在请求拦截器里统一携带JWT Token,在响应拦截器里统一处理401跳转登录页,这些属于常规操作,但确实是提升开发效率的关键。

3.4 数据库版本选择

MySQL我使用的是8.0版本。8.0和5.7相比,主要看中两点:一是性能提升明显,尤其是多用户并发场景下的表现;二是8.0对SQL标准支持更好,窗口函数在做统计报表时很好用。当然5.7也可以用,表结构和SQL语句基本通用。

4. 数据库设计的几个关键决策:拒绝简单粗暴建表

4.1 核心表结构一览

整个项目涉及9张表,我把核心的几张列出来:

-- 用户表 CREATE TABLE `user` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `username` varchar(50) NOT NULL UNIQUE, `password` varchar(100) NOT NULL, `phone` varchar(20), `real_name` varchar(50), `role` tinyint DEFAULT 0, -- 0普通用户 1管理员 `create_time` datetime DEFAULT CURRENT_TIMESTAMP ); -- 民宿表 CREATE TABLE `homestay` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `name` varchar(100) NOT NULL, `location` varchar(200) NOT NULL, `description` text, `cover_image` varchar(255), `status` tinyint DEFAULT 1 ); -- 房型表 CREATE TABLE `room_type` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `homestay_id` bigint NOT NULL, `name` varchar(50) NOT NULL, `price` decimal(10,2) NOT NULL, -- 默认价格 `area` varchar(20), `bed_info` varchar(50), `max_guest` int DEFAULT 2, `inventory` int DEFAULT 1 -- 房间数量 ); -- 每日价格表(价格日历核心) CREATE TABLE `daily_price` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `room_type_id` bigint NOT NULL, `price_date` date NOT NULL, `price` decimal(10,2) NOT NULL, UNIQUE KEY `uk_room_date` (`room_type_id`, `price_date`) ); -- 预约订单表 CREATE TABLE `booking_order` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `order_no` varchar(32) NOT NULL UNIQUE, `user_id` bigint NOT NULL, `room_type_id` bigint NOT NULL, `check_in_date` date NOT NULL, `check_out_date` date NOT NULL, `total_price` decimal(10,2) NOT NULL, `status` tinyint NOT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP ); -- 订单明细表(记录每个房间每晚的情况,应对多房间订单) CREATE TABLE `order_item` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `order_id` bigint NOT NULL, `room_type_id` bigint NOT NULL, `room_date` date NOT NULL, `price` decimal(10,2) NOT NULL );

4.2 为什么需要订单明细表

很多人设计订单表时只会想到booking_order这一层,但这会带来一个极其尴尬的问题:如果一个订单包含了"3间大床房,住2晚",那么你的总价到底怎么算?哪一晚对应哪间房?

我把每个订单拆分成order_item,一条记录代表"某房型在某天的一间房"。这样设计有几个直接好处:

  1. 价格计算清晰:total_price从明细表聚合而来,不靠人工计算。
  2. 库存校验方便:检查某个房型在某个日期是否可预约,只需查order_item表,按room_type_id + room_date分组统计,是否达到inventory就能判断。
  3. 对账有据可查:出现价格纠纷时,可以精确到"某一个晚上"来追溯。

4.3 防超卖:数据库层面怎么兜底

预约系统的核心安全性问题就是超卖——同一晚同一房型同时被两个用户约走。我用了三层防护:

第一层:Redis分布式锁。用户提交订单时,以roomTypeId + checkInDate为key加锁,确保同一房型的预约请求串行化。Redis不是必须的,但能扛住高并发场景。

第二层:数据库事务+行锁。真正的落库校验放在事务里执行,使用SELECT ... FOR UPDATE对相关房型记录加行锁,然后检查可约库存,再插入订单。事务提交后锁释放。

第三层:唯一索引兜底。在order_item表加一个UNIQUE KEY uk_room_date_type (room_date, room_type_id)的思路不可行,因为同一个日期同房型的多个订单都是合法的(比如有3间房,可以约3单)。不过如果把"确认状态"考虑进去,可以给(room_date, room_type_id, status)加一个条件索引(MySQL 8.0支持),防止同一房型同一日期出现两个"已确认"订单。这个属于进阶优化,我一并在源码注释里写了。

这里我特别想强调:做毕设时,把"防超卖"这个点写进论文和项目里,非常加分。因为大部分同学的预约系统都是直接INSERT一条记录,毫无并发控制,而你做了事务+行锁,一下就有技术含量了。

5. 后端核心实现:认证、拦截与订单接口

5.1 基于JWT的登录认证

用户登录成功后,后端生成一个JWT Token返回给前端,前端后续所有请求都在请求头带上Authorization: Bearer <token>。

生成JWT我用的是jjwt库,核心代码大概长这样:

@PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { User user = userService.login(dto.getUsername(), dto.getPassword()); if (user == null) { return Result.error("用户名或密码错误"); } String token = JwtUtil.createToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(new LoginVO(token, user)); }

JWT的好处是服务端无状态,不存Session,非常适合前后端分离架构。缺点也很明显:Token一旦签发,有效期内的主动过期处理比较麻烦。我在项目里将过期时间设置为24小时,同时在用户修改密码后强制失效旧Token。

登录密码的存储也要注意,绝不能明文存。我用的是BCryptPasswordEncoder(Spring Security里的加密器),每次加密都会加盐,即使两个用户的密码相同,密文也不一样。如果项目里看到有人用MD5直接存密码,趁早改掉。

5.2 拦截器统一鉴权

在SpringBoot里实现拦截器非常简单,我实现了HandlerInterceptor接口,在preHandle方法中校验Token,然后通过HandlerMethod判断方法上是否有@RequireAdmin注解,有的话再校验角色是否为管理员。

@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; // 放行静态资源等 } String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token)) { throw new BusinessException("未登录"); } // 解析token,将用户信息放入ThreadLocal UserContext.set(JwtUtil.parseToken(token.replace("Bearer ", ""))); return true; } }

Token的解析结果我放进了ThreadLocal,这样在Service层可以直接通过UserContext.get().getUserId()获取当前登录用户,不用每次在Controller里手动传参。这是开发效率提升的一个细节。

5.3 订单提交接口的完整链路

订单提交是整个系统最核心的接口,我按下面的顺序实现:

  1. 前端传参:userId(从Token获取)、roomTypeId、checkInDate、checkOutDate、roomCount。
  2. 参数校验:日期合法性、入住日期不能早于今天。
  3. 计算逐日价格:从daily_price表查询每天的房价,如果某天没配置价格,就回退到room_type表的默认价格。
  4. 加锁校验库存:以roomTypeId + checkInDate + checkOutDate为锁标识,校验每一天可用房间数是否充足。
  5. 生成订单号和订单明细:order_no使用时间戳+随机数生成,保证唯一。
  6. 事务提交:订单主表、明细表一起保存。二者任一失败,整体回滚。

order_no的生成看起来简单,但要注意:如果直接用System.currentTimeMillis()在高并发下会重复。我用了时间戳 + 三位随机数 + 用户ID后四位的组合方式,项目里的校验函数专门跑了并发测试,短时间内不会重复。其实更严谨的做法是用Redis自增序列,但作为毕设来讲,我的方案已经够用了。

@Transactional(rollbackFor = Exception.class) public BookingOrder createOrder(CreateOrderDTO dto) { // 1. 校验 // 2. 计算价格 List<DailyPrice> prices = dailyPriceMapper.selectRange(dto.getRoomTypeId(), dto.getCheckInDate(), dto.getCheckOutDate()); // 3. 校验库存 for (LocalDate date = dto.getCheckInDate(); date.isBefore(dto.getCheckOutDate()); date = date.plusDays(1)) { int bookedCount = orderItemMapper.countByRoomTypeAndDate(dto.getRoomTypeId(), date); if (bookedCount + dto.getRoomCount() > roomType.getInventory()) { throw new BusinessException(date + " 房间不足"); } } // 4. 保存订单 // 5. 生成明细 }

5.4 管理员订单审核的细节

用户提交订单后状态为"待确认",管理员在后台有两种处理方式:确认或拒绝。

如果是确认,状态变更为"已确认(待支付)",同时系统自动生成订单明细(在创建订单时已经生成);如果是拒绝,状态直接变为"已取消",并记录拒绝原因。这里有一个很重要的业务闭环:用户下单时只是"预占"了库存,管理员确认后才真正"锁定"库存。所以我的库存校验同时考虑了"待确认"和"已确认"两种状态的订单,防止管理员确认时发现房间已经被其他订单占满。

6. 前端页面:从TestCase到完整交互

6.1 页面结构与路由设计

前端部分我一共设计了8个页面,路由结构如下:

const routes = [ { path: '/', component: HomeView, meta: { title: '首页' } }, { path: '/homestay/:id', component: HomestayDetail }, { path: '/login', component: LoginView }, { path: '/register', component: RegisterView }, { path: '/orders', component: OrderListView, meta: { requiresAuth: true } }, { path: '/admin', component: AdminLayout, meta: { requiresAdmin: true }, children: [ { path: 'homestay', component: AdminHomestay }, { path: 'orders', component: AdminOrders }, { path: 'prices', component: AdminPrices }, { path: 'stats', component: AdminStats } ]} ];

路由守卫里,我根据meta里的requiresAuth和requiresAdmin判断是否需要登录、是否必须是管理员,不满足条件的统一重定向到对应页面。

6.2 民宿详情页与日历组件的烦恼

民宿详情页是整个前端开发中最费时间的部分。核心交互是:用户选择入住日期和退房日期,然后前端要根据后端返回的价格日历数据,渲染出每一晚的价格,同时禁选已约满的日期。

Element UI 自带的日期选择器能限制可选范围,但它不支持"显示某一天不可选"的逻辑。我最后使用了DatePicker的disabled-date回调函数,配合后端返回的"不可选日期列表",动态禁用已满房日期。这个功能交互上虽然简单,但我在调试时花了半天,因为日期格式在前后端之间的传输(yyyy-MM-dd字符串 与 JS Date 对象 的转换)非常容易出现时区偏移问题。建议所有日期统一使用字符串传递,不要在前端直接 new Date("2025-03-01") 然后加工,容易踩时区坑。

6.3 axios封装与鉴权携带

前端所有请求统一走request.js封装。下面这段代码基本是标配,但有几个细节值得注意:

import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } Message.error(error.response?.data?.message || '请求失败') return Promise.reject(error) } )

注意点了两件事:第一,请求拦截器带了Token,但注销时一定要清掉本地存储的Token;第二,后端自定义异常会返回HTTP 200 + 业务错误码,还是HTTP 500 + 错误信息,这决定了响应拦截器写法。我在项目里统一约定:业务逻辑错误走HTTP 200 + code=500,纯系统异常才走HTTP 500,这样前端处理起来相对统一。

6.4 管理后台的统计图表

管理员首页我放了一个简单的数据看板,包括:总订单数、总营收、待确认订单数、各民宿订单量排行。图表是用 ECharts 实现的。后端的统计SQL是手写的,类似这样:

SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, COUNT(*) AS order_count, SUM(total_price) AS revenue FROM booking_order WHERE status IN (2, 3, 4) GROUP BY month ORDER BY month;

这里我踩过一个坑:DATE_FORMAT返回的是字符串,前端ECharts的x轴刚好可以当类目轴使用,但如果后端直接返回 MySQL 的DATE类型,Jackson默认会序列化成数组[2025, 3, 1]而不是字符串"2025-03-01",会导致前端显示错乱。解决方式是在实体类对应字段上加@JsonFormat(pattern = "yyyy-MM-dd")。

7. 部署与运行:从源码到能跑起来的完整链路

7.1 本地环境的版本搭配

这套系统我建议的版本组合:

组件推荐版本备注
JDK1.8 或 11SpringBoot 2.x 搭配JDK8最稳
Maven3.6+管理后端依赖
MySQL8.05.7也可,注意驱动差异
Node.js14+跑Vue2项目,建议14/16 LTS
npm/yarn任意安装前端依赖

我见过太多人卡在第一步:JDK版本过高导致SpringBoot项目启动失败。SpringBoot 2.x 官方支持到JDK8,如果你用的是JDK17,建议直接把SpringBoot版本升到2.7+甚至3.x,否则会出现各种反射、CGLIB相关报错。如果你的毕设题目里并没有强制要求SpringBoot版本,建议直接用JDK8跑SpringBoot 2.7,这是目前最稳的组合。

7.2 后端启动步骤

后端工程是标准的Maven项目,启动步骤:

# 1. 导入数据库脚本 mysql -u root -p < sql/homestay.sql # 2. 修改 application.yml 数据库配置 # 3. 启动后端 mvn spring-boot:run

application.yml我贴一下核心配置,这个几乎是所有SpringBoot项目的通用骨架:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/homestay?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.homestay.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

serverTimezone=Asia/Shanghai这个参数一定不能漏。我的一个小经验是,如果你连接MySQL报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,八成就是时区配置问题。

7.3 前端启动步骤

# 1. 安装依赖 npm install # 2. 配置代理(vue.config.js) devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } # 3. 启动 npm run serve

前端有一个非常常见的坑:npm install时因为Node版本太高,报node-sass编译错误。解决方法是卸载node-sass,改装sass(Dart Sass),或者把Node降到16以下。如果是在中国区网络环境下,建议配置淘宝镜像源:

npm config set registry https://registry.npmmirror.com

这一步能帮你躲避掉大量"安装依赖装到怀疑人生"的时间。

8. 项目跑通后的进阶思考:三个值得深挖的扩展点

项目本身跑通只是第一步。如果这个系统是你的毕设,或者你想把它写进简历,下面这三个方向可以让项目含金量立刻上一个台阶。

8.1 扩展点一:引入Redis缓存热点数据

民宿列表页和详情页绝对是系统的"热数据",每个用户进来第一件事就是看这些数据,而且这些数据改动频率很低。这种场景最适合加缓存。我目前的实现里,房型信息、民宿介绍每次请求都查数据库,在并发量上来之后有性能瓶颈。加入Redis缓存后,可以做到:

  • 首页民宿列表缓存10分钟;
  • 价格日历缓存5分钟(有价格变动时手动删除对应key);
  • 热门民宿详情页缓存30分钟。

这套改造工程量不大,但可以在论文里写上"引入Redis缓存,降低数据库压力,提升接口响应速度",是一个非常标准的性能优化点。

8.2 扩展点二:对接真实支付回调

目前系统的支付是模拟的——用户点击支付,直接把订单状态置为"已支付"。对接真实微信支付/支付宝支付的话,核心变化是:

  • 下单后生成支付二维码;
  • 用户扫码支付后,支付平台异步回调后端接口;
  • 后端在回调中验签,更新订单状态,返回成功应答。

这里要注意的是:回调通知必须是接口幂等的。支付平台可能因为网络问题多次重发回调,如果每次回调都执行"将待支付订单改为已支付"的逻辑,状态更新操作必须保证幂等——通常做法是先查订单状态,只有待支付才更新。

8.3 扩展点三:消息队列削峰

旺季抢房场景下,预约请求瞬间激增,可以直接把下单请求先放进消息队列(RabbitMQ/Kafka),由消费者异步处理订单创建。前端先提示"排队中",等消费者处理完再通过WebSocket通知用户结果。这种"异步下单+结果通知"的架构是生产环境民宿系统的常见设计,写进毕设里绝对是加分项。

9. 踩坑记录:这五个坑我从开始到跑通都踩了一遍

9.1 MyBatis的where条件动态拼接

写统计SQL时,我一开始用${}直接拼接参数,结果发现SQL注入风险和语法错误并存。后来全部改成<if>标签 +#{}预编译。MyBatis的XML里,<if test="status != null">这个test表达式里,如果参数名写错,运行时报的错(There is no getter for property named xxx)会让人摸不着头脑。调试技巧是先在本地把SQL打印出来(配置log-impl: StdOutImpl),看预编译前的SQL长什么样。

9.2 MySQL 8.0的SSL连接问题

JDBC连接串里加不加useSSL=false,在MySQL 8.0下经常报SSL connection error。这个问题本质是MySQL 8.0默认开了SSL,而本机没有配置证书。最简单方案就是显式加useSSL=false并指定driver-class-name: com.mysql.cj.jdbc.Driver。注意8.0的驱动类名和5.7不一样,5.7是com.mysql.jdbc.Driver,8.0强制用com.mysql.cj.jdbc.Driver。

9.3 前后端联调时的跨域问题

开发时前端跑在localhost:8081,后端在localhost:8080,我在后端写了一个全局CORS配置,允许跨域。上线部署时,我会把前端打包后的静态文件直接放在后端src/main/resources/static目录下,这样前后端同源,跨域问题自动消失。两种方案都可以,但开发阶段跨域配置和后端运行是分开处理的。

9.4 日期选择器的边界值错误

用户选择"3月5日入住,3月7日退房",实际入住是两个晚上(5号、6号),7号退房当天不占房。这个逻辑我一开始写错了,计算房费时把7号也算进去,导致用户多付一晚的钱。选房逻辑里,日期区间的边界值判断非常容易出错。正确写法是:for (date = checkIn; date.isBefore(checkOut); date = date.plusDays(1))。用isBefore而不是!isAfter,可以一半以上概率避免这种低级错误。

9.5 前端数据回显的对象引用问题

在Vue中修改一个通过props传入的对象属性,直接改可能会触发Vue的响应式警告。应对方法是先深拷贝一份再操作。排查这种问题时,可以打印对象看是否被Vue包装成了Observer对象——看到一个对象上挂着__ob__属性,基本就能判断出是响应式对象了。

10. 源码结构速览:拿到代码后从哪里看起

最后给拿到源码的同学一个阅读路径建议。作为参考,我把整个项目的结构大概列在下面,标注了优先吃透的顺序:

├── backend │ ├── src/main/java/com/example/homestay │ │ ├── controller — Controller层(先看这里,了解接口入口) │ │ ├── service — Service层(业务逻辑核心,重点看订单服务) │ │ ├── mapper — MyBatis的Mapper接口 │ │ ├── entity — 实体类 │ │ ├── dto/vо — 数据交互对象 │ │ ├── config — 配置类(拦截器、CORS等) │ │ └── common — 统一返回结果、异常处理 │ └── resources │ ├── mapper — Mapper XML(手写SQL都在这里) │ └── application.yml ├── frontend │ ├── src │ │ ├── api — 所有后端接口的调用封装(先看这里) │ │ ├── views — 页面组件 │ │ ├── router — 路由定义 │ │ ├── store — Vuex状态 │ │ └── utils/request.js — axios封装 │ └── vue.config.js └── sql └── homestay.sql — 数据库脚本(最先导入)

建议阅读顺序是:SQL脚本 → 后端Controller → 订单Service → Mapper XML → 前端api → 前端核心页面。这个链路能够让你沿着"表结构 → 接口 → 业务实现 → 交互展示"的主线快速理解整个项目。虽然项目本身是一个完整可运行的预约系统,但真要二次开发或者毕业答辩演示,建议把主要精力放在订单状态流转逻辑和MyBatis复杂查询这两块上,这两个点也是回答老师提问时最能展现你真实掌握程度的地方。

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

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

立即咨询