做旅游民宿类项目,这几年我前前后后经手过好几个版本,从最早的单体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 Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { ... } public static <T> Result<T> error(String message) { ... } }code约定为200表示成功,400表示参数错误,401表示未登录,403表示无权限,500表示服务端异常。前端Axios拦截器统一判断code,code不是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上。不能用“先查库存再判断再更新”这种非原子操作,而要用带条件的UPDATE:
UPDATE 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 deleted=1,查询自动过滤已删除数据。民宿下架、用户注销这类场景特别好用。
- 分页用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 RemoteSigned |
| Vite启动报opensslErrorStack | Node版本与Vite/Webpack不兼容 | 升级Node到18+或降低构建工具版本 |
| 后端接口前端访问404 | 跨域代理配置的rewrite没做好 | 检查pathRewrite和后端路径前缀是否匹配 |
| 前端请求跨域 | 后端没有允许跨域或代理配置错误 | 开发环境优先用Vite代理而非后端CORS |
| 创建订单库存出负数 | 扣库存SQL没加stock >= num条件 | 使用原子UPDATE并判断影响行数 |
| JWT拦截后ThreadLocal数据串号 | 请求结束后没remove ThreadLocal | finally块里调用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的组合并不花哨,但它足够稳、足够常规,是非常典型的商用项目结构。如果让我再给一个建议,那就是把订单状态和民宿状态这两个状态机吃透,整个系统的骨架就拿捏住了。把库存扣减、价格快照这些问题想明白,你的项目就已经超过了市面上绝大多数停留在“增删改查”层面的作业了。