资讯动态

Spring Boot + Vue + Node.js旅游民宿预订网站全栈实战解析

发布时间:2026/9/9 10:42:55 来源:尧图企业网站定制
做旅游民宿类项目这几年我前前后后经手过好几个版本从最早的单体JSP项目到后来的前后端分离技术栈换了一轮又一轮。而这个基于Spring Boot Vue Node.js的旅游景点民宿预订网站算是我个人觉得最有代表性的一套组合也是目前中小型项目里最主流、最适合拿来练手或者直接二次开发的架构形态。这套项目涵盖了用户端预订、房东端管理、后台审核、景点联动推荐这些完整业务闭环前前后后踩了不少坑也沉淀了一些实战细节今天就拿出来跟大家聊聊。适合看这篇内容的人很明确一是准备做毕业设计或者个人作品集的学生二是想快速搭一套民宿预订类业务原型的开发者三是想从前端或者后端单边转向全栈的兄弟。看完之后你至少能搞清楚这套系统该怎么拆模块、核心表怎么设计、订单和房态怎么联动、前后端联调要注意什么以及那些让人头大的环境配置问题怎么绕开。1. 项目整体设计与技术选型思路1.1 为什么是Spring Boot Vue Node.js这套组合先说一个常见的误区。很多人看到标题里有Node.js以为是拿Node.js写后端服务其实不是。在这个项目里Node.js真正的身份是前端工程化的底座——Vue项目本身跑在Node环境之上npm安装依赖、Vite启动开发服务器、打包构建全都依赖Node.js运行时。也就是说这套系统的后端统一走Spring Boot前端渲染统一走Vue而Node.js负责把Vue项目“撑起来”。选Spring Boot做后端理由其实不用我多说。它对Spring生态做了大量的自动配置把以前SSH时代那套繁琐的XML配置几乎全部干掉内嵌Tomcat打成jar包就能直接跑。对于民宿预订这种CRUD密集的Web业务开发效率比传统SSM高出不止一个级别。再加上Spring家族天然的好扩展性后面想接入Redis缓存、RabbitMQ消息队列、Elasticsearch搜索都是一条龙的事。选Vue做前端核心是它的渐进式框架设计对中小团队太友好了。项目初期可以只做简单的页面渲染中期再按需引入Vue Router做路由控制、Pinia做状态管理最后配合Vite做按需编译和打包优化。而且Vue的双向数据绑定在表单类、筛选类交互特别多的民宿场景里写起来比原生JS要爽快得多——比如用户选择入住日期后总价实时变化这种联动模板语法里直接绑定就能出效果。1.2 模块拆解一个民宿预订网站到底包含哪些东西很多初学者拿到这种题目上来就写“用户表”“订单表”然后就开始堆CRUD结果做到一半发现画面撑不起来。我个人习惯是先按角色和业务域把整个系统拆成几条主线再逐条细化。这个民宿预订网站我把它分成三个端口用户端注册登录、浏览民宿列表、按景点/城市关键字搜索、查看民宿详情、选择房型和入住日期、提交订单、模拟支付、个人中心查看订单、收藏民宿、发表评价。房东端民宿信息管理、房型管理、房价日历管理节假日可单独调价、订单处理确认入住/退房、收入统计。管理后台用户管理、民宿审核上线前必须过审、景点管理、订单总览、数据统计报表。三条线各自独立又互相咬合。比如房东新增一间民宿状态默认是“待审核”管理员在后台审核通过后这间民宿才能在用户端被搜索到用户下单后订单状态流转会同时影响房东端的待办列表和用户端的订单列表。这也是我特别想强调的一点做这个项目最忌讳一上来就写代码。先把角色、状态、权限边界画清楚后面每写一个接口都有明确的归属不会出现“这个接口到底给谁用”的混乱。2. 数据库设计与业务建模2.1 八大核心表撑起整套预订业务民宿预订的数据模型我建议先建这几张核心表。你自己扩展的时候在这个基础上加字段就行不建议大改结构。用户表t_user主键、用户名、密码BCrypt加密后存储、昵称、手机号、头像、角色1普通用户/2房东/3管理员、创建时间。这里要提醒一句密码千万别明文存后面我会专门说。民宿表t_homestay主键、房东用户ID、民宿名称、封面图、轮播图JSON数组字符串、简介、省市区、详细地址、经度纬度后续做地图和距离排序要用、入住退房时间规则、综合评分、状态0待审核/1已上架/2已下架/3审核驳回。房型表t_room_type主键、民宿ID、房型名称、门市价、售价、库存、面积、床型、可住人数、房型图片。注意一个民宿可以对应多个房型一个房型就是一个SKU价格和库存都挂在房型上而不是挂在民宿上。景点表t_scenic_spot主键、景点名称、简介、封面图、所在城市、地址。这个表是用来做“某某景点附近民宿”这个推荐逻辑的怎么关联后面细说。订单表t_order主键、订单编号、用户ID、民宿ID、房型ID、入住日期、离店日期、入住天数、房间数、订单金额、支付状态、订单状态0待支付/1已支付待入住/2已入住/3已离店待评价/4已取消/5已完成、评价状态、创建时间。收藏表t_favorite主键、用户ID、民宿ID加一个唯一索引防止重复收藏。评价表t_comment主键、订单ID、用户ID、民宿ID、评分1-5、评价内容、房东回复、创建时间。优惠券表t_coupon主键、用户ID、优惠券名称、抵扣金额、使用门槛满多少钱可用、有效期起止、状态0未使用/1已使用/2已过期。这个属于锦上添花但加了之后整个项目的完整度会明显提升面试或答辩时也更有话聊。2.2 一个关键设计订单金额为什么要做快照这里插一个坑。很多刚做项目的同学喜欢在订单表里只存一个房型ID金额直接去房型表里查。表面上看没问题但你想一个场景用户提交订单后还没支付房东看节假日快到了把房价从300改成了500。用户回头付钱系统一算按500收了用户肯定不干或者反过来用户已经付了300房东后台的统计对不上数。我的做法是订单表里不但存下单时的房型ID还要把当时的单价、每晚价格明细、总金额都存进去。也就是说订单金额是一个下单时刻的快照之后不管房型价格怎么变这笔订单结算时永远按快照走。这就等于把业务上的“价格锁定”语义落到了数据模型上。顺带说一下订单编号千万别用数据库自增ID直接给用户看容易暴露业务量。我一般用当前时间戳 随机数拼一个20位左右的编号前缀加“MS”民宿区分业务类型这样用户咨询售后的时候客服拿到订单号也能一眼识别。2.3 民宿和景点的关联城市匹配而不是硬关联景点附近民宿怎么推荐这里有两种做法。第一种是给民宿表加一个hotspot_id字段直接关联某个景点逻辑最简单但一个民宿附近往往有多个景点这种设计太死板。第二种是我最终采用的民宿有省市区字段景点也有城市字段用户点进某个景点详情页时直接按城市做匹配再按距离或评分排序。如果你想做得更细腻一点可以在民宿表里加distance字段数据录入时通过地图API把民宿和景点的经纬度坐标算好距离存下来也可以用经纬度在SQL里实时算距离。前者效率高但数据可能与实际有偏差后者数据准但对索引不友好。我当时的做法是城市精确匹配 页面上标注“距XX景点约X公里”这个距离在地图接口回调时批量算好冗余到民宿表后续查询就很快。3. Spring Boot后端关键实现3.1 项目初始化的版本选择别一上来就上3.x先给一个忠告如果你用的是JDK 8或者你手里的教程、参考代码大多是两三年前的建议直接用Spring Boot 2.7.x不要上3.x。原因很简单Spring Boot 3.0之后有几处大改动包括javax命名空间整体迁移到了jakarta、最低要求JDK 17、很多第三方starter的兼容性也还没完全跟上。我见过太多人照着新项目向导创建完Spring Boot 3.x项目结果跑起来全是ClassNotFoundException一脸懵。我这边实际用的组合是JDK 8 Spring Boot 2.7.18 MyBatis-Plus 3.5.x MySQL 8.0 Maven 3.8。JWT用jjwt密码加密用Spring Security Crypto里的BCrypt工具类不引入完整的Spring Security——因为做前后端分离项目完整Security的过滤器链配置反而容易把人绕晕。3.2 统一响应结构和全局异常处理前后端分离项目最忌讳后端报错时直接抛一堆堆栈给前端前端根本没法处理。我的习惯是定义统一的响应体public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ... } public static T ResultT error(String message) { ... } }code约定为200表示成功400表示参数错误401表示未登录403表示无权限500表示服务端异常。前端Axios拦截器统一判断codecode不是200就直接弹出message不用每个页面单独写错误处理逻辑。配合全局异常处理用RestControllerAdvice把业务异常、参数校验异常、兜底异常分开处理。这里有个细节参数校验别手动写一堆if判断直接用javax.validation的NotNull、NotBlank注解配上Validated触发省时省力还统一。3.3 JWT登录认证与ThreadLocal传递用户信息民宿网站涉及下单、评价、收藏这些操作必须做登录认证。我选择的方案是JWT无状态认证流程是这样用户登录成功后后端生成一个JWT Token载荷里放userId和role有效期设24小时返回给前端。前端把Token存在localStorage里每次请求在请求头加Authorization: Bearer 。后端写一个拦截器拦截需要登录的接口比如以/api/user/、/api/order/开头的解析Token验证通过就把userId放进ThreadLocal。自定义注解RequireLogin标记在Controller方法上拦截器里读取注解判断是否要校验身份这样不需要校验的接口比如民宿列表页可以完全不受拦截灵活性高很多。ThreadLocal这里要特别注意请求结束或异常时一定要remove清理否则Tomcat线程池复用会导致数据串号。这个坑经典且隐蔽排查起来特别耗时我在这上面栽过跟头。3.4 民宿搜索与筛选的实现思路民宿列表页是用户端流量最大的页面搜索筛选条件常见的有这些关键字匹配民宿名称和简介、所在城市、入住日期、离店日期、入住人数、价格区间、评分门槛、距离景点。日期这块有个业务逻辑容易被忽略前端传了入住和离店日期后端除了校验日期合法性还要记得把日期传下去做库存过滤。比如用户搜9月20到9月22入住那就要把这两个日期之间所有房型都有库存的民宿筛选出来。SQL层面用NOT EXISTS子查询排除那些在入住时段内库存不足的房型。逻辑不复杂但漏掉这个条件用户下单时才发现没房体验就很差。价格区间和评分用MyBatis-Plus的LambdaQueryWrapper就能搞定关键字搜索用LIKE注意把空条件判掉别让SQL里出现多余的AND。3.5 创建订单与库存防超卖订单创建是整个系统最核心的接口涉及事务必须原子化。我的步骤是校验用户登录状态。根据房型ID查房型校验是否存在、是否上架。计算入住天数从入住日期到离店日期每一天检查库存。按“日单价之和 x 房间数”计算总金额节假日价格覆盖逻辑在价格日历里算。锁定库存扣减该时间段每一天的库存量。插入订单记录状态置为0待支付。生成支付二维码或调起前端“模拟支付弹窗”。防超卖怎么做关键在扣库存那条SQL上。不能用“先查库存再判断再更新”这种非原子操作而要用带条件的UPDATEUPDATE t_room_stock SET stock stock - #{num} WHERE room_type_id #{roomTypeId} AND date #{date} AND stock #{num}影响行数为0就说明库存不足。配合整个方法上加Transactional要么全部成功要么全部回滚这样才能保证不会出现同一间房同一天被两个人同时订走的情况。3.6 图片上传本地存储还是OSS民宿要传封面图、轮播图用户要传头像图片上传是标配功能。开发阶段我用本地存储在项目里配置一个虚拟路径映射上传的文件放到服务器磁盘目录直接通过URL访问。部署到生产再考虑接阿里云OSS或七牛云。这里有一个非常容易被忽略的问题上传接口的请求体大小限制。Spring Boot默认单次请求最大1MB手机拍的图片随便都是好几MB不调大会直接报错。需要在application.yml里把spring.servlet.multipart.max-file-size和max-request-size调大比如10MB和20MB。另外图片上传后一定要做文件名重命名别直接用用户上传的文件名会有几类问题中文文件名乱码、重复文件名覆盖、还有安全风险。我习惯用UUID原扩展名拼一个文件名干净省心。4. Vue Node.js前端实战要点4.1 Node.js环境配置那些卡住新手的坑Vue项目要跑起来第一步是装Node.js。搜索“nodejs安装及环境配置”时可以找到官网的LTS版本安装包建议直接装LTS不要追求最新版。装完之后验证node -v npm -v这里有个Windows用户特别容易踩的坑执行npm命令时提示“无法加载文件...因为在此系统上禁止运行脚本”。这不是npm坏了是PowerShell执行策略默认禁止运行脚本。解决办法是用管理员身份打开PowerShell执行Set-ExecutionPolicy RemoteSigned选Y确认就行。另外建议提前设置npm镜像源国内直接连npm官方源下载依赖慢到怀疑人生npm config set registry https://registry.npmmirror.com我在整个项目开发过程中Node.js用的是18.x LTS版本配合Vite 4构建前端项目Vue版本是3.4。这里也要提醒一句Node版本不要过低Vite较新版本对Node版本有要求如果启动时报ERR_OSSL_EVP_UNSUPPORTED这类错误根因就是Node版本和Vite/Crypto模块不兼容。4.2 前端工程结构路由、状态管理、请求封装Vue项目我用的标准结构大致分几块views放页面组件、router放路由配置、store放状态管理、api放接口请求封装、components放公共组件。路由配置这一块民宿预订网站至少要有这些页面首页、民宿列表页、民宿详情页、登录注册页、个人中心含订单列表/收藏夹/评价、房东工作台民宿管理/房价日历/订单管理、管理后台页面。路由守卫要做两层第一层未登录用户不能进个人中心第二层非房东角色不能进房东工作台非管理员不能进后台。Axios封装把baseURL统一指向/API前缀请求拦截器里自动带上Token响应拦截器里统一处理业务码。这样每个页面的业务代码只需要关心数据本身。状态管理我用Pinia主要存两类数据当前登录用户信息和全局配置比如景点城市列表。注意刷新页面后Pinia数据会丢失所以用户信息要从localStorage里恢复或者专门写一个初始化逻辑。4.3 民宿详情页日期联动房价是核心交互民宿详情页是整个前端最有技术含量的一个页面核心是选日期、看价格、下单这个交互链路。我的实现思路是顶部分为图片轮播区、民宿基本信息区、房东信息和操作区。中间放房型列表每个房型卡片显示名称、面积、可住人数、床型、门市价和售价。房型卡片里嵌入日期选择器默认选中入住和离店日期用户选完日期后前端实时请求后端查该房型在对应日期段内每晚价格和剩余库存并把可订状态标记出来。这里用Element Plus的DatePicker组件配上disabled-date函数把过去日期和已经满房的日期禁掉用户根本选不到没房的日期体验会好很多。总价的计算放在前端做每晚价格之和乘以房间数但最终以后端订单接口返回的金额为准前端算出来只是为了展示。4.4 前后端联调跨域和代理配置开发阶段最常见的问题就是跨域。前后端分离前端跑在5173端口Vite默认后端跑在8080端口直接请求肯定跨域。解决方案很多我最推荐前端开发服务器做代理。Vite项目在vite.config.js里配置server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, // 后端接口没有/api前缀这里需要重写 // rewrite: (path) path.replace(/^\/api/, ) } } }这里有一个特别容易迷惑的点如果你后端接口路径就是/api开头那不需要rewrite如果后端路径没有/api前缀而前端统一在请求地址上加了这个前缀就必须配合rewrite去掉否则会404。我建议前后端约定统一加/api前缀后端Controller的RequestMapper里都带这个前缀前端代理只做转发不做重写逻辑最简单不容易出问题。4.5 订单流程的前端处理模拟支付与状态刷新支付这块我没有对接真实第三方支付因为对个人项目来说申请商户号很麻烦。我的做法是用户提交订单后跳转到订单确认页展示订单详情和“去支付”按钮点击后弹出支付弹窗模拟微信/支付宝扫码界面倒计时几秒后自动标记支付成功同时轮询后端订单状态接口拿到已支付结果后跳转到订单列表页。这种方式的好处是不依赖外部环境项目跑起来就能完整演示全流程面试或答辩时可以讲清楚“如果对接真实支付只需在后端支付回调接口里替换成微信/支付宝的验签逻辑”并不影响整体架构。5. 旅游民宿场景的特色业务设计5.1 房价日历节假日调价怎么做民宿和标准酒店有个很大的区别——价格浮动特别频繁。淡季200块没人住五一直接翻倍还订不到。如果价格只写在房型表里一个固定售价业务上完全不够用。我的方案是做一个房价日历表按房型、日期、特殊价格、当日库存四个维度存储。默认情况查房型基础价格如果房价日历里有当天记录就按日历价格来。房东在后台用日历组件批量设置某段时间的价格和库存比如国庆七天把某间山景房从380改成680一键覆盖比改基础价灵活太多。这个表结构很简单主键、房型ID、日期、价格、库存、是否覆盖默认价。查询当前房型某段时间的价格直接INNER JOIN日期范围返回一个日期价格的Map前端日历组件按天渲染。要注意设置价格时做后端校验起始日期不能小于今天价格必须大于0覆盖范围别写反start和end。5.2 民宿审核流状态机思维贯穿到底很多民宿预订项目民宿表就一个status字段上架下架随便改没有任何审核流程。这在实际业务里不成立——平台需要对民宿信息合规性和图片真实性负责。我的做法是民宿状态做成一个微型状态机编辑中 - 待审核 - 已上架 - 已下架审核驳回可以回到编辑中。房东提交审核后民宿在用户端不可见管理员审核通过后自动上架。房东想编辑已上架的民宿系统先把它置为“编辑中”并强制下架等再次提交审核通过后重新上架。这个状态机逻辑用一组接口加一个状态字段就能实现但必须把状态流转的约束写清楚防止用户直接调用接口把状态改成已上架绕过审核。5.3 订单状态机从下单到入住的全链路订单状态是整个系统的主动脉我的状态定义是待支付 - 已支付待入住 - 已入住 - 待评价 - 已完成另有已取消作为分支。用户支付后如果没入住房东可以取消用户也可以在规定时间前取消。订单完成后用户可以评价评价完订单状态才真正终结。前端不同状态下按钮的显隐完全依赖状态字段。比如待支付显示“去支付/取消订单”已支付待入住显示“申请退订/联系房东”已入住显示“确认退房”待评价显示“去评价”。后端每个状态流转接口都要校验当前状态是否符合前置条件比如已取消的订单不能直接改成已完成否则会出现数据不一致。5.4 站内消息通知让系统“活”起来民宿预订里用户下单后要通知房东、用户支付成功后要通知房东确认、房东确认入住后要通知用户我们项目里做了一个简单的站内消息表谁给谁发、关联订单、是否已读个人中心的消息图标上做个红色小角标点进去能看列表。刚需通知场景做完后还可以扩展一下用户收藏的民宿降价了系统自动发通知新民宿上线后给收藏过同城景点的用户推送。这些功能不难做但会让项目显得完整很多写简历时也更有亮点。6. 后端常用工具链与开发提效6.1 MyBatis-Plus的坑与技巧项目用MyBatis-Plus做ORM核心是有现成的CRUD方法单表操作几乎不用写SQL。几个常用的偷懒技巧可以分享一下主键策略全局配置成ASSIGN_ID用雪花ID而不是数据库自增避免ID暴露业务量也方便以后分库分表。自动填充字段create_time、update_time这种字段用TableField(fill FieldFill.INSERT/INSERT_UPDATE)配合MetaObjectHandler自动填充不用每个接口手动set。逻辑删除配置logic-delete-field为deleted字段删除操作自动变成UPDATE deleted1查询自动过滤已删除数据。民宿下架、用户注销这类场景特别好用。分页用PaginationInnerInterceptor配置好之后Page对象直接传入Mapper方法返回的IPage里有总条数和当前页数据。6.2 接口文档Apifox比手写Word快十倍很多个人项目没有接口文档意识前后端联调全靠问。这里推荐直接用Apifox或者Postman写好一个接口自动生成文档前端直接在工具里看参数、调试、mock数据。对我们的项目来说另一个好处是Apifox支持从数据库表结构自动生成一部分接口定义省了一堆手写文档的时间。做项目答辩或者给同事演示的时候直接打开API文档页面比对着代码讲要专业得多。6.3 Spring Boot的YML配置加密记一个与网搜热词“springboot yml密文”相关的话题。在项目里数据库密码、JWT密钥这些敏感信息直接写在application.yml里是安全问题。开发环境无所谓但如果以后部署到生产环境配置文件要传到服务器密钥泄露风险很大。简单方案是用jasypt-spring-boot插件配置里的密码用ENC(密文)代替启动时通过环境变量传入解密密钥。密文可以用工具类生成。虽然开发阶段可以不用但知道这个方案会让你在谈项目落地时多一个安全加分项。注意jasypt版本要和Spring Boot版本匹配尤其是Spring Boot 3.x需要引入对应版本。7. 典型问题排查与避坑技巧实录7.1 问题速查表这几类问题是做这个项目过程中最容易遇到的我整理成了一个速查表。问题现象常见原因解决方案npm命令报禁止运行脚本PowerShell执行策略限制管理员执行Set-ExecutionPolicy RemoteSignedVite启动报opensslErrorStackNode版本与Vite/Webpack不兼容升级Node到18或降低构建工具版本后端接口前端访问404跨域代理配置的rewrite没做好检查pathRewrite和后端路径前缀是否匹配前端请求跨域后端没有允许跨域或代理配置错误开发环境优先用Vite代理而非后端CORS创建订单库存出负数扣库存SQL没加stock num条件使用原子UPDATE并判断影响行数JWT拦截后ThreadLocal数据串号请求结束后没remove ThreadLocalfinally块里调用remove方法日期参数报400前端传的日期格式和后端LocalDateTime不匹配统一使用yyyy-MM-dd字符串后端用DateTimeFormat上传图片失败Spring Boot默认文件大小限制调大multipart配置参数Maven依赖下载慢默认中央仓库访问慢settings.xml配置阿里云镜像民宿改了价格订单金额跟着变订单表没有做金额快照订单表冗余下单时单价和总金额7.2 一个隐蔽的Bug订单超时未支付库存一直占着做过一段时间后你会发现用户下单但没支付库存被扣掉了如果一直不取消这间房就卖不了了。真实业务里要有超时自动取消机制。实现方式我推荐用延迟任务方案。用户下单时往Redis里写一条带过期时间的Key比如order:{orderId}过期时间30分钟同时开启Key过期监听或者用一个定时任务扫描订单表里的待支付订单超过30分钟自动取消并回补库存。定时任务的方案理解成本低我用的是Spring的Scheduled注解每5分钟扫一次把超过30分钟还没支付的订单取消掉恢复库存再做一次消息通知。这个逻辑不算复杂但把“占着茅坑不拉屎”的业务问题解决了系统的可用性明显上一个台阶。7.3 项目部署的经验总结开发完成后要部署演示建议前后端分开打包后端Spring Boot打成jar包放在服务器上用java -jar运行前端Vue项目执行npm run build生成dist目录用Nginx托管配置反向代理把带有/api的请求转发到后端的8080端口。这样整个项目只需要一台服务器就能跑起来。部署时还有一个端口占用的问题。Spring Boot默认8080如果你本机已经有别的服务占用了可以临时改一下application.yml里的server.port。如果部署在云服务器上记得在安全组/防火墙里放行对应端口。7.4 踩过几次坑之后的一些心得做这类全栈项目我最大的体会是技术本身不是最大障碍业务状态管理才是。写代码之前把所有状态流转画清楚什么状态下哪个角色能做什么操作写到文档里落库字段怎么设计接口怎么命名一条条理清楚开发速度至少提升30%。另一个体会是前后端联调的共识。我踩过的最痛的坑是前后端对字段命名不统一后端返回created_at前端写的是createTime查错查到怀疑人生。这个项目里我定了一个规矩后端统一返回驼峰命名字段前端调用方严格遵守日期时间类型统一用字符串返回前端不做额外转换。规矩定在前面联调时能省下大量时间。做这个民宿预订网站我从数据库设计到后端接口从Vue页面到部署上线全程走完了一遍。现在回头看这套Spring Boot Vue Node.js的组合并不花哨但它足够稳、足够常规是非常典型的商用项目结构。如果让我再给一个建议那就是把订单状态和民宿状态这两个状态机吃透整个系统的骨架就拿捏住了。把库存扣减、价格快照这些问题想明白你的项目就已经超过了市面上绝大多数停留在“增删改查”层面的作业了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价