做点餐类小程序,最常见的技术路线就是微信小程序做前端、SpringBoot 做后端接口,这轮交付的weixin185餐厅点餐微信小程序springboot工程正是这套组合的完整落地源码包。上一轮交付过的2048-小程序.zip还只是一个单页游戏,不能算业务系统;这一轮的项目从“用户扫码进店”到“下单支付”,跑通了完整的点餐业务闭环。我把它从工程结构、页面流程、后端建模到启动排错完整过了一遍,这篇就把源码里值得拆解的部分和实际运行中会踩的坑一次性说清楚。无论你是做课程设计、毕业设计,还是准备给中小餐饮门店做一套点餐系统,都能在这套工程里找到可直接参考和复用的东西。
1. 整套点餐工程的形态:前端、后端、数据库是怎么分工的
1.1 为什么微信小程序 + SpringBoot 会成为默认组合
现在做餐厅点餐类的项目,微信小程序几乎是必选的前端载体。核心原因不是因为它“新”,而是它的使用路径实在太适合餐饮了:用户不用下载安装,扫一个桌台码或者搜一下小程序就能直接进入菜单页;用完关掉,下次再来仍然在微信入口里找得到。这个体验比让顾客下载一个原生 App 轻太多,也比让顾客打开浏览器输网址更顺。
后端选择 SpringBoot 则是开发效率上的考量。点餐业务的接口并不复杂,无非是菜品查询、购物车提交、订单创建、订单状态查询这几类,SpringBoot 的 starter 机制把这些基础设施全部架好了——内嵌 Tomcat、自动装配数据源、引入 MyBatis-Plus 后连 SQL 都不用手写太多。你只需要把精力放在业务逻辑上,而不是纠结配置文件写三小时。
整套工程的架构可以概括为三条线:
- 小程序端负责界面展示与用户交互,所有数据通过 HTTP 请求发送到后端;
- SpringBoot 后端暴露 RESTful 接口,处理登录鉴权、菜品数据、订单事务;
- MySQL 负责持久化存储,Redis 处理库存扣减和热点数据缓存。
熟悉校园食堂订餐系统这类场景的人应该对这个组合有很强的既视感:传统食堂高峰期排队取餐效率低,点餐小程序把“选菜”环节提前到用户手机上,后厨直接按订单出餐,前后端解耦明显。这套工程本质上就是把这种业务模型用最稳妥的技术栈落地了一遍。
1.2 交付内容与模块边界
拿到手解压源码包,里面大致包含三块内容:
| 内容 | 说明 |
|---|---|
| 小程序前端工程 | 微信开发者工具可直接导入,包含首页菜单、购物车、订单确认、订单列表等页面 |
| SpringBoot 后端工程 | Maven 结构,包含实体类、Mapper、Service、Controller 以及配置文件 |
| 数据库脚本 | 建库建表 SQL 及初始化数据,管理员账号、测试菜品都直接写在脚本里 |
从业务模块上看,这套工程以“用户点餐闭环”为主线,覆盖了用户登录与手机号绑定、菜品分类浏览、菜品加购、订单创建、订单状态流转这几个核心环节。如果你在找带完整后台管理界面的项目,可能需要再考虑一下——这套源码的交付重点在用户端点餐与后端接口服务,管理端的菜品上下架、订单管理更多是以后端接口形式提供。
个人建议是拿到源码后先不要急着改代码,按“数据库脚本 → 后端启动 → 小程序导入”的顺序跑通一遍,看清每个模块真实的数据流,再动手二次开发。这样后面改什么都心里有底。
1.3 工程目录和标识“weixin185”到底是什么意思
你可能留意到了工程包名前缀带了weixin185,很多课程设计和毕业设计出题时都会按编号命名,方便区分不同批次的交付内容。之前那一轮交付2048-小程序,这一轮是weixin185餐厅点餐,编号对应的就是题目编号。这种命名方式在源码分享、作业提交场景里很常见,不是代码本身的组成部分,二开时完全可以把包名、工程名改成自己的业务标识。
后端工程内部是标准的 SpringBoot 分层结构:
src/main/java ├── controller # 接口层,接收前端请求 ├── service # 业务逻辑层,下单事务、库存扣减等都在这层 ├── mapper # 数据访问层,基于 MyBatis-Plus ├── entity # 数据库实体类 └── config # 配置类,如登录拦截器、全局异常处理小程序前端则是典型的原生小程序目录,pages下按业务页面划分:menu、cart、orderConfirm、orderList、orderDetail等,utils目录里放着封装好的 request 请求工具。整体来说目录不算复杂,适合拿来做二次开发底子。
2. 小程序端的核心页面:从选菜到订单,数据到底怎么转
2.1 用户进店后的完整操作闭环
一个用户从打开小程序到完成点餐,要经过的路径是:首页加载菜品分类 → 点击某个分类浏览菜品 → 点击菜品加入购物车 → 进入购物车调整数量 → 确认订单页填写备注/选择配送或自取 → 提交订单 → 支付 → 订单列表查看状态。
这套工程在小程序端把这条链路完整串起来了。首页菜单页通常是左右两栏布局,左侧是分类列表,右侧是对应分类下的菜品卡片,菜品卡片上展示图片、名称、价格、销量和是否售罄。用户点“加入购物车”后,底部购物车栏会实时显示已选数量和总金额,点击可进入购物车页进行加减操作。
这里有个重要的数据流转原则:页面之间只传必要的业务参数,不传重数据。比如从菜品卡片跳转到订单确认页,只需要把购物车数据通过本地存储带过去,而不是每次跳转都重新请求接口。这样页面的切换响应会非常快,体验上更接近原生 App。
2.2 微信小程序登录与手机号获取:code 在前端只走了一圈
点餐系统里登录是绕不开的环节。这套工程采用的是微信生态的标准登录流程,而不是让用户手动输入账号密码。
小程序端调用wx.login()获取一个临时 code,这个 code 有效期很短,把它发给后端接口后,后端拿着 code 去微信服务端换openid和session_key。其中openid是用户在当前小程序下的唯一标识,后端用它可以判断用户是否已注册,并生成自己的登录态 token 返回给前端。前端拿到 token 后存到 storage 里,后续所有请求都在请求头带上这个 token,后端拦截器据此识别用户身份。
手机号获取则是点餐场景里的一个硬需求,因为订单通知、取餐提醒通常要发到用户手机号。小程序端不能直接拿到明文手机号,需要在页面里放一个<button open-type="getPhoneNumber">,用户点击授权后前端拿到一个手机号临时 code,再传给后端。后端用这个 code 调用微信官方接口换取真实手机号,然后绑定到当前用户账号上。
这里要特别提醒一个点:千万别把微信小程序的 AppSecret 写在代码里,更不要传到前端工程中。AppSecret 是后端调用微信接口时用的凭证,一旦泄露,别人就能冒充你的小程序去换用户信息。正确的做法是把配置写在application.yml或环境变量里,由后端持有。
2.3 菜品规格选择与库存约束
餐饮点餐和普通商品加购最大的区别在于“规格”。一道菜可能有大份/小份、辣度选择、加料项,这些在页面上通常用单选组或复选组来实现,也就是微信小程序里的radio-group、checkbox-group。早期版本的小程序单选控件样式比较有限,二开时要注意自定义样式,尤其是选中态的颜色和菜品卡片背景色要区分开,不然用户在快速加购时容易点错。
库存约束这块,前端能做的只是展示层面的“售罄置灰”。真实的库存校验必须放到后端做,原因是小程序端可以随时被绕过,修改请求参数模拟下单。后端在创建订单时需要通过数据库或 Redis 的库存数据校验菜品是否还有余量,如果库存不足则返回明确的错误提示,例如“菜品已售罄”。前端拿到这个错误提示后在购物车中标记对应菜品,引导用户更换或移除。
把库存判断放在后端,同时用 Redis 扣减库存,再配合 MySQL 做最终一致性,是这套工程最值得学习的设计思路。后文讲下单接口时会专门展开。
2.4 购物车为什么放在本地缓存而不是服务端
很多初学者拿到这套工程会疑惑:购物车数据怎么不存数据库?实际上点餐业务中购物车属于高频临时的会话数据,用户加购、减购、清空都在极短时间内发生,如果每一步都请求后端更新数据库,会产生大量无意义的写入请求,压力大且响应慢。
把购物车数据放在小程序本地 storage 中,前端增删改查零延迟,用户结算时一次性把购物车明细提交给后端创建订单。后端收到订单明细后只需做两件事:校验菜品当前价格和库存、在事务中扣减库存并写入订单表。这种做法既减少了后端负担,也贴合真实点餐场景——购物车本来就是用户自己的草稿箱,不需要和后端实时同步。
本地缓存有两个细节值得提。第一,缓存的数据结构建议用“菜品ID为 key 的对象数组”,而不是纯数组,因为按 key 增删查重快很多;第二,金额计算不要依赖本地缓存的单价,结算页展示的金额只能作为参考,后端创建订单时会根据数据库当前价格重新计算总价。这样即使前端数据被篡改,后端也能拦截,保证金额不被恶意修改。
2.5 下单与订单状态流转
用户提交订单后,订单会有几个状态:待支付、已支付、制作中、待取餐/已完成。这个小程序的订单列表页就是围绕这些状态做的,不同状态显示不同的操作按钮——待支付显示“去支付”,制作中显示“等待出餐”,待取餐显示取餐码。
订单状态的更新可以有两种触发方式:用户付款后回调更新为已支付;商家在管理端操作变更状态。这里要注意状态流转必须是单向的,不允许从已完成回退到待支付,后端 Service 层要做状态值的校验。很多人在二开时忽略这一点,导致订单状态错乱,尤其是并发场景下用户和商家同时操作同一条订单时,容易出现库存和订单不匹配的情况。
3. SpringBoot 后端:接口设计、数据库建模和几个关键“为什么”
3.1 后端接口总览
后端接口大体上围绕两类对象:用户和订单。我把工程里的核心接口整理出来,方便你对照前端页面去理解:
| 方法 | 路径 | 功能 |
|---|---|---|
| POST | /api/auth/login | 用户 code 登录,换取 token |
| POST | /api/auth/phone | 绑定用户手机号 |
| GET | /api/category/list | 菜品分类列表 |
| GET | /api/dish/list | 按分类查询菜品,可带上架状态过滤 |
| POST | /api/order/create | 创建订单,入参为购物车明细和地址备注 |
| GET | /api/order/list | 当前用户订单列表 |
| GET | /api/order/detail | 订单详情,含菜品明细快照 |
接口路径设计上遵循了 REST 的基本规范,Controller 层很薄,核心逻辑都在 Service 层。这样做的直接好处是:后续如果要把接口暴露给管理后台,或者新增一个小程序商家版,Service 层可以直接复用,不需要大改。
3.2 订单表为什么要“冗余”菜品快照
数据库建模通常是这套工程里最值得研究的部分。菜品模块设计了分类表和菜品表;用户模块设计了用户表;订单模块分成了订单主表和订单明细表。其中订单明细表除了记录订单ID、菜品ID、数量外,还会冗余一份菜品名称和单价进去。
为什么要冗余?因为菜品价格和名称不是一成不变的。商家今天搞促销把鱼香肉丝从 18 元改成 16 元,明天可能又改名。如果订单明细表里只存菜品ID,下单后菜品改了价格,历史订单再查详情时就会显示新的价格,对账和用户投诉都会出问题。冗余快照后,订单一旦生成,其明细中的价格和名称就固化了,之后无论菜品如何变化,历史订单都保持原样。
这是业务系统设计里很经典的一个取舍。数据库第三范式要求尽量减少冗余,但业务场景又要求订单快照不可变,两者冲突时通常以业务需求优先。课程设计里如果能把这一点写进设计文档,会是很明显的加分项。
3.3 下单接口的核心逻辑:事务、订单号与库存扣减
下单接口是整个后端工程的重头戏。看源码时会发现,创建订单的方法上加了@Transactional注解。这个注解保证了下单过程要么全部成功,要么全部回滚——订单写入失败时,库存不能减少;库存扣减失败时,订单不能写入。这样一个多表操作的整体原子性是点餐系统稳定性的根基。
订单号生成也是值得关注的地方。数据库自增ID不适合直接作为对外订单号,一是会暴露每日订单量,二是有些场景需要提前生成业务流水号。工程里一般会采用“时间戳 + 随机数 + 用户标识”的方式生成订单号,例如:
String orderNo = "ORD" + System.currentTimeMillis() + String.format("%04d", new Random().nextInt(10000));这个方案虽然简单,但并发量高时仍可能碰撞,更稳妥的是利用 Redis 自增序列生成每日递增编号。二开时如果订单量预期比较大,建议把订单号生成逻辑抽成独立的组件,用 RedisINCR配合日期前缀。
库存扣减的推荐写法也不是直接UPDATE dish SET stock = stock - 1 WHERE id = ?这么简单,而是配合 Redis 做预扣减:用户提交订单时先从 Redis 扣减并校验是否成功,下单完成后异步把最终库存回写数据库。如果你的课程设计不打算引 Redis,也可以先用乐观锁实现,在数据库加version字段,更新时携带版本号,防止并发超卖。
3.4 统一返回结构与全局异常处理
后端接口的返回结构如果不统一,前端联调会非常痛苦。这套工程定义了统一的Result返回体,结构大致是code + msg + data。code 为 200 表示成功,其他为业务错误码,前端可以根据 code 做统一提示,而不是每个接口都单独解析数据格式。
配合统一返回结构的还有全局异常处理。SpringBoot 的@RestControllerAdvice注解可以把 Service 层抛出的业务异常统一拦截,转换成规范化的错误响应。比如库存不足、菜品已下架、token 过期这些情况,不用在每个 Controller 里写重复的 try-catch,异常处理器会自动接管。这个机制对代码整洁度帮助特别大,也是像 MyBatis-Plus 这类框架能把 CRUD 做得如此简洁的原因之一——框架通过自动配置把重复基础设施全部兜住了。
4. 前后端联调最容易断的三个环节:登录、手机号、订单数据格式
4.1 登录链路:code 换 token 的完整流程
前后端联调时,第一个最容易卡住的地方就是登录。前端wx.login()拿到的 code,必须被后端用appid + secret去请求微信的jscode2session接口,才能换取openid。这套工程里,后端代码通常长这样:
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code";这里有个实际的麻烦:本地开发时后端机器可能没有被微信服务器信任,或者你暂时没有可用的 appid 和 secret。我的习惯是在配置里加一个mock-login开关,本地联调时自动用一串固定字符串作为模拟 openid,不需要真实请求微信接口。第一次把整套工程跑通时,这个开关可以省下大量时间。
4.2 手机号获取的两个高频坑
手机号获取是另一个联调重灾区。现在推荐的做法仍然是前端用getPhoneNumber组件获取临时 code,把 code 传给后端,由后端调用微信接口换取真实手机号。
第一个坑是 code 的一次性:getPhoneNumber返回的 code 只能用一次,换到结果后立即失效。如果你在前端调试时重复调用,第二次就会报错。第二个坑是旧版手机号解密方式依赖session_key,并且受会话有效性影响,很多老代码还在用 AES 解密手机号,但现在微信已逐步收紧这类接口,新开发时优先采用后端请求官方接口换手机号的方式,具体接口名和参数以当前官方文档为准,不要照抄旧文章里的代码。
移动端还有安卓和 iOS 的差异。有些机型在用户拒绝授权后,getPhoneNumber的返回码不同,前端要对“主动拒绝”和“系统错误”分别提示,不能都当成“获取失败”弹窗了事。
4.3 token 鉴权与请求头规范
用户登录后,后端返回的 token 会存在小程序 storage 里。前端封装request工具时,需要从 storage 读取 token 并放进请求头:
wx.request({ url: 'https://your.domain.com/api/order/list', header: { 'Authorization': 'Bearer ' + token } })后端用一个拦截器统一校验 token,对/api/auth/login、/api/auth/phone这类无需登录的接口做白名单放行,其他接口全部校验。校验不通过返回 401,前端拿到 401 应该做统一处理:清除本地 token、跳转登录页重新登录。
这里建议二开时把 token 过期时间设置为 24 小时,并且后端支持刷新机制,避免用户点餐到一半突然被踢下线,体验特别差。
4.4 联调时的高频报错排查表
| 报错现象 | 常见原因 | 解决方法 |
|---|---|---|
| request fail | 域名未配置或不在合法域名列表,或本地未勾选“不校验合法域名” | 开发工具详情里勾选“不校验合法域名”;上线前配置 HTTPS 合法域名 |
| 404 url not found | 路径拼写错误或 Controller 没扫描到 | 检查 RequestMapping 拼写,确认启动日志中 mapper 扫描路径 |
| 401 | token 缺失或过期 | 检查请求头是否带 Authorization,后端检查 token 有效期 |
| 手机号解密失败 | code 已失效,或选用了过时的解密方案 | 确认 code 一次性使用,按最新官方接口实现 |
| 数据库连接失败 | MySQL 未启动或账号密码配置错误 | 核对 application.yml 数据源配置,确认数据库服务运行中 |
联调时我习惯在微信开发者工具的 Network 面板里直接看请求,足够了。如果想看更详细的请求内容,也可以用 Charles 抓包小程序流量,能看到完整的请求头、请求体和响应体,排查数据格式问题很方便。
5. 拿到源码后从 0 到 1 跑起来,以及 3 个高频启动报错
5.1 后端环境准备与配置核对
先把后端跑起来,需要准备的本地环境是 JDK 8 或 JDK 17、Maven、MySQL 和 Redis。如果你的机器上还没有这些,先装好再继续。
后端工程里需要重点关注application.yml或application.properties配置文件,以下几项必须改成你自己的:
- 数据库地址、账号、密码;
- Redis 连接信息;
- 小程序 appid 和 secret(本地模拟登录时可不填,但要保持 mock-login 开关开启)。
数据库脚本导入方式很简单:创建一个新的数据库,执行工程里的.sql脚本即可。脚本里通常会包含用户表、菜品表、订单表建表语句,以及几条测试菜品数据和初始管理员账号。
5.2 前端工程导入与参数修改
后端启动成功后,用微信开发者工具导入小程序前端工程。导入后第一件事是把project.config.json里的 AppID 换成自己的测试号 AppID。没有 AppID 的话,开发者工具也支持使用测试号,但部分接口(如手机号获取)依赖真实权限,本地跑业务流程时可以先忽略。
本地开发时,在微信开发者工具的“详情 → 本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,这样请求http://localhost:8080或局域网 IP 也不会被拦截。
前端工程里如果有写死的接口地址 baseURL,需要一并改成你后端实际启动的地址。注意真机预览时,localhost指向的是手机本身,要改成电脑的局域网 IP,比如http://192.168.x.x:8080。
5.3 三个高频启动报错:版本太高、时区、端口占用
第一类报错和 SpringBoot 版本有关。如果你本机的 Maven 默认拉取的是 Spring Boot 3.x,而工程基于 Spring Boot 2.x 编写,就会出现包名冲突或者自动配置失效的问题,最常见的是javax和jakarta命名空间不一致。遇到这种情况不要急着改代码,先在pom.xml里固定工程原本使用的 Spring Boot 版本,并确保本地 JDK 版本与之匹配。Spring Boot 版本不是越高越好,题目的工程依赖什么版本,就尽量用对应版本启动,否则排除兼容问题就要花掉半天。
第二类报错是 MySQL 连接时区问题,报错信息类似于The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。这是因为 MySQL 连接串里没有指定 serverTimezone。在数据库 URL 后面加上?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8即可解决,同时注意保持数据库和连接串的字符集一致,防止中文乱码。
第三类报错是端口占用。默认 8080 端口如果被其他服务占用,后端启动会直接失败。两种处理方式:改掉后端配置里的端口,或者本地停掉占用进程。我建议直接改端口,例如 18080,顺便把前端 baseURL 同步改掉,避免每次启动都被这个报错打断思路。
5.4 怎么判断整套系统真的跑通了
跑通的标准不是后端控制台没有红色报错,而是走完整条业务链路。我会按下面的顺序验证:
- 打开小程序首页,能看到分类和菜品数据;
- 点击菜品加入购物车,底部购物车数量和总金额变化正确;
- 进入购物车调整数量,金额同步变化;
- 提交订单,后端日志出现订单创建记录,数据库订单表新增一条记录;
- 在订单列表页能看到刚创建的订单,状态为“待支付”。
如果这五步都符合预期,说明前后端联调已经通了。之后再去做手机号绑定、支付回调这类依赖真实微信能力的环节,就不会因为基础链路没通而找不到问题在哪。
6. 提审上线前必须处理的事:账号认证、支付、隐私协议与扩展方向
6.1 账号主体与认证
如果你不满足于在开发者工具里自嗨,想把这个小程序真正发布到微信生态里,第一关就是账号主体和认证。个人主体和企业主体能申请的能力差别很大,点餐场景涉及的支付、会员、信息收集等能力,企业主体才能完整支持。认证本身需要一定费用,而且按年计算,这个成本在项目启动前就要想清楚,不要做完才发现主体资质不匹配。
课程设计或者学习演示用的小程序,通常用一个测试号就够了,不需要做正式认证。但如果有真实商家准备投入使用,建议尽早注册企业主体小程序账号,并提前规划好小程序名称——餐厅名称如果被占用,后面再改名很麻烦。
6.2 微信支付申请与“模拟支付”方案
支付是小程序点餐闭环里最敏感也最麻烦的环节。微信支付商户号的申请需要企业资质,涉及营业执照、对公账户等一堆材料,个人开发者基本走不通这个流程。课程设计场景下,工程里通常会预留一个“模拟支付”开关:提交订单后不真实调起微信支付,而是直接把本地订单状态置为“已支付”,然后进入制作中流程。这样能完整演示业务闭环,又不依赖真实商户资质。
我建议在二开时把支付能力抽象成一个接口,正式环境走微信支付回调,演示环境走模拟支付。这样以后拿到商户资质,只需要替换支付的实现类,业务层不用任何改动。
6.3 用户隐私保护指引与 HTTPS 合法域名
小程序审核最容易被忽略的是用户隐私保护指引。小程序里获取了用户手机号,就必须在后台配置隐私保护指引,声明收集手机号的目的——比如“用于订单状态通知和取餐提醒”。如果未声明,真机调用getPhoneNumber时会直接失败,这个坑在开发阶段不明显,提审时却会被卡得很死。
同时,正式环境要求所有请求域名必须是 HTTPS 且在小程序后台完成合法域名配置。HTTP 的本地开发地址无法用于线上版本,需要后端部署到带 HTTPS 证书的服务器,并在小程序管理后台的“开发管理 → 服务器域名”中添加 request 合法域名。这一步不做,上线后小程序里所有请求都发不出去。
6.4 从课程设计到真实商用,这个工程还能往哪些方向扩展
如果你不满足于把作业交掉,想把这个餐厅点餐小程序做成真正能用的产品,下面几个方向是性价比最高的:
- 桌台码点餐:每张桌子生成带 scene 参数的专属二维码,用户扫码进入小程序时自动带上桌号,下单后订单直接关联桌台,后厨出餐和前台叫号都更精准;
- 取餐叫号大屏:后厨或前台放一个电视看板,实时滚动展示“制作中”状态的订单,出餐后在客户端同步推送取餐通知;
- 后厨出餐单:给后厨单独做一个简化版界面,只显示当前需要制作的菜品和数量,按时间倒序排列;
- 菜品评价与商家回复:点餐闭环完成后增加评价入口,商家可以在管理后台回复,提升复购率;
- 管理后台:如果当前只有后端接口没有页面,可以用 Vue 写一个后台管理界面,构建后把静态文件丢进 SpringBoot 的
static目录统一部署,一个人运维也方便。
这轮的源码工程把点餐核心闭环搭好了,接上上面任意一个方向,都比从零开始做一个完整系统省太多事。
最后聊个我自己的习惯:拿到带编号的源码工程,我先做的不是急着启动,而是把包名、应用名、数据库名这些占位标识统一改掉,再把配置里的账号密码换成自己的占位符。这样后面无论是提交作业还是做二次开发,都不会出现泄露或命名混乱的隐患。这个餐厅点餐工程最值得保留的设计就是订单快照和库存扣减流程,改业务时优先动前端页面,后端表结构和事务逻辑建议保持稳定。祝你跑通顺利。