简介:一套基于SpringBoot与uniapp(vue3)的扫码点餐系统完整源码,面向Java毕业设计、课程大作业及小程序开发学习者。后端采用Spring Security OAuth2实现安全认证,前端可发布为微信小程序或H5,覆盖多门店、外卖与自取等典型餐饮场景。资源包共2000个文件,以1323个Java源码文件为主,包含后端业务逻辑与接口;另有257个Vue页面、161个JavaScript脚本、41个CSS样式,以及SQL初始化脚本、YAML配置、Markdown文档等,可直接导入开发工具运行调试并二次开发。压缩包大小仅15.09MB,目录结构清晰,便于按模块阅读。已有403人学习浏览,适合掌握Java基础并希望快速搭建点餐系统的读者。包内附有HTML静态页面与说明文档,可辅助理解前后端交互流程;若运行中遇到问题,作者额外提供付费协助,可作参考。
1. 意向点餐(扫码点餐)系统.zip:别被名字带偏,这就是一套能直接上线的点餐源码
拿到“意向点餐(扫码点餐)系统.zip”这个压缩包,我先说个反直觉的结论:它看着像“意向点餐”,实际落地时所有人都在按“扫码点餐”来改造。意向点餐偏向“先看菜单、预订口味、预约时间”,而扫码点餐是“进店坐桌、扫码下单、后厨出单、吃完走人”。同一套系统通常两个模式都带,但你在生产环境里真正高频跑的一定是后者。这套 zip 里一般就是一个前后端项目包:后端管菜品、订单、支付,前端给顾客用的小程序或 H5 页面,外加一个给老板用的管理端。适合谁?适合餐饮店老板想低成本自建点餐系统、或小团队接单给餐饮客户做私有化部署。我不建议你拿它直接跑连锁大店,但单店到十家店以内,完全够用。
接下来我按照自己试过多次的路径拆解:先从解压和启动讲清楚项目结构,再讲数据库和接口怎么串,最后把上线最容易翻车的地方列出来。这套流程基本适用于市面上大部分“扫码点餐系统.zip”类源码包,你不用纠结文件名,重点看里面是哪种技术栈。
2. 跑通最小闭环:从 zip 解压到本地起服务的完整命令
拿到 zip 的第一步不是双击解压去看,而是先确认里面的项目形态。我经历过的扫码点餐源码大体有两种:一种是单体应用,后端和前端打包在一起,改改配置就能跑;另一种是前后端分离,后面有个server目录(Spring Boot、Node、Go 都有),前面有个miniapp或h5目录。千万别上来就去编译所有模块,你会被各种版本依赖折磨到怀疑人生。
2.1 解压与目录识别:先别急着双击,看清楚是前后端分离还是单体
我先用命令行解压,避免 Windows 自带压缩功能在中文目录名上出编码问题。常见做法是右键用 WinRAR 或 7-Zip 解压,但我更推荐在项目目录下直接操作:
cd /var/www unzip 意向点餐\(扫码点餐\)系统.zip -d order_system ls order_system解压后先看根目录。如果是前后端分离,你会看到pom.xml、package.json或go.mod其中一个位于内层目录,同时还有一个miniapp、uniapp、wxapp之类的目录。如果只有一层目录且里面有src/main/java和src/main/resources,那就是单体应用。还有一个快速判断方法:找有没有application.yml或application.properties,有它基本是 Spring Boot;找app.js且里面有wx.login,那前端就是微信小程序。这个识别过程决定了你后面所有命令。我见过不少人拿单体项目当分离项目部署,结果前端目录一直找不到接口地址,白折腾半天。
2.2 后端服务启动:以常见 Spring Boot / Node 为例的最小命令
扫码点餐系统里,后端承担了菜品管理、桌台状态、订单生成和支付回调,业务核心都在它身上。我以最常见的 Spring Boot 后端举例,先在根目录找到带<artifactId>的pom.xml,然后执行:
mvn clean package -DskipTests java -jar target/order-0.0.1-SNAPSHOT.jar --server.port=8080这条命令的逻辑是:mvn clean package先清理旧产物并打包成可执行 jar,-DskipTests跳过单元测试(源码包里的测试经常因为缺环境变量失败,没必要卡在这);java -jar启动服务,--server.port=8080强制指定端口,避免你本机 8080 被别的进程占用时还要去改配置文件。如果你是 Node 后端,常见做法是:
npm install npm run dev启动成功后,后端服务会把菜品数据表和初始管理员账号加载到数据库里。这里我要提醒一个关键点:很多源码包默认配置的是远程数据库,比如jdbc:mysql://localhost:3306/order,但密码是root/123456这种占位值。我一般会先不动它,等启动报错了再改。如果控制台打印出Tomcat started on port 8080或Server listening on port 3000,就说明服务起来了。此时先用浏览器访问http://localhost:8080/swagger-ui.html或/doc.html,如果有接口文档页面,说明后端正常;没有就访问/api/health之类的健康检查接口,或者直接看控制台日志里有没有register success。
2.3 前端小程序/ H5 的联调:把接口域名改成你的局域网 IP
后端起来了,前端不会自动连上后端。扫码点餐的小程序代码里,接口地址一般写在config.js、request.js或utils/api.js里。你搜http://或baseURL就能找到。默认值大概率是https://api.example.com或http://localhost:8080。真机调试时,手机和电脑必须在同一局域网,然后把地址改成你电脑的局域网 IP。这个改动直接影响顾客能否扫码下单,所以我把常见做法写清楚——修改微信开发者工具里的project.config.json的urlCheck为false,同时在代码里把接口改为:
// utils/request.js const BASE_URL = 'http://192.168.1.100:8080'; // 改成你电脑的局域网IP这样做的原因很简单:微信开发者工具默认不允许通过 IP 请求接口,但本地调试必须绕过这个限制。改完BASE_URL后,重新编译小程序,扫码进入店铺页面,能看到菜品列表就说明前端和后端已经打通。如果看不到菜品,先在开发者工具的 Network 面板看请求是否发出、是否返回 5xx,再回后端日志看有没有报错,不要直接去改前端逻辑。
3. 核心业务表与接口设计:餐桌码、购物车、订单状态机怎么串起来
扫码点餐和传统收银系统的本质区别在于“桌码”这个入口。顾客扫的不是一个菜单页面,而是带着桌号参数的请求。这个参数贯穿了整个下单流程,所以数据库和接口设计必须围绕它展开。很多 zip 源码包里的表结构看着没几行,但真正决定了你能不能上线的是这几张表和它们之间的关联。
3.1 四张核心表:菜品、桌台、订单、订单项
点餐系统的表可能不止四张,但这四张是骨架:dish(菜品)、table(桌台)、order(订单主表)、order_item(订单明细)。我见过一些源码把菜品和桌台混在shop_config里,扩展性很差,后面加分类、加规格都得改表。靠谱的表结构应该各自独立:
CREATE TABLE `dish` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `name` varchar(64) NOT NULL, `price` decimal(10,2) NOT NULL, `category_id` bigint NOT NULL, `image` varchar(255) DEFAULT '', `status` tinyint DEFAULT 1 ); CREATE TABLE `table_info` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `table_no` varchar(16) NOT NULL, `qr_code` varchar(255) DEFAULT '', -- 桌码内容 `seats` int DEFAULT 4, `status` tinyint DEFAULT 0 -- 0空闲 1已入座 ); CREATE TABLE `order` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `table_id` bigint NOT NULL, `total_amount` decimal(10,2) NOT NULL, `status` tinyint DEFAULT 0, -- 0待支付 1已支付 2制作中 3已完成 `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `pay_time` datetime DEFAULT NULL ); CREATE TABLE `order_item` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `order_id` bigint NOT NULL, `dish_id` bigint NOT NULL, `quantity` int NOT NULL, `price` decimal(10,2) NOT NULL );order_item里的price字段我特意强调:它存的必须是菜品下单那一刻的价格,而不是实时去dish表联查。因为菜品可以改价,如果用户先加到购物车、五分钟后再支付,你按新价格算,顾客会觉得乱收费。这就是快照价的含义,做餐饮系统的都知道,但很多二次开发的源码没做。订单主表里status是核心,后面推进会讲状态机,这里先记住:状态只能往前推进,不能倒退,退款单独走退款单。
3.2 扫码进去的“桌台参数”是怎么传递的:scene 值解析
微信扫码点餐的小程序码通常用scene参数带桌号。生成的码内容是table=12&shop=1之类的字符串,微信扫后会进到小程序的onLoad事件的options.scene里,但它是 URL 编码过的。我见过新手直接在onLoad里打印options.table,结果永远是undefined。正确做法是先解码,再按分隔符拆:
// miniapp/pages/index/index.js Page({ onLoad(options) { // 微信会把 scene 内容 URL 编码,比如 table%3D12%26shop%3D1 const scene = decodeURIComponent(options.scene || ''); const params = {}; scene.split('&').forEach(item => { const [key, value] = item.split('='); params[key] = value; }); this.setData({ tableId: params.table, shopId: params.shop }); this.loadDishList(params.shop); } })这段代码的逻辑是:先拿到scene,解码后按&拆成键值对,取table和shop参数。然后请求后端接口GET /api/menu?shopId=xxx,后端根据 shopId 返回菜品。这里有个容易踩的坑:scene参数长度有限制,别往里面塞太多东西,只放shopId和tableId就够了。用户信息、备注、口味全放到下单请求里传,不要塞进二维码。
3.3 订单状态机:待支付→已支付→制作中→已完成,退款和催菜怎么处理
订单状态不能只在代码里写个if status == 1完事。扫码点餐会遇到几个高频场景:用户扫码后下单但不支付、支付后后厨出单、超时未支付系统自动取消、服务员催菜。我一般会用常量枚举把状态钉死,避免前辈们在代码里写魔法数字:
// 订单状态常量 public static final int ORDER_WAIT_PAY = 0; public static final int ORDER_PAID = 1; public static final int ORDER_COOKING = 2; public static final int ORDER_FINISHED = 3; public static final int ORDER_CANCELED = 4; public static final int ORDER_REFUNDING = 5;状态变迁的核心规则是:只有ORDER_WAIT_PAY可以变到ORDER_PAID,这是支付回调里必须校验的。支付回调出现重复请求时,如果订单已经ORDER_PAID,就直接返回成功,幂等处理。而催菜功能只是给后厨推送一条消息,不改订单状态。退款的正确姿势是单独建一个refund表,通过退款单关联原订单,而不是把订单改回“待支付”。很多源码包图省事,退款时把订单status改成 0,结果厨师看到待支付订单还在做菜,那就乱套了。状态机不用画得多复杂,保证“单向流动”和“支付回调幂等”就成功了大半。
4. 避坑:从 zip 解压到上线,最容易翻车的 5 个问题
我翻了不止一套扫码点餐系统的 zip 包,自己也二开过,这里总结五个高频翻车点,每条都按“现象→原因→解决”来写,你在本地跑通后、正式部署前,最好逐条排查一遍。
4.1 解压后报“找不到主类”或“端口被占用”
现象:java -jar启动后立刻报Exception: Could not find or load main class,或者提示Caused by: java.net.BindException: Address already in use。原因分两种:一种是你打的 jar 是别人二次打包的,缺少启动类;另一种是你本机 8080 端口已被其他服务占着。解决:先看pom.xml里的<build><plugins>是否正确配置了spring-boot-maven-plugin,如果缺了,执行mvn clean package后 jar 里没有 Main-Class,自然找不到主类。补上这个插件后重新打包。端口占用则先用netstat -ano | findstr 8080查 PID,然后taskkill /F /PID 那个PID。我习惯直接把端口改成 8081,少跟别的服务打架。
4.2 微信小程序扫码后打不开页面:域名白名单与 IP 限制
现象:真机扫码进入后页面白屏,开发者工具体验良好但预览版就是请求失败。原因:小程序在真机上不允许访问http://192.168.x.x这种局域网 IP,也不允许请求没有配置在微信公众平台域名白名单里的域名。很多源码包的默认接口域名是http://localhost,你在开发者工具里禁用urlCheck还能跑,真机就彻底断连。解决:如果你只做本地演示,可以在公众平台“开发设置—服务器域名”里把request 合法域名加上,但注意必须是 HTTPS,且 ICP 备案。本地验证的话,最稳妥是开一个反向代理,把生产域名指向本地服务,或者用内网穿透工具临时顶一下。我不建议改小程序的checkSite配置来绕过,那种修改只能撑一阵子。
4.3 订单状态乱掉:并发支付回调处理不当
现象:用户重复点击支付按钮,或者支付平台回调重试了三次,数据库里出现两条支付成功的订单,或者订单状态从“已支付”被改回“待支付”。原因:后端处理支付回调时没有做幂等判断。扫码点餐的支付回调一般是在notify接口里校验签名后修改订单状态,但如果没加“当前订单状态是否已经为已支付”的判断,就会重复修改。解决:在更新 SQL 里加上WHERE id = ? AND status = 0,这样即使回调并发,也只有一条能更新成功:
UPDATE `order` SET status = 1, pay_time = NOW() WHERE id = #{orderId} AND status = 0;这招叫乐观状态判断,比先查询再更新省事且更稳。支付回调处理完必须给微信返回SUCCESS字符串,否则微信会反复回调,导致日志里刷屏错误。我踩过的最惨一次,是回调里抛异常没接收,微信回调了七次,订单表被 UPDATE 了七次,虽然状态没变,但支付时间被更新成了最后一次时间,对账时怎么都对不上。
4.4 图片加载不出来:菜品图片路径写死绝对路径
现象:菜品管理里能添加图片,但顾客端小程序显示空白。原因:很多源码加图片是直接存/img/dish/xxx.jpg这种相对路径,然后前端又拼了个http://localhost:8080,换环境后路径就断了;或者上传图片时把文件存到了项目目录下,但你启动 jar 后图片不会自动打包进 jar。解决:把图片改为数据库存完整 URL,或者统一走上传接口返回相对路径,由前端加一个可配置的 CDN 前缀。我一个比较省事的习惯是直接在配置里加upload.path,图片上传到固定目录,再用 Nginx 把/img/映射到那个目录。这样换 IP、换域名都只改 Nginx,不用动数据库。代码里不要写死http://192.168.1.100:8080/upload/xx.jpg,迟早要被坑。
4.5 数据库编码乱码:建表语句没指定 utf8mb4
现象:菜品名称是“酸菜鱼”,小程序里显示“é…¸…”。原因:数据库建表时默认latin1或utf8,存 emoji 和生僻字会丢失;更常见的是 JDBC 连接串没写characterEncoding=utf8,导致存入时已经乱码。解决:建库时强制指定:
CREATE DATABASE order_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;同时保证 JDBC 连接 URL 带useUnicode=true&characterEncoding=utf8。另外,源码包里如果有.sql文件,用 Navicat 导入时要注意,导入前先看一下文件的编码,如果文件本身是 GBK 的,你直接用 utf8 导入,一样乱。我一般会把.sql文件先用 VSCode 打开确认右下角位置显示UTF-8,再执行导入。
5. 上线前必须做的 3 件事:从“能跑”变成“真能用”
本地跑通只是第一步,扫码点餐系统上线前有三件事没做,开业当天一定会被老板拉黑。这三件事不涉及代码功能的新增,而是把“能用”和“能扛营业”之间的沟填平。
5.1 用商户号替换测试支付参数
源码包里的支付配置大多是测试商户号,密钥不是一个能出钱的账号。上线前必须去微信支付商户平台创建应用,然后把appid、mch_id、api_v3_key填到后端的配置里。这个环节最容易出错的是签名算法,尤其是微信支付 API v3 的证书序列号和私钥。常见做法是后端用一个配置类集中管理:
wechat: pay: app-id: wx1234567890 mch-id: 1500000000 api-v3-key: 你的32位密钥 private-key: /path/to/apiclient_key.pem替换完支付参数,一定要用真实金额 0.01 元测一笔完整流程:扫码→点餐→下单→支付→回调→打印小票。我见过有人替换了商户号但回调地址没改,还是localhost/notify,结果支付成功后订单永远在“待支付”。检查回调地址时要确认它是外网可访问的 HTTPS 地址,且路径和后端接口里的notifyUrl完全一致。
5.2 静态资源与接口分离,把图片放到 CDN 或对象存储
本地图片存储发展到每张菜品图几百 KB,一天几百单没问题,但一旦你连着 Nginx 跑,磁盘会被图片占满,而且小程序访问慢。上线前我建议把dish.image字段的值从/upload/xx.jpg改成https://cdn.你的域名.com/dish/xx.jpg。推送方式是写一个异步任务,把本地/upload目录下的文件同步到对象存储,同步完把数据库里的路径批量替换。这一块可以从简,但方向要对:接口服务不存文件,文件全部走对象存储或 CDN。这样以后部署多台服务器,就不用担心用户传到这台机的图片在另一台机上找不到。
5.3 加一个简单的老板端看板:实时订单与营业统计 SQL
大多扫码点餐源码自带的“商家端”只有菜品管理和订单列表,缺少一个一屏看完当日营业的看板。看板不用写得很复杂,核心就三行 SQL:
-- 今日营业额(已支付订单) SELECT SUM(total_amount) AS today_sales FROM `order` WHERE status >= 1 AND DATE(pay_time) = CURDATE(); -- 今日订单数 SELECT COUNT(*) AS order_count FROM `order` WHERE status >= 1 AND DATE(pay_time) = CURDATE(); -- 热销菜品 Top5 SELECT oi.dish_id, d.name, SUM(oi.quantity) AS qty FROM order_item oi JOIN dish d ON oi.dish_id = d.id JOIN `order` o ON oi.order_id = o.id WHERE o.status >= 1 AND DATE(o.pay_time) = CURDATE() GROUP BY oi.dish_id, d.name ORDER BY qty DESC LIMIT 5;拿到这三组数据后,前端就是套一个定时轮询,每分钟刷一次。为什么不用 WebSocket?因为扫码点餐订单量远没到要实时推送的程度,1 分钟一次的轮询足够老板看出今天卖了多少、哪个菜卖得快。营业额 SQL 里要加status >= 1,把”待支付“排除掉,不然顾客加了购物车不支付,营业额虚高。
6. 进阶:把扫码点餐从“单店版”改造成“多店版”的一个关键改动
你如果只给自己家的店用,前面五章已经够了。但很多从业者接的是小餐企的项目,几家店共用一套系统,这时基础的单店版源码就不够用了。好在改造方向很集中,就是给核心表加一个租户字段,然后在下单和查询链路里带上它。
6.1 数据隔离:加一个 store_id 字段
思路是给dish、table_info、order、order_item都加store_id,然后在查询语句里强制带上。比如:
ALTER TABLE dish ADD COLUMN store_id bigint NOT NULL DEFAULT 1;没有加上store_id的条件查询非常危险,后厨看到的是所有店的订单,服务员扫 A 店的码却看到了 B 店的菜单。我一般会在后端封装一个StoreContext,从请求头或 token 里拿到当前门店 ID,然后所有 SQL 都带上过滤条件,避免漏掉。小程序端不用大改,请求时带上storeId参数即可。
6.2 动态桌台码生成:不再手动建码
单店版生成桌码可以手动创建,但多店版每家店几十张桌,手动生成二维码要死。常见做法是用后端接口动态生成,返回一个二维码图片地址,内容为:
https://你的域名/h5/index.html?storeId=10&tableId=23然后打印出来贴到桌上。生成用的库可以选qrcode库,Java 用Google ZXing,前端用qrcode.js都行。我这里按后的逻辑是——storeId从管理端会话里取,tableId由前端传给后端,后端生成图片后返回给管理端。这一步改造后,新开一家店只需要在前端录入桌号数量,二维码批量生成,配一台打印机就能开工。
6.3 验证方法:用一个压测脚本模拟并发下单
改动完后,不要光在开发环境点两下就说没问题。我习惯写个简单的压测脚本模拟并发请求:
ab -n 200 -c 20 -p order.json -T application/json \ "http://localhost:8080/api/order/create"ab是 Apache 自带的压测工具,-n 200表示总共 200 个请求,-c 20表示同时 20 个并发,-p order.json里面放一份下单请求体。跑完后看两个指标:一是请求是否全部 2xx,二是数据库中order表没有重复支付状态的脏数据。压测脚本只能暴露接口和事务问题,真正的完整链路还得用微信扫码走一遍。
压测时如果发现订单表里有部分订单status=0,说明下单和支付回调存在竞态,回到第四章的幂等方案解决。多店版的并发量并不大,一台 2 核 4G 的服务器完全扛得住,瓶颈通常在数据库连接池,别一开始就上集群。我自己的习惯是每次改完数据结构,先跑一遍老主流程(扫码→下单→支付→出单),再跑一遍压测,确认没回归,这比写一堆单元测试管用。扫码点餐这个项目,坑不算深,但每个坑都藏在边界里——支付幂等、编码、路径、门店隔离,你踩过一遍就懂了。希望这些真实踩坑记录,能帮你少走几趟夜路。
本文还有配套的精品资源,点击获取