微信小程序点餐系统源码解析:表结构设计与订单状态流转
发布时间:2026/10/9 20:31:03 锦皓数字建站

简介这份资源是微信小程序点餐系统的完整项目源码包面向希望掌握小程序全栈开发的学习者与开发者帮助解决从用户端到后台管理端的实战落地问题。压缩包共1343个文件约15.31MB涵盖js业务逻辑、wxml结构、wxss样式、json配置等小程序核心文件以及vue后台页面、java服务端代码、png与svg图片素材和sql脚本前后端与资源文件层次分明。项目围绕菜单展示、商品管理、购物车、订单状态流转、微信支付集成、消息推送与用户账户管理等模块展开并配有readme说明与bat启动脚本便于快速理解架构与数据流。目前已有1665人学习下载适合作为课程设计、毕业设计或小程序入门进阶的参考案例通过阅读源码可深入体会小程序的组件化设计、接口调用与用户体验优化思路。1. 点餐小程序这套源码真正值钱的是那张表结构很多人拿到「微信小程序点餐系统」的源码压缩包第一反应是解压、打开开发者工具、点编译然后盯着模拟器里那几道菜发呆——能跑但不知道从哪改起。我见过太多这样的场景某开发者把源码丢进项目里改了个店名就上线结果订单和菜品对不上退款退不了最后回滚重做。问题不在代码写得烂而在于没人先去看那张orders表是怎么设计的。点餐系统的本质不是页面是「菜品 → 购物车 → 订单 → 支付 → 出餐」这条状态链。小程序端只是这条链的展示层真正决定你能不能改、能不能扩展的是后端表结构和接口约定。这套源码通常包含小程序前端原生或 uni-app、一个轻量后端PHP 或 Node.js、一份 SQL 建表脚本以及一份说明文档。适合谁适合想接私活做餐饮类小程序的独立开发者、想拿一个完整业务练手的学生以及需要快速给小型餐饮店搭一套点单工具的技术负责人。我一般会建议先别急着跑先把docs/或sql/目录翻出来把表关系画在纸上。这一步花 30 分钟能省掉后面三天的调试。下面按「先看懂 → 再跑通 → 再改对 → 再避坑」的顺序拆开讲。2. 先看懂这套点餐系统的骨架目录、表结构与接口约定2.1 拿到压缩包后的第一件事目录分层与入口文件定位解压后不要直接点project.config.json先看根目录。典型的点餐系统源码目录长这样order-miniapp/ ├── miniprogram/ # 小程序前端 │ ├── pages/ │ │ ├── index/ # 首页/菜单 │ │ ├── cart/ # 购物车 │ │ ├── order/ # 订单确认 │ │ └── mine/ # 个人中心 │ ├── utils/ │ │ └── request.js # 统一请求封装 │ └── app.js ├── server/ # 后端 │ ├── api/ │ │ ├── menu.php # 菜品接口 │ │ ├── order.php # 订单接口 │ │ └── pay.php # 支付回调 │ ├── config/ │ │ └── db.php # 数据库配置 │ └── sql/ │ └── init.sql # 建表脚本 └── README.md先打开server/sql/init.sql这是整套系统的「黑匣子钥匙」。如果源码里没有这个文件只有一堆.php那你要警惕——说明作者可能把表结构藏在代码里后期迁移会很痛苦。2.2 五张核心表菜品、分类、订单、订单明细、用户不管前端用什么框架点餐系统的后端表结构基本逃不出这五张。下面是我从常见源码里提炼的最小可用结构你可以对照手里的init.sql逐字段核对-- 菜品分类 CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, sort INT DEFAULT 0 ); -- 菜品 CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(64) NOT NULL, price DECIMAL(10,2) NOT NULL, image VARCHAR(255), status TINYINT DEFAULT 1 -- 1上架 0下架 ); -- 订单主表 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, -- 0待支付 1已支付 2已完成 3已取消 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细 CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT NOT NULL, count INT NOT NULL, price DECIMAL(10,2) NOT NULL -- 下单时快照价 ); -- 用户 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) UNIQUE, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );逻辑说明order_item里的price必须是下单那一刻的快照价不能只存dish_id然后实时查dish.price。原因很简单——菜品调价后历史订单金额会跟着变对账直接翻车。参数上status用 TINYINT 而不是字符串是为了索引效率openid加 UNIQUE 约束防止同一用户重复插入。2.3 接口约定三个必须对齐的字段前端request.js里通常封装了统一请求后端返回格式要统一。我一般会检查这三个字段是否对齐字段含义常见错误code业务状态码0 成功有的用 200有的用 0前后端不一致data业务数据有时叫result有时叫listmsg错误提示直接返回英文报错用户看不懂如果源码里code用的是 200而前端判断的是res.code 0那所有接口都会走失败分支。这种问题在模拟器上不一定报错真机上直接白屏。排查方法打开开发者工具的 Network 面板看任意一个接口的返回 JSON再对照request.js里的判断条件。3. 把源码跑起来环境配置、数据库导入与真机调试3.1 后端环境PHP MySQL 的最小配置多数点餐源码后端是 PHP因为部署便宜。你需要一个 PHP 7.4 和 MySQL 5.7 的环境。如果本地没装用集成环境最快。配置server/config/db.php?php // 数据库配置 define(DB_HOST, 127.0.0.1); define(DB_USER, root); define(DB_PASS, your_password); // 改成你的密码 define(DB_NAME, order_system); // 连接 $conn new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME); if ($conn-connect_error) { die(json_encode([code 500, msg 数据库连接失败])); } $conn-set_charset(utf8mb4); // 必须 utf8mb4否则 emoji 菜品名会乱码逻辑说明utf8mb4不是可选项。菜品名里带一个「」或者特殊符号用utf8直接插入失败前端拿到空数据。参数上DB_HOST用127.0.0.1而不是localhost在某些 PHP 版本下能避免 socket 连接问题。导入建表脚本mysql -u root -p order_system server/sql/init.sql执行后进数据库确认五张表都在USE order_system; SHOW TABLES; -- 应该看到 category, dish, order_item, orders, user3.2 小程序端修改请求域名与 AppID打开miniprogram/utils/request.js找到baseUrl// 统一请求封装 const baseUrl http://127.0.0.1:8080; // 改成你的后端地址 function request(url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: baseUrl url, method: method, data: data, header: { content-type: application/json }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request };逻辑说明baseUrl在模拟器上可以写127.0.0.1但真机调试时必须换成局域网 IP如192.168.x.x因为手机访问不到你电脑的 localhost。这是「模拟器正常、真机接口失败」最常见的原因没有之一。3.3 真机调试的三个检查点真机跑不通按这个顺序查手机和电脑是否在同一局域网。手机连 WiFi电脑插网线但不在同一网段直接不通。后端服务是否监听 0.0.0.0。PHP 内置服务器默认只监听 localhost用php -S 0.0.0.0:8080启动。小程序后台是否配置了 request 合法域名。开发阶段可以在开发者工具里勾选「不校验合法域名」但体验版和正式版必须配置 HTTPS 域名。提示真机调试时把baseUrl改成局域网 IP 后记得在手机浏览器里先访问一下http://192.168.x.x:8080/api/menu.php能返回 JSON 再进小程序。这一步能排除 80% 的网络问题。4. 改对业务逻辑购物车、下单与支付回调的四个关键参数4.1 购物车为什么不能只存 dish_id很多源码的购物车实现是cart: [1, 2, 3]只存菜品 ID。看起来简洁但用户改了数量、加了备注、或者同一菜品点了两份不同规格就崩了。正确的结构应该是// 购物车项结构 { dishId: 1, name: 宫保鸡丁, price: 28.00, count: 2, remark: 不要花生 // 备注必须跟着购物车项走 }逻辑说明price存快照count存数量remark存备注。下单时直接把整个数组传给后端后端逐项写入order_item。参数上count要做边界校验——小于 1 或大于 99 都要拦截防止用户手滑输入 999。4.2 下单接口的幂等处理用户点「提交订单」时网络卡顿会连点两次生成两笔订单。这是点餐系统最典型的翻车场景。解决办法是在前端加按钮禁用后端加幂等 token// 前端提交前禁用按钮 let submitting false; async function submitOrder() { if (submitting) return; submitting true; wx.showLoading({ title: 提交中 }); try { await request(/api/order.php?actioncreate, POST, { items: cart }); wx.redirectTo({ url: /pages/order/order }); } finally { submitting false; wx.hideLoading(); } }后端侧我一般会在orders表加一个token字段前端每次进入订单确认页生成一个 UUID提交时带上。后端先查 token 是否存在存在就直接返回已有订单不重复插入。4.3 支付回调状态流转必须用数据库事务支付回调是整套系统最容易出数据不一致的地方。用户付了钱但订单状态没改或者改了两次。核心原则回调里先验签再用事务更新状态。?php // pay.php 回调处理简化示意 $conn-begin_transaction(); try { // 1. 验签略按支付平台文档实现 // 2. 查订单当前状态 $order $conn-query(SELECT status FROM orders WHERE id{$orderId} FOR UPDATE)-fetch_assoc(); if ($order[status] ! 0) { // 已处理过直接返回成功避免重复 $conn-commit(); exit(json_encode([code 0])); } // 3. 更新状态 $conn-query(UPDATE orders SET status1 WHERE id{$orderId}); $conn-commit(); echo json_encode([code 0]); } catch (Exception $e) { $conn-rollback(); echo json_encode([code 500, msg 处理失败]); }逻辑说明FOR UPDATE行锁防止并发回调同时读到status0。先判断状态再更新保证幂等。参数上orderId必须从回调数据里取不能从 URL 参数取否则会被篡改。4.4 菜品上下架的软删除菜品下架不要DELETE用status0。原因历史订单的order_item还关联着dish_id物理删除后订单详情查不到菜名。查询菜单时加WHERE status1后台管理页查全部。5. 避坑与排查点餐小程序最常见的五个翻车现场5.1 现象模拟器能登录真机获取手机号失败原因wx.getPhoneNumber需要按钮触发且必须使用open-typegetPhoneNumber不能在onLoad里直接调用。另外个人主体小程序没有获取手机号的权限。解决检查按钮写法确认小程序主体类型。如果只是测试可以在后端用openid模拟一个手机号别卡在权限上。5.2 现象订单金额和购物车显示不一致原因前端用price * count累加后端用dish.price实时查菜品调价后两边对不上。解决下单时前端传price快照后端以传入的price为准写入order_item但要做一次校验——传入价格不能低于当前菜品价格的 80%防止前端被篡改。5.3 现象支付成功后订单状态还是「待支付」原因支付回调地址配置错误或者回调里验签失败直接exit没有更新状态。解决先看后端日志有没有收到回调请求。如果收到了但没更新检查验签逻辑和数据库事务是否提交。我一般会在回调入口加一行file_put_contents(pay.log, json_encode($_POST), FILE_APPEND)先确认数据到了。5.4 现象菜品图片在真机上不显示原因图片路径用了本地相对路径或者后端返回的是http://localhost开头的地址。解决图片统一用 CDN 或后端域名拼接完整 URL。检查dish.image字段存的是相对路径还是完整地址前端image标签的src要能直接访问。5.5 现象多人同时下单订单号重复原因订单号用时间戳生成同一秒内多个请求拿到相同值。解决订单号用「时间戳 用户 ID 后四位 随机数」或者直接用数据库自增 ID 拼接日期。更稳妥的做法是用 Redis 的INCR生成序列号。6. 进阶把点餐系统改成多门店可用的三个改动点这套源码默认是单门店。如果你想接连锁店的活不用重写改三个地方就能撑住。第一加shop表和shop_id字段。category、dish、orders都加shop_id所有查询带上WHERE shop_id ?。前端首页加一个门店选择页选中后把shop_id存进wx.setStorageSync后续请求统一带上。第二菜品库存按门店隔离。如果同一菜品在不同门店库存独立需要加shop_dish关联表存shop_id、dish_id、stock。下单时用UPDATE shop_dish SET stock stock - ? WHERE shop_id ? AND dish_id ? AND stock ?影响行数为 0 就说明库存不足。第三订单号加门店前缀。比如A01-20240520-0001方便门店自己核对。后端生成订单号时从shop表读前缀。验证方法开两个浏览器窗口分别用不同shop_id调菜单接口确认返回的菜品列表不同再用同一个用户分别在两个门店下单确认订单表里shop_id正确。我一般会在改完后跑一遍这个检查比写单元测试快。最后说个血泪经验别在源码基础上直接改支付逻辑。支付回调涉及真金白银改之前先把原逻辑用注释包起来新逻辑单独写一个文件出问题能一键切回。这个习惯帮我省过至少两次线上事故。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。