简介:《微信小程序-餐饮点餐外卖-源代码》是一套面向餐饮商家、小程序开发者和初学者的可运行项目方案,针对餐厅在线点餐、外卖配送、叫号排队、微信支付等真实场景提供代码实现,适合作为创业原型、课程设计或实战练手素材。压缩包共521个文件,整体仅833KB,以JavaScript、WXML、WXSS、JSON四类代码文件为主,分别对应逻辑处理、页面结构、样式布局和项目配置,另含WXS脚本与少量PNG图标资源,目录清晰、模块化较好,导入微信开发者工具即可直接运行体验。已有2331人学习下载,作者zyqytsoft,适合具备一定前端基础、想快速上手微信小程序开发的学习者。通过学习该项目,可理解点餐流程、购物车联动、订单状态管理、支付对接、排队叫号等功能的实现思路,也可在其架构上做定制化扩展,避免从零搭建的重复劳动,对梳理完整业务链路和提升小程序实战能力都很有帮助。
1. 开箱即用的餐饮外卖小程序:它到底帮你解决什么问题
一套餐饮点餐外卖的源码包下载下来,最常见的结局是卡在登录、图片裂开或者订单状态不动,最后只能重新开一个空项目;真正的问题往往不是代码坏了,而是配置没对齐。这套开箱即用的小程序源码给我的第一印象是“三个端被拆得很干净”:小程序前端、Spring Boot 后端、SQL 初始化脚本,目录里每一样都是现成的,导入就能看到完整菜品页。对想快速跑通一个点餐商城的开发者来说,它比从零写能省下大半天时间;对要拿点餐业务做二次开发的熟手,订单状态机和购物车逻辑也能直接抄过来改。这篇文章就按我平时拆项目的方式,带你把它从目录结构看到真机下单。
2. 项目全貌与目录结构:三端如何在一个仓库里协作
拿到源码先不要急着双击导入,先打开目录看一遍。这类点餐外卖项目通常由三部分组成:给顾客用的小程序端、给商家用的管理后台、以及连接两端的服务端和数据库。三个端在一个仓库里分目录放,跑起来之后各自负责不同角色,数据最终都落在同一个 MySQL 库里。
2.1 技术选型:为什么是原生小程序加 Spring Boot
小程序端用的是微信原生语法,WXML 写结构、WXSS 写样式、JavaScript 写逻辑,没有引入 uni-app 或 Taro 这套编译链。好处是导入微信开发者工具后不需要额外构建,改完代码直接编译预览,排错路径短,这对“开箱即用”这个目标非常重要。我拆过不少套类似源码,凡是引入跨端框架的,十套里有三套会卡在依赖安装阶段。
服务端选了 Spring Boot 2.x 加 MyBatis,配合 MySQL 5.7 或 8.0。订单、支付、配送这类流程涉及事务和状态流转,用 Java 这套组合在工程上最稳妥;MyBatis 的 Mapper 文件把 SQL 和业务代码分开,二次改表结构时不用在 Java 代码里翻 SQL。管理后台则是轻量的 Web 页面,负责菜品上下架、订单查看、营业状态开关。如果你拿到的版本没有独立管理端,那这套操作一般会落在后端接口和小程序的管理员入口里。
2.2 目录结构与请求流向:源码包里的每一层都在干什么
典型的目录结构长这样,先把地图看清楚,后面改代码时才知道去哪里找文件。
wxfood/ ├── miniprogram/ # 小程序前端 │ ├── pages/ │ │ ├── index/ # 首页:分类与菜品列表 │ │ ├── cart/ # 购物车页 │ │ ├── checkout/ # 结算页:地址、配送费、优惠 │ │ ├── order/ # 订单列表与详情 │ │ └── user/ # 我的:地址管理、会员信息 │ ├── utils/ │ │ ├── request.js # wx.request 封装,统一 baseURL 和鉴权 │ │ └── auth.js # 登录态与 token 管理 │ └── app.js # 全局配置、登录初始化 ├── server/ # Spring Boot 后端 │ ├── src/main/java/xxx/ │ │ ├── controller/ # 接口层:/api/user、/api/order、/api/menu │ │ ├── service/ # 业务层:下单、支付回调、配送规则 │ │ ├── mapper/ # MyBatis Mapper 接口 │ │ └── domain/ # 实体对象与 DTO │ ├── src/main/resources/ │ │ ├── mapper/ # Mapper XML,SQL 集中在这里 │ │ └── application.yml # 数据源、端口、微信配置 │ └── pom.xml ├── sql/ │ └── food.sql # 建库建表 + 测试菜品数据 ├── admin/ # 商家的 Web 管理后台 └── docs/ # 接口文档与部署说明请求流向是理解这套源码的第二把钥匙。小程序页面里调用utils/request.js封装的wx.request,带着登录后拿到的 token 请求后端/api开头的接口;Spring Boot 的 Controller 接收参数,Service 层处理业务和事务,Mapper 通过 XML 里的 SQL 读写 MySQL,返回 JSON 给前端渲染。菜品的增删改在管理端发起,走的是另一组管理接口,和小程序用户端接口分开,避免权限混在一起。
提示:找代码时先看
server/src/main/resources/mapper目录下的 XML,这套项目的查询逻辑基本都写在 SQL 里,Java 代码反而更薄。
2.3 数据库核心表:菜品、订单、配送靠什么关联起来
数据库是这类项目最值得先读的部分,因为订单、菜品、配送、用户的关联关系都体现在表结构上。food.sql 里通常包含这些表:
| 表名 | 角色 | 核心字段 | 说明 |
|---|---|---|---|
user | C 端用户 | openid,nickname,phone | 小程序登录后写入用户信息 |
menu_category | 菜品分类 | name,sort | 首页左侧分类列表 |
menu_item | 菜品 | category_id,name,price,image,stock | 每个菜品归属一个分类 |
cart | 购物车 | user_id,menu_id,count | 服务端购物车,部分版本存在本地 |
order | 订单主表 | order_no,user_id,status,total_amount,delivery_fee | 一单一条记录 |
order_item | 订单明细 | order_id,menu_id,count,price | 一个订单对应多条明细 |
delivery_address | 配送地址 | user_id,province,city,detail,lat,lng | 下单时选地址 |
shop | 店铺配置 | name,business_hours,delivery_fee,status | 单店或连锁店铺信息 |
订单主表和订单明细表是典型的一对多关系,下单时必须在一个事务里同时写两张表。配送费和营业时间这两个业务参数放在shop表里,business_hours用字符串形式存“10:00-21:30”,下单接口在 Service 层解析并判断当前是否在营业时段内。order.status用数字表示订单状态,常见约定是 0 待支付、1 待接单、2 配送中、3 已完成、4 已取消,后面讲状态机时会再展开。
如果把menu_item表建表的 SQL 打开看,核心结构大概是这样的:
CREATE TABLE `menu_item` ( `id` int NOT NULL AUTO_INCREMENT, `category_id` int NOT NULL COMMENT '所属分类ID', `name` varchar(64) NOT NULL COMMENT '菜品名', `price` decimal(10,2) NOT NULL COMMENT '单价', `image` varchar(255) DEFAULT NULL COMMENT '图片URL', `stock` int DEFAULT '99' COMMENT '库存', `status` tinyint DEFAULT '1' COMMENT '1上架 0下架', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里price用decimal而不是float,是支付项目的基本功,避免浮点误差导致订单金额对不上。status字段控制上下架,管理端切换状态时只更新这个字段,不需要删菜品。image字段存的是图片 URL,后面避坑章节里会说到图片裂开的问题,根源就在这个字段的设计上。
3. 核心模块拆解:从加购物车到支付成单的完整链路
这一章把整条业务主链路拆开讲,按你在小程序上实际操作一次的顺序走:浏览菜品、加购物车、结算、下单、支付、查订单状态。每一段都会贴出核心代码,并说明这段代码在整个链路里的职责和参数含义。
3.1 小程序端:购物车与菜品的本地状态管理
购物车在这套项目里用的是本地存储方案,数据放在wx.getStorageSync('cart')里,没有在加购时立刻请求后端。这样设计的好处是响应快,用户连续加菜不需要等待网络往返;风险是换设备或清缓存后购物车会丢,所以结算后会在下单成功时清除本地购物车。
加购逻辑在首页index.js里是这么实现的:
// 用户点击“+”按钮时触发 addToCart(e) { const { id, name, price } = e.currentTarget.dataset; // 先从本地缓存读取购物车,没有则初始化为空数组 const cart = wx.getStorageSync('cart') || []; // 按菜品id判断是否已在购物车中 const exist = cart.find(item => item.id === id); if (exist) { exist.count += 1; // 已存在则数量+1 } else { cart.push({ id, name, price, count: 1, checked: true }); } // 写回本地存储,并更新底部角标数量 wx.setStorageSync('cart', cart); this.updateCartBadge(cart); }这段代码的关键点是e.currentTarget.dataset里的三个字段来自 WXML 上的>@PostMapping("/order/create") public Result createOrder(@RequestBody OrderDTO dto) { // 1. 检查店铺是否在营业时间内 Shop shop = shopMapper.findById(dto.getShopId()); if (!TimeUtils.isInBusinessHours(shop.getBusinessHours())) { return Result.fail("当前不在营业时间内"); } // 2. 计算配送费和订单总额 BigDecimal deliveryFee = deliveryFeeRule.calc(dto.getAddress()); BigDecimal total = BigDecimal.ZERO; for (OrderItemDTO item : dto.getItems()) { MenuItem menu = menuMapper.findForUpdate(item.getMenuId()); if (menu.getStock() < item.getCount()) { return Result.fail("菜品库存不足: " + menu.getName()); } total = total.add(menu.getPrice().multiply(new BigDecimal(item.getCount()))); } total = total.add(deliveryFee); // 3. 生成订单号,写入主表和明细表 String orderNo = OrderNoGenerator.gen("FOOD"); Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(dto.getUserId()); order.setStatus(OrderStatus.CREATED.getCode()); order.setTotalAmount(total); order.setDeliveryFee(deliveryFee); orderMapper.insert(order); // 事务由 @Transactional 保证 orderItemMapper.batchInsert(dto.getItems(), order.getId()); return Result.ok(orderNo); }
这段逻辑里有几个容易踩坑的地方。findForUpdate加了行锁,防止两个用户同时购买最后一份菜品时库存扣成负数;如果项目里的 Mapper 没有写FOR UPDATE,高并发下库存会出现超卖,这是电商类项目最经典的问题。deliveryFeeRule.calc是一个配送费计算组件,常见规则是满 30 元免配送费,否则按距离收 3 到 6 元,参数都可以在shop表里配置。
订单号用OrderNoGenerator.gen生成,前缀 “FOOD” 加时间戳再加随机数,避免多商户同时下单时订单号冲突。整个方法上标了@Transactional,如果明细插入失败,订单主表也会回滚,不会产生“有订单没明细”的脏数据。结算页拿到的返回结果是订单号,然后跳转到收银模拟页去走支付。
3.3 订单状态机与外卖配送:从待支付到已完成的状态流转
订单状态在整套系统里用一个整数枚举控制,我见过的版本约定是:0 待支付,1 待接单(已支付),2 配送中,3 已完成,4 已取消。支付成功、商家接单、骑手送达这三个动作各触发一次状态变更,每次变更都被记录在订单操作日志里,方便商家后台做对账。
状态机的 Java 枚举定义大致是这样的:
public enum OrderStatus { CREATED(0, "待支付"), PAID(1, "待接单"), DELIVERING(2, "配送中"), FINISHED(3, "已完成"), CANCELED(4, "已取消"); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code = code; this.desc = desc; } public OrderStatus next() { switch (this) { case CREATED: return PAID; // 支付成功后 case PAID: return DELIVERING; // 商家接单后 case DELIVERING: return FINISHED; // 用户确认收货后 default: return this; } } }这里有一个容易忽略的细节:取消操作不是next()的一部分,而是在待支付状态下由用户主动触发,直接把状态置为CANCELED;待接单状态下用户不能自行取消,需要联系商家处理。设计时要区分“正常流转”和“异常操作”,如果用同一个方法处理,会出现已支付订单被用户误取消,退款流程没有对应实现的问题。
配送模块关心的是配送费计算和地址匹配。前端地图选点后,结算页把detail地址和经纬度传给后端;后端用经纬度算直线距离再加一个绕路系数得出配送距离,最后套用阶梯计价规则。这套源码简化了这部分,deliveryFeeRule.calc里按距离分段:3 公里内 6 元,之后每公里加 2 元,超过 5 公里不配送。参数在shop表的config_json字段里维护,改价不需要动代码。
4. 跑起来:从导入项目到真机验证的完整过程
前面把结构和逻辑都过了一遍,现在进入“能不能跑到我电脑上”的关键环节。很多源码包本身没坏,坏在环境缺失或者配置没改。这一章按我实际操作的顺序来,每一步都解释为什么要这样做,失败时从哪里排查。
4.1 环境准备:先装齐这四样再动手
跑这套项目需要四样基础环境:微信开发者工具、JDK 1.8 或 11、Maven 3.6 以上、MySQL 5.7 或 8.0。微信开发者工具去官网下载稳定版,JDK 和 Maven 装好后在命令行验证一下版本号,MySQL 安装时记住 root 密码。这些环境都是常规配置,没有特殊版本要求。
java -version mvn -version mysql --version三条命令输出正常再继续下一步。如果 Maven 命令不在系统 PATH 里,后面启动后端时会报mvn: command not found,需要手动配置 Maven 的环境变量。这一步虽然基础,但我见过好几次中途翻车,都是因为 Maven 没配好就急着导入 IDEA。
4.2 初始化数据库与配置后端:先让服务能吐 JSON
打开 MySQL 命令行,执行 sql 目录下的初始化脚本:
mysql -uroot -p < sql/food.sql这个脚本会创建数据库wxfood、所有业务表和一批测试菜品数据。执行完可以用SHOW TABLES;确认表是否齐全,看到前面提到的menu_item、order等表就说明导入成功。然后打开server/src/main/resources/application.yml,把数据库账号密码改成你自己环境的:
spring: datasource: url: jdbc:mysql://localhost:3306/wxfood?useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password server: port: 8080这里有两个容易错的点。serverTimezone=Asia/Shanghai必须加上,否则数据库连接时会报时区错误,或者插入的时间比真实时间差 8 小时,导致订单列表里的时间全部错乱。端口默认 8080,如果本机 8080 被占用,改成 8081 之后别忘了同步改小程序的请求地址,这个细节很多人会漏。
启动后端有两种方式,命令行直接用 Maven 插件,IDEA 里则右键启动类。命令行方式如下:
cd server mvn spring-boot:run看到Started Application日志后,用 curl 验证接口是否响应:
curl http://localhost:8080/api/shop/list返回 JSON 数组里带店铺信息,说明后端已经正常启动,数据库连线也没有问题。如果启动时报Access denied,回 4.2 检查username和password;如果报数据库不存在,确认food.sql是否真的执行成功。
4.3 小程序端导入与 AppID 配置:决定你能否真机预览
打开微信开发者工具,选择“导入项目”,目录指向miniprogram文件夹。AppID 有两种选择:如果你只是预览,用测试号即可;如果你要在手机端真机预览,需要在小程序管理后台注册并拿到自己的 AppID。测试号能跑通整个流程,但无法调用微信支付等需要商户号的真实能力,这个限制先知道,后面避坑章再展开。
导入后打开utils/request.js,找到BASE_URL并改成后端地址:
const BASE_URL = 'http://localhost:8080';本地开发时,还需要在开发者工具的“详情-本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。这是因为微信默认只允许请求 HTTPS 域名,而本地开发用的 HTTP 地址会被拦下来。勾选后点编译,首页应该能显示出测试菜品和分类。
提示:真机预览时,手机和后端必须处于同一局域网,
localhost要改成你电脑的局域网 IP,否则手机上会一直请求超时。
4.4 联调验证清单:五个功能点确认这套源码真正可用
跑通不是首页能显示就结束,至少要确认这五个点都正常工作,再往下做二次开发。我每次联调都按这个清单走,缺一项都不敢说“开箱即用”:
- 首页分类切换后菜品列表更新,菜品图片正常显示。
- 点击加购按钮,底部购物车角标数量增加。
- 进入购物车页,修改数量后总计金额实时变化。
- 提交订单并模拟支付成功后,订单列表出现一条“待接单”记录。
- 商家管理后台能看到这笔订单,接单后小程序端状态变为“配送中”。
如果第一项图片是裂开的,跳到第 5.2 小节看图片地址的坑;如果第四项卡在“待支付”,看 5.3 小节的模拟支付说明。整套流程走完之后,你才算是真正把这个项目跑了起来,下一步才是考虑改造它。
5. 避坑指南:四类让这套源码“开不了箱”的常见问题
这一章记录的是真实踩过的坑。每条结构统一为现象、原因、解决,按出现频率从高到低排,你跟着做大概率能避开。
5.1 登录报错:换了 AppID 后 code2Session 直接失败
现象:小程序模拟器中打开首页,console报login failed: code2Session error,页面一片空白。很多人第一反应是代码逻辑不对,但实际是换了 AppID 后没有同步配置 AppSecret。
原因:小程序端调用wx.login拿到临时code,后端用这个code加appid、secret请求微信接口换取openid。源码里的application.yml默认写的是作者自己的 AppID 和 AppSecret,你导入后如果用的是测试号,这两个参数和测试号不匹配,微信接口就返回异常,登录流程直接中断。
解决:登录微信小程序管理后台拿到正式的 AppID 和 AppSecret,填入后端的application.yml中。用测试号开发时,也要把测试号的 AppID 填到小程序端app.js里,两处保持一致。登录态无非就是openid换取后端的 token,问题解决后首页才能正常渲染。
5.2 菜品图片裂开:本地路径和正式环境地址不通用
现象:分类切换正常,菜品名称和价格都有,就是图片位置空着或者裂图。
原因:这套源码的测试数据里,image字段很可能存的是相对路径,比如/images/dish1.jpg或者http://localhost/xxx这样的开发地址。在模拟器里本地资源可能会加载,但换了真机预览或者部署到线上域名后,这个地址就访问不到了。
解决:把menu_item.image的值统一改成可访问的图片 URL。本地开发用http://127.0.0.1:8080/images/xxx.jpg,并确保后端resources/static/images目录下有对应文件;上线时换成 CDN 或对象存储地址。如果不想改数据库,可以在小程序端写一个图片地址拼接方法,检测到image不是http开头时就自动补全前缀,这样数据不用动也能显示。
5.3 订单卡在待支付:模拟支付和真实回调是两条路
现象:提交订单后,订单列表里记录状态一直是“待支付”,即使已经点击了“模拟支付成功”按钮,也没有变成“待接单”。
原因:这套源码的“模拟支付”通常只是在本地把订单状态改成已支付,而真实微信支付流程是后端收到支付成功回调后再更新状态。如果你用的接口不是模拟支付接口,而是真实的下单支付逻辑,本地没有商户号就永远收不到回调,状态自然不动。
解决:开发阶段全部走模拟支付路径。在结算页确认调用的方法是mockPay(orderNo)而不是wx.requestPayment。mockPay的常见实现是把订单状态直接改为“已支付”,同时把PAID的时间和状态一起写库。后端接口POST /api/order/mockPay接收orderNo参数,返回success后刷新订单列表就能看到状态变化。等真正开通了微信支付,再替换成wx.requestPayment并配置商户号和回调地址。
5.4 订单时间差了八个小时:时区配置没有对齐
现象:订单列表里的时间比实际时间晚 8 小时,商家后台按天统计订单数时,数据全部归到前一天。
原因:MySQL 连接的 URL 里没有指定serverTimezone,Spring Boot 默认按 UTC 时区处理时间,中国时间比 UTC 早 8 小时,所以插入数据库的时间就偏了。这不是项目代码的问题,是驱动连接时区参数没有配对。
解决:确认application.yml的数据源 URL 里有serverTimezone=Asia/Shanghai。改完重启后端,把之前错的订单数据清掉重新测试。如果还不对,再看 MySQL 自身的default-time-zone配置,命令行执行SELECT NOW()确认数据库当前时间是否正常。两条都对齐后,状态流转时间就不会再差 8 小时。
5.5 后端端口冲突:8080 被占用导致启动失败
现象:启动 Spring Boot 时控制台红色报错,提示Port 8080 was already in use。
原因:本机有其他应用占用了 8080 端口,比如本地跑着另一个服务,或者系统软件就占用了这个端口。
解决:改application.yml里server.port为 8081 或其他空闲端口,同时把小程序端utils/request.js的BASE_URL同步改掉。注意管理后台的默认请求地址也可能写死了 8080,改完一起搜一下项目里所有localhost:8080,统一替换,避免前端连上后端但管理后台连不上。
6. 二次开发实战:把单店外卖改造成多商家点餐商城
跑通之后,大多数人的下一个需求是把它用于真实的餐饮运营场景。最常见的改造方向是从“单店外卖”升级为“多商家点餐商城”。这个改造不需要推翻重来,核心思路是在现有表结构和接口上增加一个“店铺维度”。
第一步是数据结构改造。给menu_item和order表各加一个shop_id外键,新建一张shop表存店铺信息、营业时间、配送费规则。这步让后端有了多商户的数据基础,原单店逻辑不变,只是每个店的数据带上归属标识。第二步是前端入口改造。原首页直接展示全部菜品,改造后需要先有一个“选择店铺”页,选中后把shop_id传给首页,首页请求GET /api/menu/list?shopId=xxx只加载对应店铺的菜品。第三步是商家后台改造。管理员登录后按shop_id过滤订单列表和菜品管理,甲店的管理员看不到乙店的订单,这步主要是接口层加一个参数判断。
改造的验证方法很简单:在shop表插入两家店铺,分别设置不同营业时间,给各自菜品下单,确认订单列表中两家店的数据互不串扰。购物车在跨店场景下要加一个规则——不允许把两家店的商品放在同一个结算单里,否则订单金额会被错误合并。四种状态流转不受影响,因为每个订单只属于一家店铺。
这套改造做完,整个项目的适用边界就从“一家店的员工自用”扩展到“一个区域多店入驻”。从那以后我每次拿到一套新源码,都会先把它完整跑通再动第一行代码,避免把原本可用的东西改成一堆不可控的变更;希望这份记录对你有帮助。
本文还有配套的精品资源,点击获取