ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

餐厅点餐微信小程序+Spring Boot完整源码解析

餐厅点餐微信小程序+Spring Boot完整源码解析 做点餐类小程序差不多两年了中间带过几个朋友从零搭过整套点餐系统。最近正好把一套“餐厅点餐微信小程序 Spring Boot”的完整源码整理了一遍包括小程序前端、后端接口、数据库脚本和部署说明很多细节值得单独拿出来聊聊。如果你正准备做类似的项目或者刚拿到一份带源码的工程却不知道从哪跑起来这篇文章应该对你有用。这套源码不是那种只有前端页面、数据全靠Mock的演示项目。它把小程序端、Spring Boot后端、MySQL数据库全串起来了从用户扫码进店、浏览菜品、加购物车、提交订单到支付回调改订单状态是一条完整的可运行链路。对想学全栈的人来说它提供了很标准的写法对想直接拿店里用的人来说也能看出哪些地方不能省、哪些地方可以简化。1. 这套点餐系统的设计定位给谁用、解决什么问题先说结论这套源码更适合中小型餐厅、快餐店和档口核心场景是“扫码点餐 后厨出单 线上支付”。不是那种包含排队叫号、会员储值、多门店连锁的复杂系统但它的边界非常清晰恰好覆盖了餐厅日常运营中最痛的那一段。1.1 三种典型使用场景我把实际见过的情况分成三类你会发现同一套源码在不同场景下配置方式完全不一样。第一类是堂食扫码点餐。每个餐桌上放一个二维码用户微信扫一扫直接进到对应桌台的点餐页选完菜提交订单直接微信付款。这个场景最关键的是“桌台号怎么传”。传统做法是后端根据二维码参数生成一个scene用户扫码后小程序通过onLoad拿到 scene 参数再通过后端接口解析出桌台ID。这套源码也沿用了这个思路桌台表和订单表通过table_id关联保证用户坐在哪桌、订单就挂到哪桌。第二类是提前预约打包带走。用户还没到店先打开小程序点餐下单选择自提时间到店以后报订单号取餐。这种场景下桌台可能为空所以订单表的table_id要允许为null下单逻辑里也要做兼容。很多自己改源码的人容易在这里出问题一上来就查桌台状态结果预约单没有桌台直接报空指针。第三类是小型档口的简化版。前台由收银员在平板上代客下单小程序端其实只负责展示菜单和接单后的状态提醒。这种情况甚至可以把登录和支付逻辑拆出去只保留购物车和订单提交但缺点是丢失了用户自助点餐的体验。从源码结构上看你只需要在controller层去扩展一个“员工代下单”的入口复用service层的订单创建逻辑即可。1.2 技术选型的取舍逻辑为什么前端选微信小程序而不是H5或者App原因很直接微信支付的接入成本低、免安装、用户扫码即走。对线下餐厅来说“扫一扫就能点餐”比任何引导下载App的流程都顺滑。再加上小程序有自己的原生弹窗、授权流程和微信生态打通得很自然不需要自己搭账号体系。后端为什么用Spring Boot因为Spring Boot的自动配置和起步依赖让项目可以很快成型。对于点餐这种业务场景不需要上微服务和消息队列单体应用加一个MySQL就能支撑日常流量。源码里采用经典的三层结构Controller、Service、Mapper没有引入太多花哨组件这样无论是做毕业设计展示还是小团队维护别人接手都能看懂。有些人会纠结要不要用前后端分离、要不要用Redis做缓存。说实话单店点餐系统把菜品表放进数据库加上MyBatis的一级缓存就够用了。真正需要Redis的是高峰期菜单接口被频繁轮询的场景但你可以直接在上线后用Nginx缓存或Spring Cache做一层没必要把依赖搞复杂。2. 数据库建模与核心业务表的设计点餐系统的核心就是菜品、订单和桌台这三块。我发现很多新手做这种系统时最容易犯的错是“一张表想搞定一切”菜品表里塞分类名称、订单表里塞菜品JSON字符串。当时跑着没问题等你要做统计、退款、出报表的时候就会吃尽苦头。这套源码的表结构虽然不复杂但每个关键字段都有目的。2.1 菜品分类与菜品表的基本形态分类表和菜品表是一对多关系。分类表一般就四个核心字段id、name、sort、status。sort用于控制菜品展示排序status用于控制分类是否启用。像凉菜、热菜、主食、饮品这些分类靠sort字段就能决定在小程序上显示的顺序。菜品表需要重点关注价格字段和库存字段。CREATE TABLE dish ( id bigint(20) NOT NULL AUTO_INCREMENT, category_id bigint(20) NOT NULL COMMENT 分类id, name varchar(100) NOT NULL COMMENT 菜品名称, price decimal(10,2) NOT NULL COMMENT 单价元, image varchar(255) DEFAULT NULL COMMENT 菜品图片, description varchar(500) DEFAULT NULL COMMENT 描述, stock int(11) DEFAULT 999 COMMENT 库存-1表示不限量, status tinyint(1) DEFAULT 1 COMMENT 1上架 0下架, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品表;价格字段必须用decimal(10,2)不要用float或者double。这个坑我踩过数据库里存的9.99Java一取出来变成9.989999999订单总金额对不上用户如果仔细核对就会发现多一分钱少一分钱。金额计算一律用BigDecimal字符串转的时候也别用parseFloat。库存字段这里默认999是为了兼容那些不限量的菜品比如米饭、例汤。真正需要限量的菜品比如每日限售20份的招牌菜可以手工维护stock值。下单逻辑里扣库存并不是必须的如果餐厅觉得菜品经常估清那就保留扣减逻辑如果大多数菜品都是后厨备货建议直接把库存关掉否则很容易出现用户下单成功但后厨早就不做了的情况。2.2 为什么订单表要拆成主表加明细表订单表拆成两张大表是标准做法但很多人一开始不理解。我用大白话解释一下主表存“这一单总共有多少钱、现在是哪个状态、谁下的单”明细表存“这单里有哪些菜、每道菜多少钱、买了几份”。它们是一对多关系通过order_id关联。这种设计的好处有三点。一是结算简单。一个订单的总金额等于明细表里所有菜品的小计之和。主表单独存total_amount字段是为了查询订单列表时不用每次都做SUM直接读取即可。你可以在下单事务里计算好保证两表数据一致。二是退款方便。如果用户只退了其中一道菜你可以直接操作明细表的某一行而不是把整个订单状态改成退款。订单主表可以增加一个refund_amount字段记录部分退款金额这样可以保留完整的退款痕迹。三是报表统计容易。想统计一道菜卖的怎么样直接按菜品id去查order_items表想统计一天营业额按订单主表的支付时间查。如果一开始就把菜品塞在订单表的JSON字段里这些SQL写起来会非常痛苦。订单明细表的字段建议包含id、order_id、dish_id、dish_name快照、price快照、number、subtotal。注意dish_name和price必须单独存一份快照不能实时去关联菜品表。因为菜品以后可能改价或者菜品被删除但历史订单里的信息必须保持原样。很多系统做久了之后订单数据对不上就是因为他们直接去读菜品表当前的价格。2.3 桌台状态与订单状态的联动桌台表比较简单核心字段是id、name、seats、statusstatus一般有“空闲/就餐中/待清理”三种。用户扫描桌台码进入点餐页时后端应当根据桌台status判断当前是否可点餐。如果桌台状态是“就餐中”还要判断当前是否有未完成的订单如果有用户继续点菜其实是“加菜”而不是新开一单这就需要前端做引导或者后端直接返回当前桌台的未完成订单id让用户继续加购。这里最容易踩的并发问题是“两个用户同时扫同一个桌台码下单”。比如一个桌子坐了四个人每个人都扫了码理论上应该合并成同一桌的订单。如果后端没有做“桌台锁”就会出现两三个待支付订单挂在同一桌上最后结算非常混乱。合适的做法是下单选桌台时用数据库行锁或者分布式锁把桌台记录锁住检查是否已有该桌台的有效订单如果存在就直接把新购物车里的菜合并到已存在的订单中或者提示用户选择“继续加菜”。订单状态机建议这样定义0待支付、1已支付待制作、2制作中、3已完成、4已取消、5退款。不要把状态设计得太细否则后期维护状态流转会让人抓狂。核心原则是待支付只能流转到已取消或已支付已支付只能流转到制作中或退款制作中只能流转到已完成或部分退款。3. Spring Boot后端接口的实现要点后端这部分我觉得最值得讲的是四个点统一返回结构、登录鉴权、下单并发控制、微信支付回调。把这四个点搞明白你基本就掌握了这套源码的骨架。3.1 统一返回体与异常处理小程序前端不管是拿到数据还是报错最希望的就是“能通过一个字段快速判断这步操作成功没有”。这套源码在Result类里统一封装了code、msg和datacode为0表示成功非0表示业务失败。业务异常直接抛出BizException由全局异常处理器转成统一返回体。public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 0; r.msg success; r.data data; return r; } public static T ResultT error(Integer code, String msg) { ResultT r new Result(); r.code code; r.msg msg; return r; } }小程序端的请求封装里建议把所有HTTP请求都包在一个request.js中统一在响应拦截器里判断code。如果code不为0直接wx.showToast弹出msg这样业务代码里就不用到处写错误提示了。这套源码就是这样的惯例读代码的时候你会发现service层只负责业务逻辑不需要关心消息提示。3.2 小程序登录鉴权从code到token小程序登录不是传统意义上的账号密码登录它依赖微信的code2Session接口。前端调用wx.login拿到一个临时code后端拿这个code加上AppID和AppSecret去微信接口换取openid。每个用户对一个小程序只有一个唯一openid后端就用这个openid作为用户身份标识。后端拿到openid后先查用户表不存在就注册一个新用户存在则返回已注册信息。然后生成一个token存到Redis或数据库并设置过期时间。小程序端每次请求都在header里带上token后端通过拦截器解析token得到userId。PostMapping(/login) public ResultString login(RequestBody LoginDTO dto) { String openid wxService.code2Session(dto.getCode()); if (openid null) { return Result.error(1001, 微信登录失败); } User user userService.findOrCreate(openid); String token jwtUtil.createToken(user.getId()); return Result.success(token); }要注意的是微信小程序登录获取手机号和使用手机号快速验证的接口都是需要在小程序后台申请并开通的而且会收取少量认证费用。这套源码默认聚焦点餐流程没有把手机号作为硬性登录条件用户直接使用微信身份下单。如果你真的需要收集手机号比如用于会员营销需要在小程序后台申请【手机号快速验证组件】并且前后端都要做相应处理。3.3 下单接口的完整事务逻辑下单接口是点餐系统里最核心的写操作它同时涉及订单主表、订单明细表、菜品库存、桌台状态四项数据。Transactional(rollbackFor Exception.class) public OrderVO createOrder(CreateOrderDTO dto) { // 1. 锁桌台防止并发重复下单 Table table tableMapper.lockTableById(dto.getTableId()); // 2. 校验菜品和价格 ListDish dishes dishMapper.selectBatchIds(dto.getItems()); // 3. 计算总金额同时判断菜品是否上架、库存是否足够 // 4. 插入订单主表 // 5. 批量插入订单明细表 // 6. 扣减库存如果开启 // 7. 更新桌台状态为就餐中 return orderVO; }关于并发控制一定要提两个方案。乐观锁方案在菜品表加一个version字段更新库存时写UPDATE dish SET stock stock - #{num}, version version 1 WHERE id #{id} AND stock #{num}。如果影响行数为0说明库存不足或版本冲突直接抛出“手速太快了菜可能不够了”的提示。这个方法不用锁表性能好点餐系统完全够用。悲观锁方案在桌台表上使用FOR UPDATE防止同一桌同时产生两个订单。这个锁粒度很小只锁一张桌子不会影响其他桌的用户。源码里用的就是这个方案我看着也很稳。3.4 微信支付的签名与回调处理微信支付一定是后端统一下单然后小程序端拉起支付。后端调用微信的统一下单API会得到一个支付参数前端用wx.requestPayment发起支付。这里需要注意签名生成的key不能明文写死在代码里必须放到application.yml中并且提交Git仓库时要忽略掉配置文件。支付成功的通知是微信服务器异步回调到你的后端接口而不是小程序前端通知后端。这个顺序很多人容易搞反。实际流程是用户在小程序里点了支付并输入密码微信支付服务器扣款成功微信服务器向你的后端回调URL发送支付结果通知后端收到通知后验签确认支付成功更新订单状态小程序前端通过轮询或WebSocket收到订单状态变化跳转到“下单成功”页面。回调接口必须做两件事验签和幂等。验签是为了防止有人伪造支付成功通知幂等是为了避免微信服务器重试回调时你重复把订单状态从“待支付”改成“已支付”。最简单的方式是回调处理前用订单号加分布式锁或者直接在订单状态更新时加条件WHERE status 0。如果更新行数为0说明订单已经处理过了直接返回成功给微信服务器。4. 微信小程序端的关键交互与实现细节小程序端是用户直接接触的部分看起来简单但有不少细节没处理好就会显得很业余。我从登录态、购物车和页面跳转三个角度来拆。4.1 登录态怎么在小程序端维持小程序端的登录态通过token维持标准做法是启动时先检查本地缓存里有没有token没有token就调用wx.login把code传给后端换token然后存到wx.setStorageSync里。之后所有请求都从storage里取出token放到请求header的Authorization字段。这里有个很实际的分支如果token过期了怎么办一般后端返回code为401或业务码10002前端请求拦截器发现这个code后先调用一次静默登录刷新token再重放刚才失败的请求。这套源码里封装了一个request方法专门处理这种重试逻辑你在二次开发时最好不要把那部分代码删掉否则用户用着用着就会突然因为token过期被弹回登录页。有一点要注意点餐这种低频场景里不建议在App启动时强制用户做“微信授权登录”弹窗。正确做法是先把token静默登录搞定当用户真正需要支付、查看个人订单时再引导授权。强制授权会吓跑很多只是想点菜的顾客。4.2 购物车状态前端临时数据 vs 后端存储大多数点餐小程序的购物车是纯前端状态用全局变量或本地缓存保存。好处是响应快用户加菜减菜没有网络延迟。但问题是如果用户退出小程序再进来购物车可能丢了。处理方式是把购物车数据保存在storage里而不只是放在内存对象中。用户在下单时把前端购物车数组转换成订单参数提交给后端后端校验价格是否变化、菜品是否售罄。这里需要设定一个取舍如果用户加购物车到提交订单时间间隔很短一般可以直接信任前端价格但如果中间隔了很久后端应当重新从数据库取最新价格。源码里的做法是以后端数据库价格为准前端购物车里的“小计”只是展示作用。在购物车页面我会建议你加上两个小交互一是总价的实时计算显示在底部并且可以根据份量份数变化自动刷新二是点击“去结算”时先判断购物车是否为空为空就弹提示不为空再跳确认订单页。这些看起来简单但很影响用户体验。4.3 页面跳转与参数传递的几个坑小程序页面跳转和浏览器的区别很大。最常用的wx.navigateTo打开新页面并保留当前页wx.redirectTo关闭当前页跳转新页面wx.switchTab只能跳tabBar页面。点餐流程里一般这么用首页和订单列表是两个tab页菜单详情页用navigateTo支付成功页用redirectTo避免用户从支付成功页返回又能回到之前的下单确认页重复提交订单。还要注意URL长度限制。以前我在菜单列表页把整个购物车JSON通过URL参数传到结算页结果数据长了直接截断页面怎么也打不开。后来改成用全局变量或eventChannel传复杂对象URL只传订单或菜品ID。这不是什么高级技巧但确实容易踩。另外微信小程序页面栈最多十层如果用户连续点了七八个菜品详情再点返回页面栈就满了。点餐场景虽然有强制引导但最好在菜单页面做好分类锚点减少用户来回跳转。5. 从源码到上线部署、联调与小程序审核的经验在本地跑通源码不难难的是把它弄到真机上、给朋友试用、最后上架微信平台。这一段我把从开发到上线会遇到的关键问题写出来希望能帮你少走弯路。5.1 本地联调小程序怎么访问你的本机后端开发阶段后端跑在你电脑的8080端口小程序前端要访问它不能直接写localhost。小程序开发者工具里可以填局域网IP比如http://192.168.1.101:8080并开启“不校验合法域名”选项。手机预览时手机和电脑需要连同一个WiFi并且电脑防火墙要允许8080端口被外部访问。当你用iPhone手机预览时如果出现请求一直pending很有可能是后端服务绑定的地址问题。Spring Boot默认绑定0.0.0.0一般没这个问题但如果你自定义了server.address127.0.0.1那就只监听本机回环地址局域网肯定访问不了。记得检查配置。还有一个小技巧写一个小配置类把baseUrl抽取到app.js里开发环境用局域网IP生产环境用HTTPS域名切换环境只需要改一行配置。5.2 正式环境为什么必须HTTPS和ICP备案小程序正式版要求接口必须是HTTPS协议并且域名已经ICP备案。这个限制没有任何弹性不要想着用IP或者随意域名去顶。你需要买一个云服务器和域名完成域名ICP备案国内服务器必须做一般需要2-3周为域名申请SSL证书可以使用免费的单域名证书在Nginx里配置HTTPS反向代理把443端口转发到Java应用的8080端口。Nginx配置看起来长但核心就是下面这一段server { listen 443 ssl; server_name api.yourdomain.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配好以后在小程序后台把request合法域名、uploadFile合法域名、downloadFile合法域名都改成你的HTTPS域名然后上传代码、提交审核。注意微信小程序后台的两个域名校验是白名单模式改完以后要等几分钟才能生效。5.3 微信审核与用户隐私保护如果你的小程序要上线类目选择很关键。点餐类小程序一般属于“餐饮 点餐”需要选择企业主体。个人主体基本无法开通微信支付也无法申请很多和交易相关的接口所以如果你在用这套源码做毕业设计个人主体调试是没问题的但要是真拿去开店你要么注册个体工商户或企业要么使用第三方点餐平台。关于用户隐私新版小程序需要填写《用户隐私保护指引》并在代码中调用隐私接口前先获得用户同意。如果你的后端存储了用户手机号、地址等信息就必须在隐私指引里声明。不要尝试绕过微信的隐私合规检查否则随时会被下架。另外数据库里保存用户openid时建议加一层可逆加密或者使用微信提供的用户数据加密方式。openid本身就是用户在某一个小程序内的唯一身份标识如果数据库泄露对用户影响很大。这一点在源码里有时候做得比较简单二次开发时最好加上字段级别的加密处理。5.4 拿到一份源码你应该重点学什么最后聊一点读源码的方法论。很多同学拿到源码后喜欢直接右键运行启动完以后面对一堆代码不知道看哪里。我的建议是先画一遍业务数据流从小程序端首页开始点一个菜品观察它调用哪个接口、后端哪个Controller处理、访问了哪些表。把这条线理通你就掌握了整个项目的70%。第二个建议是把订单状态机背出来。看代码时重点关注状态回推比如“取消订单”这个操作会调用哪个service方法它会不会退库存、退桌台。这种状态迁移逻辑是点餐系统的灵魂比任何单一技术的炫技都重要。第三个建议是挑一个点做自定义修改比如增加“菜品规格”大份/小份、加辣/微辣。你可以自己加一张spec表也可以直接在dish表里加一个json字段存规格选项。亲手改一遍你才真正学会怎么扩展这套系统。回到开头说的定位这套点餐系统不是那种大而全的SaaS而是一个能跑通、能学习、能扩展的标准工程。我个人在带项目过程中的体会是最值钱的部分不是“代码能运行”而是你能看着它说清楚“为什么订单拆主表明细表”“为什么要先锁桌台再下单”“为什么支付结果以后端回调为准”。把这三个为什么想透了以后不管换成什么框架你都有能力重新写一套。最后再分享一个小技巧改造这套源码时建议把订单状态、支付状态都定义成枚举常量不要用裸的数字散落在代码里。第一次用数字0、1、2看着没问题等做到售后、退款、加菜时你就知道有一份集中的状态定义是多么幸福的事。
返回列表