多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Spring Boot+Vue酒店点餐管理系统核心设计与实战解析

Spring Boot+Vue酒店点餐管理系统核心设计与实战解析 1. 项目梳理这套酒店点餐系统到底解决什么问题这几年我经手过好几个餐饮类管理系统从街边小馆的扫码点餐到连锁酒店的内部餐饮服务都接触过。后台私信里也一直有人问酒店里的点餐系统和普通餐厅的点餐系统到底有什么区别为什么有些项目看着功能齐全上线后却没人愿意用趁着这次整理一个基于 Spring Boot Vue 的酒店点餐管理系统我把自己做这类项目的完整思路、踩过的坑、值得复用的套路一次讲清楚。这套系统不算复杂但覆盖了一个典型的餐饮后台全链路菜品管理、桌台管理、用户点餐、订单流转、支付对接、数据统计。它解决的痛点也很明确——酒店客房送餐和餐厅就餐长期依赖人工喊单、手写小票、口头传菜高峰期漏单错单频发月底对账全靠翻单据。用系统替换纸质流程让前台、后厨、财务三端的数据打通这个价值但凡在酒店餐饮干过的人都懂。适合谁参考如果你是正在做毕业设计、个人项目需要一套结构清晰、能跑通前后端完整流程的餐饮系统代码这篇文章能帮你省不少找方向的精力如果你是刚转行做全栈开发、想了解 Spring BootVue 这类主流组合在业务系统里怎么落地可以重点看技术选型那一节哪怕你只是想让代码更规范一点后面关于接口设计、异常处理、状态机、JWT 鉴权的经验同样通用。下面我把这套系统的设计骨架、核心模块实现、参数计算思路以及我实际测试中遇到过的问题全部展开。这不是教科书式的流程复述而是我一个字一个字敲过、调试过、上线后还被运营提过需求的经验总结。2. 系统设计与技术选型为什么是 Spring Boot Vue2.1 业务建模先行别急着写代码很多人在做这类系统时第一反应是建表、写接口。我的习惯是先做业务建模把酒店点餐的完整场景在纸上画一遍。酒店点餐和独立餐饮店有一个显著区别住客既有在餐厅堂食的场景也有在客房内打电话或扫码点餐的场景。这意味着用户的身份体系不能只围绕“到店顾客”设计还要兼容“住客”这个角色。我在设计这套系统时把所有用户抽象成三类管理员酒店餐饮管理方、收银/服务员门店运营角色、顾客住客或到店散客。桌台状态、订单状态、支付状态这三套状态机是系统的心脏必须先定义清楚再动手。桌台我设计了空闲、占用、待清洁三个状态订单从待支付、已支付/制作中、待配送/待上菜、已完成、已取消、退款中等状态流转支付状态则单独维护避免和订单状态强耦合。为什么支付要单独拆开因为实际业务里存在“先用餐后结账”“挂房账”“部分退款”这些场景如果把支付和订单绑死后面每次支付渠道调整都要改订单主流程维护成本很高。这是我在第一个版本踩过的坑这里先提醒你。2.2 后端选型Spring Boot 不是唯一答案但确实最稳这个项目最终采用 Spring Boot 2.7 MyBatis-Plus MySQL理由很直接Spring Boot 的生态成熟度、自动配置机制和社区资料量对一个需要快速落地、后续可能增加功能的业务系统来说学习成本和维护成本最平衡。可能有人会问为什么不直接用 Spring Cloud单体应用阶段引入微服务是典型的过度设计。这套系统的并发量级在酒店场景下通常在几十到几百人同时点餐单体架构配合数据库连接池和 Redis 缓存完全能撑住。我见过太多个人项目因为追求“架构完美”把 Nacos、Gateway、Feign 全套搬上来结果部署时光修配置就花了两天业务功能却没写几个。技术选型的核心逻辑是先满足核心业务再考虑极端扩展性。MyBatis-Plus 的选择也同样务实。它把单表 CRUD、分页查询、逻辑删除这些高频操作封得很舒服同时保留了手写 SQL 的能力——报表统计那块我照样用自定义 XML 写复杂 SQL没有任何障碍。JPA 我也用过但国内团队和多数开源项目对 MyBatis 系列的熟悉度更高出问题好排查。2.3 前端选型Vue 3 组合式 API Element Plus 的平衡点前端我选的是 Vue 3 Vite Element Plus。为什么不是 Vue 2Element Plus 和 Vue 3 目前生态已经完全成熟组合式 API 对状态管理的组织方式比 Options API 清晰得多特别是订单列表、购物车这种多组件共享状态的场景用 ref/reactive 配合 Pinia 管理比在 Vue 2 里写 mixin 舒服太多。有人用 vue-element-admin 这类后台模板直接改我不反对但建议你先想清楚这类模板自带权限体系、多标签页、动态路由对个人项目来说一半功能你都用不上但它们的复杂度会拖慢你的理解速度。我的做法是手搭一个轻量级后台框架只保留路由、布局、Axios 封装、Token 管理、权限指令这几个核心模块。代码量不大但是每一行都是自己写的出了问题你能马上定位而不是在一个几千星的模板源码里大海捞针。Vite 的选型不用多解释冷启动速度和热更新体验比 Webpack 时代强了一个量级。开发依赖和构建配置简单还能对 Node 版本的要求极高这一点后面部署章节我会单独说。3. 数据库设计与核心模块拆分3.1 表结构设计思路与关键字段数据库我拆了这么几张核心表用户表、菜品分类表、菜品表、桌台表、订单表、订单明细表、购物车表、支付记录表。另外为了报表统计方便加了一张每日销售汇总表由定时任务生成快照。一张值得说的表是订单明细表。它不能只存菜品名称和数量还要冗余下单时的单价、折扣价、备注甚至菜品快照JSON 字段。为什么冗余这么多因为菜品价格、名称会变如果订单明细不保存快照一个月后查历史订单时看到的价格可能已经被最新菜品价格覆盖了财务对账会对不上。这是餐饮系统里最经典的“历史数据一致性”问题靠关联菜品表是解决不了的。订单号我采用日期自增序列随机数的组合方案比如20250614153000012345前8位日期、6位时间和4位随机数。这个设计能避免并发下的重复订单号同时方便按时间段模糊查询。很多人喜欢直接用自增 ID 当订单号但只要订单号泄露你的当日单量就暴露了而且商品系统里自增 ID 很容易被爬虫遍历所以我强烈建议业务主键和自增主键分离。菜品表里我保留了一个 sort 字段用于排序还有一个 status 字段控制上下架状态。这个简单设计在运营侧非常实用套餐调整、时令菜品下架不需要删数据一个 update 就搞定还保留了历史数据。逻辑删除配置在 MyBatis-Plus 里通过TableLogic实现所有查询默认过滤已删除记录。3.2 接口模块划分从 Controller 到 Service 的职责边界后端接口我按业务域拆成了几个大模块用户认证、菜品管理、桌台管理、点餐流程、订单管理、支付回调、数据统计。每个模块的职责边界遵循一个原则Controller 只做参数接收和结果封装不写业务逻辑Service 里放业务规则事务边界划在 Service 方法上Mapper 只做数据访问。这个三层结构听起来基础但我确实见过不少项目在 Controller 里直接查库的写法前期图快后期维护哭都来不及。举一个具体接口设计POST /api/order/create创建订单接口。它的入参包含桌台ID、就餐人数、订单明细列表菜品ID、数量、备注、支付方式。Service 层的逻辑依次是校验桌台状态、校验菜品是否在售、计算订单金额含折扣规则、生成订单主记录和明细记录、更新桌台状态。这四步必须放在同一个事务里任何一个失败都要回滚不能出现“订单创建了但桌台还是空闲”的状态不一致。并发场景下的库存或菜品可用量怎么处理我在菜品表加了一个 daily_limit 字段每日限量创建订单时使用UPDATE t_dish SET daily_limit daily_limit - 1 WHERE id ? AND daily_limit 0这种带条件更新的写法而不是先 select 再 update。这个操作利用数据库行锁天然避免了超卖问题比 Java 层加锁靠谱得多。实测下来即便几十个用户同时点同一道限量菜也不会出现超卖。3.3 用户端 H5 点餐页与后台管理的功能拆分这个系统实际包含两个前端界面用户点餐 H5 和后台管理端。H5 面向顾客核心流程是选择桌台或房间号、浏览菜品、加入购物车、提交订单、支付后台管理端面向管理员和收银员核心流程是菜品维护、桌台状态管理、订单审核与派单、接单出餐、数据统计。两个界面共用同一套 API但权限边界差异很大。H5 端走的是微信小程序或手机浏览器我用 JWT 做无状态认证用户扫码时通过桌台码携带一个临时 token 自动登录不需要输密码。后台管理端则采用账号密码登录 RBAC 权限控制管理员、收银员、后厨角色看到的菜单完全不同。这里有一个容易被忽略的坑H5 端如果直接用管理端的接口会暴露大量敏感接口。所以我把接口按“端”做了隔离用户端只暴露/api/portal/**前缀下的接口管理端走/api/admin/**在网关或拦截器层做白名单校验。这样就算用户篡改 token也访问不了管理端的数据。4. 核心流程实现点餐到出餐的全链路细节4.1 扫码点餐与桌台绑定的完整链路用户在酒店餐厅扫码后前端会解析二维码里的桌台编号比如T-2F-08同时向/api/portal/table/info发送请求后端根据桌台编号查询状态并返回桌台的会话 token。这个 token 和用户 ID 是解耦的只关联桌台维度有效期默认2小时——超过2小时用户重新进入点餐页需要再次扫码这个设计能有效防止占座不点单的情况。桌台会话 token 我放在 Redis 里key 是table_token:{tableNo}value 是桌台信息。为什么要放 Redis因为桌台状态会被收银员手动调整比如并桌、换桌如果把状态只存在 MySQL那么频繁的写库操作会造成锁竞争Redis 的原子性更新配合定期落库性能和一致性都能兼顾。点餐页的菜单是实时从后端拉取的前端做了分类 Tab 和滚动加载。有个细节每次加入购物车我都调用一次/api/portal/cart/add接口而不是纯前端本地存储。为什么因为顾客中途可能刷新页面、换设备继续下单如果购物车数据只存在 localStorage刷新就丢了。服务端保存购物车配合用户 token任何终端都能恢复购物车状态这个体验提升非常明显而且实现只多了一张表的事。4.2 订单价格计算、库存扣减与事务控制订单提交是整个系统最核心的接口我把它拆成六个关键步骤每一步都值得你细看校验桌台状态是否空闲已被占用的桌台不允许新创建订单从购物车读取菜品列表逐条校验菜品状态、每日限量按规则计算总价单价 × 数量叠加满减优惠比如满300减30、会员95折生成金额明细扣减库存带条件更新写入订单主表和明细表更新桌台状态为占用记录开台时间返回支付参数前端拉起支付。我把第2到第5步放在一个Transactional方法里传播级别默认 REQUIRED。这里有一个极易错的细节如果在第4步扣库存时失败抛了异常前面的校验和计算不会有副作用但 Redis 里的购物车数据不能跟着回滚——因为 Redis 的操作不在事务管理范围内。所以我在购物车清理上做了补偿逻辑事务提交成功后再通过事务同步器TransactionSynchronizationManager在 commit 后执行清空购物车操作如果事务回滚购物车不清空用户重新提交即可。这种“数据库事务 外部存储补偿”的思路在分布式系统里到处都用得上。价格计算时还有一个隐藏问题浮点数精度。金额计算一定不要用 double/floatJava 后端我用BigDecimal精度设为两位运算模式用ROUND_HALF_UP。数据库字段类型用 decimal(10,2)前端传给后端的金额参数后端一律重新计算绝不信任前端传来的总价——前端传的总价只做展示参考这个校验能防住绝大多数恶意刷单。4.3 支付模块对接从设计到沙箱实测支付是这个系统里最容易出“看起来能用、一上线就崩”的模块。我采用对接通用支付网关的方式之所以不用某一个支付平台的直连是因为酒店用户可能来自不同渠道有人用微信、有人用支付宝、还有境外用户可能用银行卡。对接一个支持多渠道的支付网关把微信、支付宝、银联收拢到一套回调接口里代码会清爽很多。支付流程用户在前端选择支付方式后端生成预支付单记录订单号、金额、渠道调用支付平台下单接口获取支付链接或二维码用户扫码后平台异步通知后端回调地址。回调接口收到通知后先验签再查单确认状态最后更新订单状态和支付记录。回调处理有一个必须遵守的原则接口要做幂等处理。支付平台的回调可能重复推送如果每次回调都执行“更新订单为已支付”的操作第二次执行时订单已经是已支付会造成状态混乱甚至重复发货。我的处理方式是支付记录表设置唯一约束订单号渠道回调时先尝试插入支付记录如果插入冲突说明已处理过直接返回成功响应不再重复更新订单状态。这个方案比查状态再更新的“check-then-act”更稳因为数据库唯一索引是强约束并发下也不会出问题。沙箱测试时我还特意走了几组异常数据金额不匹配的回调、签名错误的通知、重复回调、订单号不存在的回调。前两种情况直接拒绝并记日志重复回调靠幂等处理订单号不存在说明有人伪造回调记录风险日志并告警。对接支付平台时强烈建议你把所有回调日志存全出了问题拿日志和支付平台对账——这一步能在排查资金问题时帮你节省大量时间。4.4 后厨出餐、配送状态与异常处理闭环订单支付成功后状态变为“已支付/待制作”。这里涉及一个角色分派问题谁来处理这个订单我的设计是后厨大屏轮询“待制作”列表厨师点击“接单”后状态变为“制作中”出餐后点击“出餐”状态变为“待上菜/待配送”再由服务员或配送机器人送到对应桌台点击“送达”后订单变为“已完成”。这个流程里我加了一个超时机制如果订单支付成功后15分钟内没有厨师接单系统会自动通知管理员。实现方式不是定时任务而是通过 Redis 的键过期事件监听——创建订单时设置一个order_timeout:{orderNo}的 key15秒过期测试时缩短订阅 Redis key 过期事件触发检查。为什么不直接用定时任务扫全表因为全表扫描在高并发下对数据库压力很大而 Redis 键过期监听是精准触发只处理“确实超时”的订单效率高得多。异常处理闭环是另一个不能省的环节。菜品售罄、后厨拒单、配送异常这些情况单独改订单状态是不够的必须给用户一个明确感知。我在订单表加了 cancel_reason 字段记录取消/异常原因前端在订单详情页直接展示。用户对“为什么会这样”的感知直接决定了投诉率。5. 权限安全与前后端联调细节5.1 JWT 认证与刷新策略认证方案我用的是 JWT Redis 黑名单组合。登录成功后后端生成 access_token有效期2小时和 refresh_token有效期7天。前端 Axios 拦截器统一在请求头携带 access_token后端拦截器校验签名和有效期。这里有个业界常见的坑JWT 是无状态的一旦签发在有效期内无法主动失效。用户改密码、账号被禁用、退出登录这些场景都要求 token 立即失效。我的方案是把“需要主动失效的 token”的 jtiToken ID写入 Redis设置和 token 剩余有效期一致的 TTL。拦截器校验时先查 Redis 里有没有该 jti有就直接拒绝。这个方案比维护服务端 session 省内存同时比纯 JWT 更安全。详细解释一下jti 黑名单放在 Redis 里而不是把整个 token 状态存 Redis是因为一个用户可能同时存在多个有效 token多端登录只把被吊销的 jti 记下来内存占用小清理也方便。5.2 接口权限控制与参数校验管理端接口我使用自定义注解RequirePermission(dish:edit) AOP 拦截器实现 RBAC 权限控制。用户登录后后端从数据库查出该角色权限码集合放入 ThreadLocal 缓存。AOP 拦截器在校验到注解后从 ThreadLocal 取权限码判断是否放行。为什么用 AOP 而不是在拦截器里每次查库因为接口调用的高频场景下每次都查权限表会造成无谓的数据库开销一次登录查询后缓存权限变更时清理缓存即可。参数校验方面我引入 spring-boot-starter-validation实体类字段加NotNull、Size、DecimalMin注解Controller 入参加Validated。比如创建订单时tableId不能为空、itemList不能为空且大小不超过50quantity必须大于0、小于等于99。这些基础校验不要全部写在业务代码里用注解声明式处理业务代码重点处理跨字段、跨表的一致性校验代码会干净很多。5.3 跨域配置与 Axios 封装的实战细节前端本地开发是localhost:5173后端是localhost:8080必然要处理跨域。后端统一配置 CORS允许的来源我用白名单方式而非*并且 credentials 设置为 true。注意如果你既要跨域又要携带 CookieallowCredentials(true)和allowedOrigins(*)不能同时使用这会被浏览器拒绝。正确的做法是allowedOrigins显式列出前端地址或者使用allowedOriginPatterns(*)加allowCredentials(true)。Axios 封装我有几个建议请求拦截器统一注入 token、添加请求时间戳响应拦截器统一处理 HTTP 状态码和业务状态码比如后端约定{ code: 200, message: success, data: ... }业务错误 code 非200时前端直接 ElMessage 提示401 状态码统一触发刷新 token 逻辑刷新失败则跳转登录页下载文件的接口要设置responseType: blob否则返回的二进制流会被当作 JSON 解析这是很常见的坑。5.4 联调中的接口文档维护前后端联调时接口文档我选择 Apifox 管理它支持导出 OpenAPI 规范还能一键生成 Mock 数据。开发流程是后端先定义接口文档前端根据文档类型定义生成 TypeScript 类型再开始写页面。接口变更时文档同步更新前端能立刻感知。这个流程极大减少了“接口字段名不一致”带来的扯皮。这里分享一个我个人的习惯每个接口的文档里我会把异常场景列全。不仅写“成功返回什么”还要写“菜品不存在返回什么、桌台被占用返回什么、库存不足返回什么”。因为前端真正难处理的不是正常分支而是各种异常分支的提示文案和交互逻辑。文档里提前列清楚联调时可以减少沟通成本。6. 性能优化与部署上线实录6.1 数据库索引优化与慢查询排查系统联调后我在一千多条菜品、三万多条订单的数据量下做了性能测试发现一个明显问题订单列表分页查询耗时接近 800ms无法接受。用慢查询日志定位后发现是订单状态查询条件status 0 AND create_time BETWEEN ? AND ?没有走索引。优化方案有三步给order_status和create_time建联合索引idx_status_time(status, create_time)因为查询条件是等值匹配 范围匹配把订单明细表的order_id建普通索引因为明细表的关联查询高频报表统计那段 SQL 增加日维度分组提前按天聚合避免全表扫描。优化后同一个页面查询耗时降到 120ms 左右日常使用完全够。索引不是越多越好每个索引都会拖慢写入速度我只为高频查询字段建索引。6.2 缓存策略哪些数据适合 Redis我用 Redis 缓存了三个部分菜单数据菜品分类菜品列表、桌台状态、token 相关会话。菜单数据几乎是只读的菜品修改后通过管理端主动删除缓存下次请求重新回源数据库这个“Cache Aside”模式最简单可靠。为什么菜单数据用 Redis 而不是本地缓存因为这套系统会部署多实例前端部署在一台、后端可能扩容到两台本地缓存会导致两台机器数据不一致用 Redis 集中管理天然一致。缓存 key 的设计我采用menu:list:{categoryId}失效时间设置30分钟商品信息变更时删除对应 key。6.3 后端打包与前端构建配置文件部署我采用前后端分离方式。后端 Jar 包用 Maven 打包关键要处理 application.yml 的环境配置本地开发用application-dev.yml生产环境用application-prod.yml通过启动参数--spring.profiles.activeprod切换。数据库密码等敏感信息放入环境变量而不是写死在配置文件中。前端项目构建时Vite默认构建到dist目录其中/api开头的请求需要在生产环境通过 Nginx 反向代理转发到后端服务。Nginx 配置里有两个关键点一是proxy_set_header Host $host否则后端拿到的 Host 是内网地址回调地址会错二是 gzip 压缩开启静态资源体积能减少 60% 左右。构建 Vue 项目时有一个常见的 Node 版本兼容问题Vite 需要 Node 14.18Vite 4 需要 16如果你机器的 Node 版本过低安装依赖时会报 ERESOLVE 等错误。建议用 nvm 管理多个 Node 版本切换到所需版本后再跑npm install。6.4 Docker Compose 一键部署的实践为了减少环境差异导致的问题我写了 Docker Compose 文件把 MySQL、Redis、后端应用、前端 Nginx 四个容器编排在一起。前端 Nginx 镜像里我用多阶段构建第一阶段用 Node 镜像构建静态文件第二阶段用 Nginx 镜像拷贝 dist 目录并写入 nginx.conf。这样部署只需要在服务器上执行一条docker compose up -d资源也少占用非常省心。需要注意的一个坑容器内 MySQL 的数据卷要挂载到宿主机如果容器删了重建数据不会丢。/var/lib/mysql目录务必映射出来。第一次启动时数据库初始化和数据导入我放在/docker-entrypoint-initdb.d/目录下MySQL 容器首次启动会自动执行该目录下的 SQL 脚本后续启动不会重复执行。7. 测试要点与常见问题排查7.1 核心功能测试用例设计功能测试我按两个维度设计正常流程和异常流程。正常流程包括用户选菜加入购物车并下单支付订单状态按预期流转到已完成管理员上下架菜品用户端菜单实时刷新桌台换桌、并桌后订单关联对应桌台。异常流程包括库存不足时下单是否被拒绝、桌台已占用时是否还能下单、支付回调重复推送是否幂等、token 过期后访问接口是否返回 401。测试数据我建议先构造一万条量级的测试数据用 Jmeter 或 Postman 做并发测试而不是只用一两条数据验证接口通不通。并发场景下更容易暴露接口的并发安全问题。7.2 高频问题速查与解决实录我在联调阶段遇到并解决过这些问题整理出来供你排雷问题现象排查思路解决方案前端请求后端接口 404确认请求地址是否进入 Nginx 正确路由检查后端接口前缀是否匹配统一路径前缀前后端约定一致支付回调一直失败检查回调地址是否为外网可访问地址排除内网穿透问题使用内网穿透或部署到公网服务器测试订单创建成功但桌台状态未改变检查事务是否提交排查事务同步管理器逻辑将桌台状态更新纳入同一事务并发下单同一菜品超卖检查扣库存是否使用乐观锁改用带条件更新 SQLRedis 锁补充前端登录后刷新 token 失效检查 refresh_token 轮换机制登录后返回双 token每次刷新后重新签发中文乱码检查数据库连接串是否带字符集参数连接串添加useUnicodetruecharacterEncodingutf87.3 安全加固与上线前检查清单上线前我做了一次安全自查以下几点是你也应当注意的所有接口在网关层统一做限流防止恶意刷接口密码加密使用 BCrypt不能使用 MD5MD5 已经被彩虹表攻破管理端接口启用操作日志记录谁改了价格、谁下架了菜品都有记录前端隐藏敏感字段如支付回调地址、密钥绝对不进入前端代码数据库备份脚本每周执行一次备份文件异地保存。8. 这版项目做完我后续想扩展的方向如果你也想在酒店点餐系统基础上继续完善有几个方向我提前帮你踩过规划接入小程序端可以把客房扫码点餐体验做得更轻跟公众号打通还能做会员积分对接酒店 PMS 系统实现挂房账、房态联动能进一步减少前台操作出餐单打印、小票自动打印也需要接上热敏打印机协议后厨 KDS 大屏的排队算法、压单预警也值得深入优化。从我个人的角度说做管理系统这类项目最大的收益不是学会某个框架API而是把业务规则、状态流转、异常处理这些“看不见的设计”想透彻。功能能跑起来是第一步能在并发和异常条件下依然不出错才是系统真正能上线使用的关键。这类经验没有捷径只能靠一次次测试、排查、复盘才能沉淀下来。希望这篇关于 Spring Boot Vue 酒店点餐管理系统的分享能帮你少走几步冤枉路。
返回列表