资讯动态

SpringBoot+Vue3+Uniapp点餐小程序全栈开发实战

发布时间:2026/8/30 10:52:39 来源:尧图企业网站定制
简介本资源是一套基于Spring Boot Vue3 UniApp全栈技术实现的点餐小程序完整源码工程面向Java后端开发者、前端工程师及跨端小程序学习者解决餐饮场景下多端统一交付、前后端分离开发与快速迭代的实际需求。压缩包共499个文件涵盖78个Java后端类含SysGoodsController、UserOrderServiceImpl等核心业务控制器与服务、127个Vue组件支撑小程序页面逻辑与交互、58个JS/40个TS脚本含API封装与状态管理、40个PNG图标资源及配置类yml、json、xml等整体19.16MB结构规范模块划分清晰便于二次开发与教学分析。已有832人学习下载提供可直接运行的前后端协同方案Spring Boot构建RESTful接口并集成安全与数据库支持Vue3通过Composition API实现响应式界面UniApp保障微信小程序、H5、App三端一键编译附带完整订单、商品、用户、评论等业务闭环代码是理解现代全栈开发协作模式的优质实践样本。1. 项目概述与整体设计思路做点餐小程序这个项目最早是因为朋友开了一家小餐厅堂食点单全靠服务员手写高峰期经常出错后厨出餐顺序也乱。他问能不能做一个客人扫码就能自己点餐的小程序后厨和前台能实时看到订单。我盘了一下需求决定用 SpringBoot Vue3 Uniapp 这套组合来做。这套方案选型不是拍脑袋而是基于几个现实约束后端要稳定、能扛住并发Java 系 SpringBoot 是当前最成熟的选择管理端需要做菜单管理、订单管理、数据统计Vue3 生态完善、组件丰富非常适合做这类后台系统用户端要同时兼容微信小程序和 H5Uniapp 一套代码多端发布省去了重复开发的成本。整个项目本质上是一个典型的前后端分离架构。后端只负责提供 RESTful API不关心页面长什么样前端分成两个独立的工程管理端是 Vue3 Element Plus 的 Web 应用给餐厅管理员用用户端是 Uniapp 项目编译成微信小程序给顾客用。三者之间通过 HTTP/HTTPS 通信数据格式统一用 JSON。我画过一张简单的架构图在心里小程序端 → Nginx → SpringBoot 服务 → MySQL / Redis管理端同样走这条路。这样分层的好处是职责清晰任何一个环节出问题都能快速定位不至于像老式 JSP 项目那样前后端代码揉在一起改个样式都要重启服务。这套方案的适用人群很明确有一定 Java 基础、想完整走一遍全栈流程的开发者或者接私活时需要快速交付点餐类项目的朋友。项目做完之后可以延伸出很多变体比如奶茶店点单、食堂预订、火锅店叫号等核心流程都是相通的。下面我会按模块把关键实现拆开讲包括后端接口设计、管理端页面开发、小程序端核心功能以及实际部署中踩过的坑尽可能做到看完就能照着做。2. 后端 SpringBoot 核心模块设计与接口实现2.1 数据库表结构设计与订单状态机点餐系统的核心数据模型其实不复杂但表设计直接决定了后面能不能撑住业务扩展。我最初设计时规划了这几张核心表category分类表、dish菜品表、setmeal套餐表、user用户表、orders订单主表、order_detail订单明细表、shopping_cart购物车表以及address_book地址簿表。订单表是整个系统的核心我重点说一下状态字段的设计。我用一个status整数字段表示订单状态1 待付款、2 待接单、3 已接单制作中、4 派送中/待取餐、5 已完成、6 已取消。这里有个容易踩坑的地方——很多人喜欢用字符串存状态比如PENDING、PAID虽然可读性高但在数据库层面排序、索引、统计都不如整数方便而且接口传给前端后还得做一层映射转换。我最终选择了整数 枚举类双向映射的方式Java 代码里定义枚举类通过EnumValue注解映射到数据库既能保证代码可读性又不牺牲查询效率。订单号生成也是个细节活。直接用数据库自增 ID 当订单号暴露给用户很容易被猜到今天有多少订单而且多表合并查询时不方便。我采用时间戳 随机数的方案yyyyMMddHHmmss 4位随机数虽然并发极高时有极小概率重复但在加唯一索引的保障下冲突会直接报错重试实际跑下来没出过问题。如果你追求更严格的唯一性可以引入雪花算法但中小型餐厅的并发量完全没必要上这么重的方案。2.2 菜品管理接口与文件上传菜品管理是后端最基础也最繁琐的部分。菜品表dish需要关联分类表一个分类下有多个菜品菜品有名称、图片、价格、描述、口味标签、起售/停售状态等字段。这里我强调一点status字段一定要加索引因为前端小程序首页只会查status 1的起售菜品高频查询场景下没有索引会非常吃亏。菜品图片上传我用的方案是本地存储 Nginx 静态映射没有引入 OSS 对象存储。原因很简单个人项目或小餐厅的量级OSS 的维护成本和费用完全不划算。实现方式是后端提供一个/common/upload接口接收MultipartFile重命名文件避免中文乱码按日期分目录存储然后把可访问的 URL 返回给前端。重命名规则我用的是UUID.randomUUID()拼接原始文件扩展名这样能最大程度避免文件名冲突也能防止用户上传恶意文件名。静态资源映射在 SpringBoot 里只需要在WebMvcConfigurer中加一行registry.addResourceHandler(/upload/**).addResourceLocation(file: uploadPath)非常简单。菜品的新增和修改往往需要同时处理口味数据。一个菜品可能有多个口味比如“辣度微辣/中辣/特辣”如果每次都是先删后插在高并发下会出现短暂的“查不到口味”的中间状态。我改用事务方式处理先保存菜品主表拿到主键 ID然后遍历口味列表逐个插入整个过程包在Transactional里任何一个环节失败就整体回滚。之前有朋友问我为什么不用 MyBatis-Plus 的saveBatch批量插入——我当时确实是逐条插入的原因是口味数量通常很少最多三五个逐条插入的性能损耗完全可以忽略但代码的清晰度会高很多。2.3 微信小程序登录与 JWT 鉴权体系小程序端用户最头疼的就是登录鉴权。传统的 session 方案在小程序场景下不太好用因为小程序的请求天然带有跨域和并发特性服务端 session 不一定能保持。我选择了 JWTJSON Web Token方案服务端无状态、天然支持横向扩展。登录流程是这样的小程序端调用wx.login()获取临时 code传给后端/user/login接口后端拿着 code 调用微信的jscode2session接口换取 openid查数据库看用户是否已存在不存在就自动注册一个然后把 user 对象的核心信息userId、openid放进 JWT 的 claims 里签名后返回 token 给前端。同时我把用户第一次登录的昵称和头像也做了一次更新这样用户资料会自动同步到最新状态。这里有个容易踩的坑jscode2session接口的 appid 和 secret 一定不能写在前端代码里必须放在后端配置文件中否则等于把微信支付等敏感能力暴露给了任何人。我刚开始开发时为了方便把 secret 写死在代码里后来发现自己用抓包工具都能看到请求参数里有 secret吓出一身冷汗赶紧改成配置项并通过环境变量注入。JWT 的拦截器实现用了 SpringBoot 的HandlerInterceptor在preHandle里从请求头Authorization字段取出 token解析并校验签名把 userId 放入ThreadLocal中方便后续业务代码直接获取。ThreadLocal用完一定要remove()否则线程池复用会导致数据串号这是非常隐蔽的 bug排查起来很痛苦。还有一个需要注意的点/user/login、/common/upload这类匿名接口要记得在拦截器配置中放行否则前端一进来就被拦截连登录页都打不开。注意JWT 有个先天缺陷是无法主动失效。如果用户需要“退出登录”功能只能靠前端删除 token服务端没法强制下线。在点餐系统里通常不需要严格处理这个问题但如果你在做后台管理系统的鉴权建议引入 Redis 做 token 黑名单机制或者干脆用 Redis Token 替代纯 JWT。2.4 购物车与订单提交的事务处理购物车的 API 相对简单无非是添加、查看、清空、减少数量四个接口本质上是对shopping_cart表做增删改查。但我发现一个大多数人会忽略的点购物车内同一菜品添加两次是插入两条记录还是合并数量我在实现时采用了“同一用户、同一菜品、相同口味则数量叠加”的策略用查询条件先去数据库找找到了就setNumber(number 1)找不到才插入新记录。这种做法的好处是前端购物车列表清爽不会出现同一个菜品刷屏的情况。订单提交是整个系统事务最重的地方。用户点击“提交订单”后后端需要做一串操作创建订单主表记录状态为待付款→ 批量插入订单明细 → 清空购物车 → 如果是“到店自取”还需要通知商家。这一串操作必须全部成功或全部失败所以我用Transactional(rollbackFor Exception.class)包裹整个方法。这里rollbackFor必须显式指定否则默认只在运行时异常时回滚而 Spring 声明式事务遇到Exception的检查型异常是不会自动回滚的很多人踩过这个坑。订单金额计算我建议以后端为准前端传的价格只能作为参考。具体做法是后端根据购物车里的菜品 ID 重新查一遍数据库拿到最新单价乘以数量计算总价防止前端篡改价格。有些开发者图省事直接信任前端传来的 totalAmount这在真实项目里是不可接受的——点餐小程序直接跟钱挂钩任何漏洞都可能造成真金白银的损失。3. Vue3 管理端从零搭建到核心页面实现3.1 脚手架选型与工程结构规范管理端是一个典型的单页应用我选用 Vue3 Vite Element Plus Pinia 的组合。Vite 相比 Webpack 在开发体验上强太多冷启动基本秒开热更新也是毫秒级对于整天改 UI 的后台项目来说能省下大量等待时间。Vue3 的组合式 APIComposition API配合setup语法糖代码逻辑聚合度高同一个功能的变量和方法放在一起不用像 Options API 那样在data、methods、computed之间反复横跳。工程结构我按功能模块分包而不是简单按文件类型分src/views放页面组件src/api放接口请求封装src/router放路由配置src/store放 Pinia 状态管理src/utils放工具函数如request.js的请求封装。有一点很关键接口请求必须统一封装不要在页面里直接写axios.get。我在utils/request.js中创建了一个 axios 实例统一设置基础 URL 和超时时间然后在请求拦截器里加上 token在响应拦截器里统一处理后端返回的code/message格式。这样做的好处是后端接口格式一旦有变化只需要改一个文件而不用满项目搜索修改。管理端的接口统一返回{ code: 200, message: 操作成功, data: {...} }结构code 非 200 时前端弹错误提示并中断流程。3.2 动态菜单与权限控制方案管理端的用户分为超级管理员和普通操作员。普通操作员可能只能操作菜品和订单不能查看营收统计超级管理员则全部开放。这个权限模型在路由层面用“动态路由”实现用户登录后后端根据其角色返回可访问的菜单列表前端拿到后动态添加路由同时根据菜单列表渲染侧边栏。我在具体实现中用的是“前端路由表 后端权限码”配合的方式。静态路由基础框架包括登录页、404 页动态路由按模块配置好 meta 信息如{ title: 菜品管理, icon: Dish, roles: [admin, operator] }。用户登录后前端通过store里的generateRoutes方法过滤出当前角色可访问的路由用router.addRoute动态注册。这里有几个坑一是在页面刷新后Pinia 的状态会丢失需要重新从后端获取权限并再次注册路由否则刷新后白屏二是在router.beforeEach守卫中要处理好“已登录 访问登录页 → 跳转首页”和“未登录 → 跳转登录页”这两种情况三是 addRoute 添加的路由如果又删又要再添加会存在重名警告记得先通过router.removeRoute移除。我实际测试下来这套方案能解决绝大多数点餐管理端的权限需求。如果你在做一个高度定制化的 SaaS 后台可能要引入更细粒度的按钮级权限控制但那个复杂度会成倍增加个人项目没必要一上来就上。3.3 菜品管理页面的增删改查与图片回显菜品管理页面是管理端用得最多的功能。我用了一个“表格 搜索 弹窗表单”的组合列表页顶部放分类筛选和菜品名称关键字搜索表格显示菜品图片、名称、分类、价格、状态、操作列新增和编辑共用一个弹窗组件里面是表单校验、图片上传、口味设置。这里我重点说一下图片上传组件的封装。Element Plus 的el-upload组件功能全面但直接用在表单里有两个问题一是el-upload自带一套 action URL不方便统一走我们封装的 axios 实例因为要带 token 头二是回显时需要手动把 URL 转成文件列表格式这个转换逻辑在不同页面会重复。我封装出一个ImageUpload.vue组件内部用http-request自定义上传方法走统一的 axios 实例上传成功后把返回的 URL 通过v-model暴露给父组件回显时在on-change里做处理。这样菜品、套餐、分类都直接用同一个上传组件一行代码搞定省了无数重复工。口味设置是一个动态表单点击“添加口味”会追加一行表单控件每行包括口味名称如“辣度”和可选值如“微辣/中辣/特辣”。这里我用了 Vue3 的v-for结合reactive数组增删行就是 push 和 splice。在提交前要注意把所有空行过滤掉否则用户点击了“添加口味”又没填内容就提交会有空数据入库。这个小问题曾经困扰了我一个下午后来在过滤逻辑中统一处理才解决。3.4 订单管理与数据统计的两个关键页订单管理页面相对简单核心是列表筛选详情。筛选条件包括订单号关键字、订单状态、下单时间段。订单号我用了模糊查询状态用精确匹配时间段用BETWEEN查询。这里涉及一个 MyBatis-Plus 的LambdaQueryWrapper使用技巧多个筛选条件要判断非空再拼接否则用户不选时间段的默认查询会出问题。订单详情弹窗是查看某个订单的菜品明细、金额明细、用户信息和配送信息。这里涉及多表联合查询我在后端写了一个getOrderDetailWithDish方法先查订单主表再查订单明细表再把每个明细里对应的菜品名称和图片查出来。出于性能优化考虑我在这里用了一次FOR循环 批量查询的方式而不是逐条查询——即使这样在实际场景中一个订单最多也就十来个菜品性能压力基本可以忽略。数据统计页面我用了 ECharts 做可视化。两个核心图表近 7 天营业额折线图、分类销售占比饼图。后端提供两个统计接口一个是按日期分组求和另一个是按分类分组求和。SQL 写法上日期分组用DATE_FORMAT(order_time, %Y-%m-%d)做分组条件分类汇总用JOIN order_detail和JOIN dish做关联。这里有个需要处理好的细节用户选的日期范围可能跨月直接用%Y-%m-%d分组没问题但如果跨年后续前端显示时最好连年份一起展示。4. Uniapp 小程序端的核心功能实现与适配4.1 页面结构与 tabBar 配置用户端小程序的功能相对集中我设计了四个底部 tab 页首页点餐、订单、购物车、我的。这个结构经过多次调整——最初我还加了“收藏”页后来发现使用率极低果断下线。砍功能也是开发的一部分忍住加功能的冲动往往比实现功能更重要。页面结构确定后需要在pages.json里配置 tabBar。tabBar 的图标我直接用 PNG 格式尺寸要求 81px 81px不能超过 40KB否则真机预览时图标会不显示。这个坑是微信小程序的老规矩很多新手第一次配 tabBar 就栽在图标尺寸上。还有一个细节tabBar 页面不能使用uni.navigateTo跳转必须用uni.switchTab否则会报错说页面不存在。我刚开始不知道这个限制写接口时跳转总失败查了半天才发现是跳转方法用错了。小程序的页面结构还有一点要注意pages数组中的第一项是启动页也就是冷启动时默认加载的页面。我把首页放在第一位确保用户打开小程序就能看到点餐界面而不是先进登录页。登录逻辑我放在“进入首页后静默登录”的流程中用户无感知体验会好很多。4.2 点餐首页与分类联动实现点餐首页是整个小程序最核心也最复杂的页面。布局采用“左分类、右菜品”的双栏滚动结构左侧是竖向滚动的一级分类列表右侧是分类下菜品列表。这个结构有两个实现方案一是左侧点击分类时右侧列表跳转到对应位置二是左右两侧独立滚动右侧滚动经过某个分类时左侧高亮自动切换。我选择了第二种因为它更接近美团、饿了么的主交互。实现方案需要分别给左侧和右侧设置scroll-view右侧监听scroll事件获取滚动的 scrollTop与各分类的 offsetTop 做对比判断当前应该高亮哪个分类。能触发scroll-view滚动事件的属性是scroll取滚动距离用event.detail.scrollTop。这里有个行业通用的坑两个scroll-view嵌套时内层滚动会与外层滚动冲突解决方案是给内层设置scroll-y并确保外层没有滚动或者用 flex 布局把高度撑满。我经过多次试验最终用flex: 1 固定高度的方式解决了滚动冲突效果很稳定。菜品的加购操作我加了一个“SKU 选择弹窗”。部分菜品有口味和规格用户点击加购时不能立刻加入购物车而是弹出选择窗口选完规格后确认。这个弹窗的数据结构设计为每个菜品包含specs数组如[{ name: 规格, values: [小份, 大份], priceDelta: [0, 8] }]总价等于基础价加上所有priceDelta的累加值。由于涉及价格计算我在computed里做了动态求和确保任何规格变化都会自动刷新展示价格。4.3 购物车逻辑与本地状态管理购物车页面的状态管理我选择了 Pinia因为小程序端和 Web 端的共享逻辑几乎一致Pinia 在 Uniapp 中配合 Vue3 使用非常自然。购物车的状态包括cartList数组每个元素是菜品 ID、名称、图片、单价、数量、规格组合。所有增删改操作都封装成 action在页面中只需调用store.addToCart(dish)或store.removeFromCart(dishId)。购物车有一个两个页面需要同步的数据问题首页的购物车图标右上角要显示数量角标而角标数据存在 store 里。小程序中 store 是内存级共享页面切换不会丢失但小程序冷启动时会重新加载初始状态如果用户上次没提交订单购物车数据就丢了。我在实现时用了uni.setStorageSync做了持久化购物车每次变更时同步写入本地存储应用启动时读取本地存储初始化 store。这样即使用户杀掉小程序重进购物车也能恢复不过要记得在订单提交成功后清除对应存储。还有个细节购物车的选中状态和结算逻辑分开设计。比如购物车中有 A、B 两个菜品用户勾选了 A结算时只计算 A 的价格。这个实现其实不复杂在cartList每项加一个checked布尔值结算按钮的金额通过computed计算出“所有 checked 为 true 的项的价格总和”。这里很多人会忘记处理“勾选了但商品已下架”的边界情况我在每次拉取菜品状态时做了比对如果下架则自动置灰且不可勾选避免用户付了钱发现商品没了。4.4 微信支付与订单状态更新流支付环节是整个小程序端最绕不开的坎。微信小程序支付必须走统一下单流程前端调用后端/order/pay接口后端接收订单号后调用微信支付 API 生成预支付交易单拿到prepay_id后返回给前端wx.requestPayment所需的参数timeStamp、nonceStr、package、signType、paySign。这里最关键的一点是小程序端的 appid 必须和微信支付商户号绑定否则会报错appid and mch_id not match这个绑定关系需要在微信支付商户平台设置不是代码能解决的。我最初在这个问题上卡了整整一天反复检查代码都找不出问题后来才想起检查商户平台的绑定关系。支付成功后的状态更新我采用“支付回调 前端轮询确认”的双保险方案。微信支付成功后微信服务器会向商户后台配置的notify_url发送异步通知后端接收后校验签名、更新订单状态为“待接单”。由于回调是异步的前端在wx.requestPayment的success回调里不能立刻认为订单已完成支付而应该跳转到订单详情页后通过轮询或setInterval定时请求订单状态接口确认结果为已支付后再渲染结果页。这个细节很多人会忽略导致用户付款成功后看到的状态还是“待付款”体验极差。注意微信支付异步通知的地址必须是 HTTPS 公网地址而且回调处理要满足幂等性——同一笔订单的通知可能重复发送多次后端拿到通知要先查订单状态已支付就直接返回成功不要再做重复更新。4.5 uniapp 跨端适配与真机调试一个 Uniapp 项目除了微信小程序通常还要兼顾 H5 打包所以我在开发时尽量使用uni.开头的 API 而不是直接调用wx.系列 API。例如跳转页面用uni.navigateTo、弹出提示用uni.showToast、本地存储用uni.setStorageSync。如果必须要用某个平台的特定 API我会用条件编译#ifdef MP-WEIXIN包裹确保 H5 端不会因缺失 API 而报错。真机调试中我遇到的最典型的适配问题有两个一是 iPhone 底部安全区在page.json中设置app-plus的safeArea配置或者使用 CSS 的env(safe-area-inset-bottom)来适配底部操作栏否则购物车结算按钮会被 Home 指示条遮挡二是页面滚动穿透弹窗打开时底层的页面仍然可以滚动解决方法是给弹窗层的touchmove.stop.prevent阻止事件冒泡。另外在所有涉及到图片的页面要设置modeaspectFill否则不同分辨率下手机会出现图片拉伸变形这个问题在菜品列表和轮播图中尤其明显。5. 前后端联调部署与常见问题排查5.1 开发环境跨域配置与本地联调前后端分离开发最大的拦路虎就是跨域。前端开发服务器跑在localhost:5173后端接口在localhost:8080浏览器默认会拦截跨域请求。我在 SpringBoot 后端配置了一个全局 CORS 过滤器允许所有来源访问但在生产环境会限制为具体域名。这里有个容易迷惑的地方小程序端请求后端接口其实不涉及跨域限制因为小程序的请求不在浏览器环境里真正需要跨域处理的是 H5 端和管理端。开发时我用的代理方案Vite 配置文件里设置server.proxy把/api前缀的请求代理到http://localhost:8080。这样做的好处是前端代码里请求的 URL 可以写相对路径/api/xxx不需要关心后端实际的 IP 和端口部署时切换环境也只需要改代理配置。后端 Controller 的RequestMapping我统一加了/api前缀这样语义清晰也能跟非接口的静态资源请求区分开。5.2 Nginx 部署与 HTTPS 证书配置生产环境我选择用 Nginx 做反向代理承载前端的静态资源服务同时把/api请求转发给后端 Java 进程。Nginx 配置文件的关键部分如下server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # 前端资源 location / { root /var/www/admin-dist; index index.html; try_files $uri $uri/ /index.html; } # 后端接口 location /api/ { 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; } # 图片资源 location /upload/ { alias /data/upload/; } }需要注意的细节try_files这行是 SPA 路由的基础配置没有它刷新 Vue 路由子页面会报 404。proxy_pass后面的 URL 末尾有没有/会影响路径拼接——http://127.0.0.1:8080不带斜杠表示保留完整原始 URI带斜杠会去掉location匹配到的前缀这个规则容易搞混我是吃了亏才记住的。HTTPS 证书我用的是免费证书配合自动续期脚本到期前自动续签基本不需要人工干预。微信小程序要求所有请求域名必须配置在request合法域名中而且必须是 HTTPS没有证书小程序根本发起不了请求这部分在开发时就要规划好不能等到上线前才临时搞。5.3 MySQL 连接、Redis 缓存与并发扣库存数据库我用了 MySQL 8.x连接池使用 HikariCP这是 SpringBoot 2.x 之后默认集成的连接池性能非常出色。HikariCP 有几个关键参数值得配置maximum-pool-size连接池最大连接数我设为 20minimum-idle最小空闲连接数设为 5connection-timeout连接超时设为 30000 毫秒。如果是个人开发环境保持默认值也完全够用但生产环境建议至少设置一下maximum-pool-size否则高并发下连接数不够会导致接口缓慢。Redis 在这个项目里我用在三个方面首页菜品列表的缓存、用户 token 黑名单、验证码存储。菜品列表缓存是性价比最高的优化手段——菜品数据一般不经常变化我在新增、修改、删除菜品时主动删除对应缓存查询时先查缓存缓存不存在再查库并回填。这个策略能显著减少数据库压力在高峰期尤其明显。并发扣库存的部分我使用 Redis 的DECR命令实现原子扣减防止超卖。菜品表中有stock字段表示库存用户提交订单时对菜品 ID 对应的 Redis key 执行DECR如果返回值小于 0 则说明库存不足回滚事务。这个方案比数据库的乐观锁更简单而且天然支持分布式场景。不过要注意Redis 只是扣减计数的“临时台账”最终库存的持久化还是要靠数据库更新同时配合定时任务做数据对账。外卖场景下超出库存的订单还会触发短信通知运营人员补货。5.4 支付回调丢失、订单不同步等典型问题联调阶段遇到最多的问题集中在支付环节。第一种是支付回调丢失用户付了钱但订单状态没更新。排查思路先看后端日志有没有收到微信回调如果没收到大概率是notify_url配置错误或者回调接口响应不合法微信要求接口必须返回{code:SUCCESS}的 XML 格式且 HTTP 状态必须为 200。我踩过最蠢的一个坑回调接口返回了 JSON 格式微信服务器认为回调失败连续重试多次全部失败后放弃通知只能靠用户反馈才发现。第二种是订单状态不一致。用户支付成功但后端由于回调延迟还未更新订单状态用户刷新页面看到“待付款”就重复支付了一次。用前面提到的“前端支付成功后轮询订单状态 后端回调幂等”方案后这个问题基本杜绝。另外我在订单表中增加了pay_time字段对账时可以根据这个字段排查支付记录和订单状态是否匹配。第三种是小程序冷启动后登录态丢失。由于 JWT 存在本地存储中用户删除小程序后重新打开本地 token 清空按常理应重新静默登录。我在App.vue的onLaunch中先读取本地 token有 token 就带着去请求用户信息接口验证有效性如果 token 无效或过期再走wx.login()重新登录。这套流程能够保证用户打开就能点餐同时不会频繁弹登录框打扰正常使用。5.5 数据备份与日志排查的必备技巧上线后最怕的就是数据丢失。我的备份策略是每天凌晨 2 点执行一次 MySQL 全量备份使用mysqldump命令导出 SQL 文件保留最近 7 天的备份再加上异地备份一份到云存储。备份脚本用 crontab 定时执行关键命令如下0 2 * * * mysqldump -u root -pXXXXXX ordering /backup/ordering_$(date \%Y\%m\%d).sql find /backup/ -name *.sql -mtime 7 -exec rm {} \;日志排查方面SpringBoot 项目我使用logback配置了按天滚动的文件日志。生产环境一旦出问题我先看logs/spring.log重点搜索 ERROR 和 Exception 关键字同时配合retention参数配置了 Redis 的键过期时间避免内存无限增长。这里有个经验在 Controller 层统一加一个日志切面自动打点记录请求路径、参数、耗时、响应码联调和排错能省一半时间。切面 AOP 配置如下Around(execution(* com.example.controller.*.*(..))) public Object logAround(ProceedingJoinPoint pjp) throws Throwable { long start System.currentTimeMillis(); Object result pjp.proceed(); long elapsed System.currentTimeMillis() - start; log.info(Request: {}, Params: {}, Cost: {}ms, Response: {}, pjp.getSignature(), JSON.toJSONString(pjp.getArgs()), elapsed, JSON.toJSONString(result)); return result; }这套日志方案配合后续的链路追踪基本可以覆盖绝大多数异常的定位需求。实测在生产环境高峰时每秒钟几十个请求日志量虽然不小但配合 grep 工具定位一个异常通常几分钟就能完成。6. 经验总结与扩展方向这个项目从立项到上线前前后后花了我一个多月时间中间踩过的坑、收获的经验我觉得可以总结成几条第一前后端分离是发展趋势但沟通成本会显著上升。接口文档一定要及时维护我一开始用 Word 手写后来切换到 Apifox 在线文档联调效率提升了一个档次。建议所有接口变更都同步更新文档不要“顺手改了代码忘了改文档”否则后端改完接口前端还在用旧参数调用排查半天才发现是两边对不上。第二状态机设计决定了业务流程的清晰度。订单状态从待付款到已完成每一步都要有明确的操作入口和权限控制。不要为了省事把状态流转写死在多个页面的判断逻辑里我最初的版本就是这样后来发现新增一个“退款中”状态几乎要改十几个文件痛定思痛后重构为集中式状态管理后续扩展就从“改动无数处”变成了“改一个枚举类”省心太多。第三测试环境要尽量贴近生产。小程序端最忌讳拿开发者工具里的模拟数据去测微信支付、微信登录这些能力必须真机调试才能暴露真实问题。我建议在开发早期就做好内测流程重点测支付流程、多端适配、弱网环境这三个场景。第四关于项目的后续扩展我列几个可以玩的方向一个是接入 WebSocket 实现商家端实时接单提醒用户在 App 上下单后商家能立刻收到弹窗而不是靠轮询几秒才刷新一次另一个是引入更灵活的营销能力比如优惠券、满减、会员积分这个模块的核心是规则引擎设计需要考虑的地方更多还有一个是把小程序端升级为 React Native 或原生进一步提升复杂页面的性能比如商品列表更长的场景下scroll-view的渲染性能会到瓶颈需要考虑虚拟列表。如果你也想做点餐类项目我建议第一版不要贪大求全优先保证核心闭环能跑通用户能浏览菜品、加购、下单、支付商家能收到订单、出餐、完成订单。把这些跑通了就已经是一套能用的商业系统了。后面那些花里胡哨的功能等真正有客户在使用、有真实痛点了再加也不迟。本文还有配套的精品资源点击获取

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

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

免费获取报价