
简介一套基于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.port8080这条命令的逻辑是mvn clean package先清理旧产物并打包成可执行 jar-DskipTests跳过单元测试源码包里的测试经常因为缺环境变量失败没必要卡在这java -jar启动服务--server.port8080强制指定端口避免你本机 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参数带桌号。生成的码内容是table12shop1之类的字符串微信扫后会进到小程序的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?shopIdxxx后端根据 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里的buildplugins是否正确配置了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 连接串没写characterEncodingutf8导致存入时已经乱码。解决建库时强制指定CREATE DATABASE order_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;同时保证 JDBC 连接 URL 带useUnicodetruecharacterEncodingutf8。另外源码包里如果有.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?storeId10tableId23然后打印出来贴到桌上。生成用的库可以选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/createab是 Apache 自带的压测工具-n 200表示总共 200 个请求-c 20表示同时 20 个并发-p order.json里面放一份下单请求体。跑完后看两个指标一是请求是否全部 2xx二是数据库中order表没有重复支付状态的脏数据。压测脚本只能暴露接口和事务问题真正的完整链路还得用微信扫码走一遍。压测时如果发现订单表里有部分订单status0说明下单和支付回调存在竞态回到第四章的幂等方案解决。多店版的并发量并不大一台 2 核 4G 的服务器完全扛得住瓶颈通常在数据库连接池别一开始就上集群。我自己的习惯是每次改完数据结构先跑一遍老主流程扫码→下单→支付→出单再跑一遍压测确认没回归这比写一堆单元测试管用。扫码点餐这个项目坑不算深但每个坑都藏在边界里——支付幂等、编码、路径、门店隔离你踩过一遍就懂了。希望这些真实踩坑记录能帮你少走几趟夜路。本文还有配套的精品资源点击获取