新闻详情

新闻详情

首页 / 资讯中心 / 详情

在线订餐系统从0到1:Spring Boot+微信支付+Redis实战全解析

发布时间:2026/9/2 22:39:23来源:尧图网络
在线订餐系统从0到1:Spring Boot+微信支付+Redis实战全解析
简介在线订餐系统HTML5前端模板面向Web前端开发者与全栈学习者用于快速搭建具备现代交互体验的订餐平台。模板集成了餐厅菜单展示、订单提交、配送状态跟踪等核心模块重点演示响应式布局、移动端适配、支付接口预留、后端API对接等关键设计可作为课程设计、毕业项目或商业项目起步原型。资源包共194个文件以120余张PNG切图、30张JPG素材为主配套JavaScript交互脚本、GIF动效、HTML页面与CSS样式表另含字体图标与地图数据文件整体仅3.12MB结构紧凑、便于部署。目前已有486人学习使用适合需要参考完整前端实现的中级开发者。通过分析模板源码可掌握Bootstrap栅格布局、Ajax异步交互、表单验证、性能优化及HTTPS安全传输等实战技能同时了解SVG图标、Web字体加载与跨浏览器兼容处理对理解现代前端工程化流程有直接帮助。 前阵子帮人做了一套在线订餐系统从需求梳理到部署上线前后折腾了三周。这个项目看起来就是个常见的业务系统但真正做进去才发现坑基本都藏在订单、支付、库存这些核心链路里。这篇文章把这套系统的完整落地过程掰开揉碎讲一遍包括技术选型、数据库设计、关键流程实现还有那些测试环境根本暴露不出来的实际问题。不管你是准备拿它当毕业设计还是想给自己的简历加一个完整项目或者公司食堂真需要这么一套东西都可以参考。1. 项目整体定位与功能边界梳理1.1 这套系统到底要解决什么问题在线订餐系统本质上是一个连接用户、商家、骑手或者自提柜三方的交易平台。用户侧要能浏览菜单、加购、下单、支付、跟踪订单状态商家侧要能管理菜品、上下架、接单、出餐管理后台要能看到所有订单数据、处理退款、管理用户和商家账号。如果还涉及配送就得有骑手端或者配送调度模块。我这次做的是一个小型餐饮连锁品牌的在线订餐系统服务范围覆盖三家门店。核心诉求很明确顾客通过小程序在线下单支付门店后厨自动接单出餐自提和外卖两种履约方式。这个定位决定了系统不需要太复杂的营销功能但订单状态流转和支付对账必须做得非常扎实。1.2 角色划分与权限设计系统涉及四种角色每个角色的功能边界必须从一开始就划清楚。用户端包含注册登录、浏览菜品、下单支付、订单查询、售后申请商家端包含菜品管理、订单接单/拒单/出餐、营业状态设置、数据统计管理后台包含全平台订单监控、退款审核、用户/商家管理、基础数据配置骑手/自提环节则通过订单状态同步来完成履约闭环。权限设计上我用了简单的RBAC模型角色表、用户表、角色用户关联表再加上一个权限点表。实际开发中不需要做到Spring Security那种粒度的细权限控制但菜单级别的权限区分还是要有否则商家端和管理后台混在一起后面越做越乱。2. 技术选型为什么是这套组合不是别的2.1 前端小程序 管理端Web双端并行用户端选了微信小程序原因很直接不需要下载App微信生态内打开即用支付直接走微信支付能力对中小餐饮商家是门槛最低的方案。小程序端用原生语法开发没有引入额外框架——对于这种页面数量在30个左右的项目原生小程序完全够用引入Taro或者uni-app反而增加一层编译成本和调试复杂度。商家端和管理后台合并成了一个Web项目用Vue3 Element Plus实现。Vue3的组合式API在这种中后台场景下写起来很舒服逻辑复用比Options API直接很多。Element Plus的表格、弹窗、表单组件基本覆盖了后台管理的全部需求不需要自己造轮子。2.2 后端Spring Boot依然是中小型业务系统的最优选后端选了Spring Boot 2.7 MyBatis-Plus MySQL Redis的组合。为什么不用Spring Cloud那套微服务原因很简单业务复杂度还没到需要拆分的程度三个门店的订餐量单机部署的Spring Boot在性能和可靠性上都绰绰有余。引入微服务等于给自己增加服务发现、配置中心、分布式事务这些额外负担对项目本身没有实际收益。MyBatis-Plus比原生MyBatis舒服的地方在于单表CRUD不需要写XML代码生成器可以直接生成实体类、Mapper、Service、Controller一层省掉大量重复劳动。复杂查询仍然可以手写SQL控制性能和灵活性都有保障。Redis在这个项目里承担了三个职责缓存菜品列表和门店信息、实现购物车后面会详细说为什么用Redis而不用数据库、处理分布式锁主要是库存扣减和防止重复下单。订单数据没有放在Redis里因为订单需要严格持久化和事务保障缓存订单反而增加一致性维护的复杂度。2.3 为什么放弃了一些看起来更高级的方案下单的时候很多人第一反应是上消息队列比如RocketMQ或者RabbitMQ觉得异步削峰才专业。但这个项目的峰值QPS撑死也就几十数据库完全扛得住。引入MQ意味着还要部署Broker、处理消息可靠投递、解决消息乱序这些复杂度在这个量级下都是负资产。文件存储没有用FastDFS或者MinIO就用了服务器本地目录加Nginx映射。菜品图片量不大一台服务器硬盘绰绰有余后续If需要迁移对象存储只需要改一个文件上传的工具类就好。做技术选型最忌讳的就是看着厉害就上一切应以实际业务量级来定。3. 数据库设计一个在线订餐系统最核心的几张表3.1 订单主表与订单明细表的设计要点订单相关表是整个系统的心脏设计得好不好直接决定后续开发的顺畅程度。订单主表我命名为orders避免用order这个SQL关键字核心字段如下CREATE TABLE orders ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id bigint(20) NOT NULL COMMENT 下单用户ID, store_id bigint(20) NOT NULL COMMENT 门店ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, pay_amount decimal(10,2) NOT NULL COMMENT 实付金额, discount_amount decimal(10,2) DEFAULT 0.00 COMMENT 优惠金额, order_status tinyint(4) NOT NULL COMMENT 订单状态0待支付 1已支付 2制作中 3待取餐/配送中 4已完成 5已取消 6退款中 7已退款, pay_type tinyint(4) DEFAULT NULL COMMENT 支付方式1微信支付, pay_time datetime DEFAULT NULL COMMENT 支付时间, receiver_name varchar(50) DEFAULT NULL COMMENT 收货人姓名, receiver_phone varchar(20) DEFAULT NULL COMMENT 收货人电话, receiver_address varchar(255) DEFAULT NULL COMMENT 收货地址, remark varchar(255) DEFAULT NULL COMMENT 订单备注, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_store_id (store_id), KEY idx_status (order_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;订单号不直接用自增ID而是独立生成一个业务订单号这个细节很重要。自增ID暴露给用户后会产生两个问题一是用户可以猜测订单数量有数据泄露风险二是多笔订单号连号后面做对账和外部分发的时候不够灵活。订单号生成规则我用的是时间戳门店编号随机数例如20250608103045000102在并发量不高的场景下足够保证唯一。订单明细表record每一条代表订单中的一个菜品包含菜品ID、菜品名称下单时快照防止菜品改名影响历史订单、数量、单价、总价。菜品名和价格必须做快照不然商家改了菜单历史订单数据就乱了这是交易系统的基本素养。3.2 购物车的Redis数据结构选择购物车如果存数据库每一次增删改都要落库频繁的读写对数据库压力不小。用Redis合适得多我把每个用户的购物车存成一个Hashkey是cart:userIdfield是菜品IDvalue是数量。HSET cart:1001 15 2 HSET cart:1001 22 1 HGETALL cart:1001选择Hash结构而不是String存JSON原因是Hash可以单独修改某一个菜品的数量不需要整个购物车取出来反序列化再存回去。批量查询菜品价格和库存时拿到field列表后一次性查数据库性能也足够好。购物车加入Redis的另一个好处是天然支持过期时间用户长时间不操作自动清空。3.3 菜品表与库存字段设计菜品表的核心字段包括菜品名称、分类ID、价格、图片、描述、月销量、状态上架/下架、库存。库存字段我用了int类型每次下单扣减时使用乐观锁UPDATE dish SET stock stock - #{quantity}, sold sold #{quantity} WHERE id #{dishId} AND stock #{quantity}这里用stock quantity作为条件配合受影响行数判断天然防超卖。并发场景下单条UPDATE语句是原子的MySQL的行锁保证不会有两个请求同时扣减同一份库存。4. 从下单到配送核心业务流程的完整串联4.1 下单接口的完整处理链路用户点击下单后后端接口按下面的流程处理。第一步校验购物车非空并锁定用户ID防止越权访问他人购物车。第二步遍历购物车items查询菜品当前状态和库存发现已下架或库存不足立即返回错误。第三步重新计算订单金额实际项目中前端只做展示金额一律以后端计算为准。第四步扣减库存用3.3节那个乐观锁UPDATE失败则说明库存被抢光了。第五步生成订单主表和明细表先插主表拿到订单ID再批量插明细。第六步清空Redis购物车。第七步调用微信支付统一下单接口拿到支付参数返回给前端。这里有一个重要的细节库存扣减和订单创建不是原子的。如果第五步失败库存已经被扣了需要补一个补偿机制。我在实践中用的方案是扣库存和生成订单放在同一个事务里保证要么都成功要么都失败。购物车清空在事务提交之后执行因为即使购物车删除失败用户重新进入购物车还能看到之前的商品最坏情况是重复下单不会出现订单有了但购物车没清的数据不一致。4.2 支付回调处理幂等性是最容易忽略的坑微信支付的支付结果是以异步回调通知商户后端为准的。回调接口的处理逻辑直接决定资金安全和对账准确性。核心代码如下PostMapping(/wxpay/notify) public String handleWxPayNotify(RequestBody String xmlData) { // 1. 验签 WxPayNotifyResult result wxPayService.parseOrderNotifyResult(xmlData); // 2. 根据商户订单号查询本地订单 Orders order ordersMapper.selectByOrderNo(result.getOutTradeNo()); // 3. 幂等校验如果订单已经是已支付状态直接返回成功 if (order ! null order.getOrderStatus() 1) { return success; } // 4. 校验金额是否一致防篡改 if (order ! null order.getTotalAmount().equals(result.getTotalFee())) { // 5. 更新订单状态 ordersMapper.updateStatus(order.getId(), 1, new Date()); return success; } return fail; }为什么必须做幂等因为微信支付的回调机制是如果商户没有返回success微信会按间隔递增的策略重试多次。如果回调处理中因为网络抖动重复执行没有幂等保护就会出现订单状态反复更新的问题。第一次回调已经把订单改成已支付第二次回调如果不判断直接再执行一次虽然结果可能还是已支付但相当于把用户已经完成的订单又重新支付一遍一旦后续有短信通知、积分赠送这类扩展逻辑就等着被骂吧。4.3 订单状态机用一张图想清楚所有流转路径订单状态管理不能只靠if else硬写一定要先画状态流转图再写代码。状态流转如下待支付可以取消或支付成功进入已支付已支付后商家接单进入制作中也可以拒单进入退款中制作中完成出餐进入待取餐/配送中待取餐用户取走或骑手送达后进入已完成已支付之后用户申请退款走退款中退款审核通过进入已退款。代码层面我封装了一个OrderStatusEnum枚举每个状态定义可流转到的下一个状态集合更新时校验流转合法性。这样做的好处是后续加新状态比如商家已接单和制作中分开时只需要在枚举里加一个值并调整流转关系不会出现改一处漏一处的情况。5. 实际开发中踩过的坑与排查实录5.1 并发场景下的库存超卖问题第一次联调测试的时候我用Jemeter模拟了100个并发请求同时购买最后一个库存的菜品结果生成了11张订单。问题出在最开始的代码是先查询库存判断是否大于0再执行UPDATE扣减。两个请求同时查到库存为1都通过了判断然后都执行了扣减最终库存变成-1。后来改成UPDATE语句直接带stock quantity条件利用数据库行锁保证原子性同样场景再测只会有1个请求成功。这个问题是秒杀系统、限量商品系统的经典问题但基础的业务系统同样会踩。5.2 微信支付回调与本地订单状态不一致上线第二天就遇到一个诡异问题用户反馈支付成功了但订单还是待支付状态。排查日志发现微信在某个时间点同时发送了两次回调第一次处理成功第二次刚进入处理器时被Redis分布式锁拦截抛了异常代码没有捕获这个异常导致微信认为回调失败又开始重试。后来在回调入口加了try-catch所有异常都记录日志并返回fail让微信继续重试问题解决。还有一个容易遗漏的问题是回调接口超时时间如果业务处理超过微信的限制调用方会超时断开所以回调里只做最核心的校验和状态更新短信通知、消息推送这些全部异步处理。5.3 数据库连接池配置不当导致的接口雪崩压测的时候发现用户下单接口的耗时从平均200ms直接飙升到5秒以上数据库连接池报Connection is not available, request timed out。原因是HikariCP默认最大连接数是10下单流程中长时间持有数据库连接比如在事务里调用微信支付微信接口响应2秒这2秒连接一直被占用并发一上来连接池就耗尽。解决方案是把最大连接池调到50同时规范事务边界事务里只做数据库操作像调用微信支付、发送短信这类远程调用全部挪到事务外边事务提交成功后再执行。这类问题在开发环境几乎不会暴露只有压测或者真实高并发才会现出原形。5.4 商家端接单重复提交商家快速点击两次接单按钮后端收到了两个请求第一个把订单状态从已支付改成制作中第二个进来发现订单已经是制作中但代码里没有做状态校验又执行了一次UPDATE导致操作日志出现两条接单记录。解决方式是在状态更新SQL里带上当前期望的状态UPDATE orders SET order_status 2 WHERE id #{id} AND order_status 1受影响行数为0说明状态已经被改过了直接返回订单状态已变更。6. 项目部署与上线后的持续优化6.1 用Docker Compose一把梭部署部署方案用的是Docker Compose一条命令把MySQL、Redis、Spring Boot应用、Nginx全部拉起来。Nginx的配置里同时做了三件事托管前端打包后的静态文件、反向代理/api路径到后端服务、处理图片静态资源的访问。HTTPS证书用泛域名证书直接配置在Nginx层后端服务本身不需要处理SSL简化了Spring Boot的配置。小程序端有域名白名单校验上线前记得把HTTPS域名配到小程序后台否则真机预览会报域名不合法。数据库初始化用了一组SQL脚本在docker-entrypoint-initdb.d目录下放schema.sql和data.sqlMySQL容器首次启动时自动执行。这个团队协作时特别省心小伙伴clone代码后docker compose up -d就能拿到一套完整的本地环境不用手动导入数据库。6.2 上线后需要盯的三个核心指标第一个是支付成功率和支付回调耗时。我在支付回调的入口加了一个计数器每次回调打一条日志用ELK或者简单的Shell Script定时扫日志就能看到平均值和异常值。如果回调耗时突增优先检查数据库连接池和Redis响应。第二个是超时未支付订单的数量和转化率这个数据直接影响商家的营收一般通过定时任务每分钟扫一次将超过15分钟的待支付订单自动取消参数做成可配置方便根据不同门店调整。第三个是菜品售罄提示准确度库存没有及时同步会导致用户下单成功但商家无货我加了一个下单前再校验库存的兜底逻辑一旦发现库存不足直接拒绝下单并给用户明确提示。6.3 后续还可以继续扩展的方向这个项目做完后很多场景可以继续深化。比如加入优惠券系统涉及券的发放、核销、与订单金额的联动比如对接第三方的配送平台API下单后自动呼叫骑手并把配送状态回传到订单比如增加多门店的库存分仓管理不同门店独立库存互不干扰再比如把订单数据接入商业智能报表做菜品销量分析、用户复购分析。这些都是在线订餐系统从能用到好用需要迈过的坎。我个人实际操作中最深的体会是这种业务系统写代码的时间大概只占四成剩下的时间都在梳理流程和排查边界情况。把订单状态流转、支付回调、库存扣减这三块想透彻了整个项目就稳了一大半。希望这套思路和踩坑经验能帮你少走一些弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

硬件工程师必收:9个运放经典电路全解析 2026/9/2 23:36:38

硬件工程师必收:9个运放经典电路全解析

这次我们回到硬件工程师最熟悉也最容易翻车的环节:运算放大器。很多朋友调板子时遇到过这些情况:信号放大后波形不对,增益按公式算好了但实际输出总是偏低,接上负载后电压直接被拉垮,面试被问到“同相放大器为什么输入…

阅读更多 →
RV1106 Linux GPIO控制全攻略:从Shell命令到C程序实现 2026/9/2 23:36:38

RV1106 Linux GPIO控制全攻略:从Shell命令到C程序实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
独立开发者如何用五大核心原语构建自动化单人公司运营体系 2026/9/2 23:36:38

独立开发者如何用五大核心原语构建自动化单人公司运营体系

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
2026蓝牙耳机选购指南:四类场景对应三种佩戴形态,避坑实测一次讲透 2026/9/2 23:36:38

2026蓝牙耳机选购指南:四类场景对应三种佩戴形态,避坑实测一次讲透

如果你在 2026 年买蓝牙耳机,最该先想的不是“哪款音质最好”,而是“我在什么场景用、戴什么形态最合适”。这篇文章直接按通勤、手游、运动、办公四个场景拆解,把入耳式、半入耳式、开放式三种形态的优劣势讲透,再结合 vivo、华为…

阅读更多 →
Agent Skills:从临时发挥到可复用技能封装,让LLM应用更可控 2026/9/2 23:36:38

Agent Skills:从临时发挥到可复用技能封装,让LLM应用更可控

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
AI Agent SaaS 产品设计指南:架构、API与出海合规实践 2026/9/2 23:33:37

AI Agent SaaS 产品设计指南:架构、API与出海合规实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞