简介:本资源为基于Java与Vue的yshop意象桌面扫码点餐系统设计源码,面向具备一定SpringBoot与前端基础、希望研究多门店点餐业务实现的学习者与开发者。项目支持在线点餐的外卖与自取两种小程序模式,并兼容多门店场景,采用SpringBoot与Spring Security构建后端,前端以Vue与TypeScript组织页面组件,整体结构清晰、注释详尽,适合作为课程设计、毕业设计或技术选型的参考案例。压缩包共2005个文件,约50.28MB,其中Java源码1401个、Vue组件257个、JavaScript文件161个,另有XML、HTML、CSS、SQL及YAML等配置与样式文件,覆盖后端逻辑、前端界面、数据库脚本与部署配置等层面。目前已有440人学习下载。读者可从中获取完整的点餐系统源码与目录组织方式,理解多门店与扫码点餐的业务拆分思路,并借鉴Spring Security权限控制、前后端接口协作及TypeScript组件封装等实践,便于二次开发与排错参考。
1. yshop 意象桌面扫码点餐系统到底解决什么问题
扫码点餐这件事,表面看是「顾客扫个码、点几个菜、下单」,真落到门店里,麻烦全在后台:桌台状态要实时同步、菜品库存要扣减、订单要分给后厨、加菜和退菜要能追溯、多门店还要隔离数据。yshop 意象桌面扫码点餐系统就是冲着这套链路来的,它用 Java 做后端、Vue 做前端,把「扫码—点餐—下单—后厨—结算」串成一条可跑通的业务线。源码形态意味着你能拿到完整工程,改菜单结构、改桌台逻辑、接自己的支付和打印,而不是被 SaaS 按年收费锁死。适合谁?想给中小餐饮做定制点餐的 Java 工程师、要拿一套完整前后端分离项目练手的 Vue 开发者,以及手里有门店资源、想自己搭一套系统的技术型创业者。它不解决「零代码开店」,它解决的是「我要一套能改、能部署、能对接硬件的点餐底座」。
2. 技术选型与工程结构:为什么是 Java + Vue 这套组合
2.1 后端为什么选 Spring Boot + MyBatis 而不是别的
扫码点餐的核心压力不在页面,在并发下单和库存扣减。顾客高峰期十几桌同时提交,后端要保证订单不重、库存不超卖。Java 生态里,Spring Boot + MyBatis 是最稳的落地组合:Spring Boot 管事务和依赖注入,MyBatis 让你把扣库存的 SQL 写清楚,而不是被 ORM 藏起来。常见做法是把下单逻辑放在 Service 层,用@Transactional包住「生成订单 + 扣库存 + 写桌台状态」三步,任何一步失败整体回滚。
@Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private DishMapper dishMapper; // 下单核心:事务包住三步,避免超卖和脏桌台 @Override @Transactional(rollbackFor = Exception.class) public Long createOrder(OrderDTO dto) { // 1. 扣库存,带库存充足条件,返回影响行数 int affected = dishMapper.reduceStock(dto.getDishId(), dto.getCount()); if (affected == 0) { throw new BizException("库存不足,下单失败"); } // 2. 写订单主表 Order order = new Order(); order.setTableId(dto.getTableId()); order.setStatus(OrderStatus.WAIT_CONFIRM.getCode()); orderMapper.insert(order); // 3. 更新桌台为占用状态 orderMapper.updateTableStatus(dto.getTableId(), TableStatus.OCCUPIED.getCode()); return order.getId(); } }逻辑说明:reduceStock的 SQL 必须写成update dish set stock = stock - #{count} where id = #{id} and stock >= #{count},靠数据库行锁保证并发安全,而不是先查再减。参数上rollbackFor = Exception.class是关键,默认只回滚运行时异常,业务里抛的受检异常不回滚,这是新手最容易翻车的地方。桌台状态用枚举而不是魔法数字,后面加「预订」「清洁中」状态时不用改一堆判断。
2.2 前端为什么用 Vue 而不是服务端渲染
点餐页面的交互密度很高:切换分类、加减数量、购物车实时算价、规格弹窗。Vue 的响应式正好吃这类场景,数据一变视图自动更新,不用手动操作 DOM。yshop 这类项目一般用 Vue2 + Element UI 或 Vue3 + Element Plus,路由用vue-router管页面跳转,状态用 Vuex/Pinia 管购物车。扫码进来带的是桌台号参数,常见做法是路由上挂?tableId=8,进页面先解析参数再拉菜单。
// 扫码进入点餐页,从路由参数拿桌台号 export default { data() { return { tableId: null, cart: [] }; }, created() { // 路由参数是字符串,转成数字再存 this.tableId = Number(this.$route.query.tableId); if (!this.tableId) { this.$message.error('桌台信息缺失,请重新扫码'); return; } this.loadMenu(); }, methods: { async loadMenu() { // 按桌台所属门店拉菜单,避免跨店串菜 const res = await this.$http.get('/api/menu/list', { params: { tableId: this.tableId } }); this.menuList = res.data; } } };逻辑说明:created里做参数校验比mounted早,能在渲染前拦住无效扫码。tableId一定要转数字,路由参数默认是字符串,后端如果按数字匹配会查不到。菜单接口带上tableId而不是只带门店 ID,是为了让后端能根据桌台反查门店,减少前端传参被篡改的风险。
2.3 前后端分离后怎么联调和打包
开发阶段前端跑npm run serve起在 8080,后端 Spring Boot 起在 9090,跨域是第一个拦路虎。常见做法是在vue.config.js里配代理,把/api转发到后端,前端代码里只写相对路径。
// vue.config.js 开发代理配置 module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:9090', // 后端地址 changeOrigin: true, pathRewrite: { '^/api': '' } // 去掉前缀再转发 } } } };逻辑说明:changeOrigin: true让后端收到的 Host 是目标地址,避免某些鉴权中间件拦截。pathRewrite要不要配取决于后端接口有没有/api前缀,两边必须对齐,否则 404。生产环境不靠代理,而是npm run build出静态文件,丢进 Nginx 或 Spring Boot 的static目录,Nginx 里再配一次/api转发到后端端口。这一步没配好,上线后就是「本地好好的,服务器全 404」。
3. 从零把 yshop 点餐系统跑起来的最小步骤
3.1 环境准备与依赖版本对齐
跑不起来十有八九是版本不对。Java 侧建议 JDK 8 或 11,Spring Boot 2.x 对这两个支持最稳;MySQL 用 5.7 或 8.0,注意 8.0 的驱动类名和时区参数变了;Node 用 14 或 16,太新的 Node 17+ 会让老版本 node-sass 编译失败。前端依赖装不上时,先看package.json里的node-sass或sass版本,node-sass对 Node 版本极其敏感,能换sass(Dart Sass)就换。
# 后端:导入数据库后改配置再启动 mysql -uroot -p yshop < sql/yshop.sql # 改 application.yml 里的数据库连接 # spring.datasource.url: jdbc:mysql://localhost:3306/yshop?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai mvn clean package -DskipTests java -jar target/yshop-admin.jar # 前端:装依赖并起开发服务 cd yshop-vue npm install --registry=https://registry.npmmirror.com npm run serve逻辑说明:serverTimezone不配,MySQL 8.0 下时间字段会差 8 小时,订单时间全乱。mvn package -DskipTests跳过测试加快打包,但首次跑建议先跑一次测试看环境是否完整。前端用国内镜像源能显著减少npm install卡住的情况,这不是玄学,是网络现实。
3.2 数据库表结构和关键字段
点餐系统的表不多,但字段设计决定后面好不好扩展。核心是桌台表、菜品表、订单表、订单明细表四张。桌台表要有store_id做多门店隔离,菜品表要有stock和status(上架/下架),订单表要有table_id和status,明细表要存下单时的菜品快照价格。
| 表名 | 关键字段 | 作用 | 注意点 |
|---|---|---|---|
| shop_table | id, store_id, table_no, status | 桌台管理 | status 用枚举,别用 0/1 硬编码 |
| dish | id, store_id, name, price, stock, status | 菜品 | price 用 decimal(10,2),别用 float |
| orders | id, table_id, store_id, total, status, create_time | 订单主表 | create_time 建索引,报表要查 |
| order_item | id, order_id, dish_id, dish_name, price, count | 订单明细 | 存 dish_name 和 price 快照 |
逻辑说明:明细表存菜品名和价格快照,是因为菜品改价或下架后,历史订单必须还原当时的价格,直接关联 dish 表查会出错。price用decimal不用float,浮点数算钱会出现0.1 + 0.2 = 0.30000000000000004这种问题,对账时是灾难。store_id在每个查询里都要带上,这是多门店数据隔离的底线,漏一个就是跨店串数据。
3.3 扫码到下单的完整链路走一遍
顾客扫码后,浏览器打开的是带tableId的页面,前端拉菜单、加购物车、提交订单,后端校验桌台状态、扣库存、生成订单、推给后厨。这条链路里每一步都要能单独测。常见做法是先用 Postman 或 curl 直接打后端接口,确认后端通了再接前端。
# 直接测下单接口,确认后端逻辑 curl -X POST http://localhost:9090/order/create \ -H "Content-Type: application/json" \ -d '{"tableId":8,"dishId":101,"count":2}'逻辑说明:先测接口再联前端,能把「前端传参错」和「后端逻辑错」分开定位。返回体里要有明确的错误码,库存不足返回业务码而不是 500,前端才能弹对应提示。桌台状态要在下单前查一次,已占用的桌台不允许重复开单,除非是加菜场景,加菜走的是往已有订单追加明细,不是新建订单。
4. 避坑与排查:扫码点餐系统上线前必须过的坎
4.1 库存扣成负数
现象:并发下单后,某个菜品库存显示 -3。原因:扣库存写成了「先 select 查库存,再 update 减」,两个请求同时查到库存 1,都判断够,都去减。解决:把判断和扣减合并成一条 SQL,update dish set stock = stock - #{count} where id = #{id} and stock >= #{count},靠affected行数判断是否成功,返回 0 就是库存不足。这是血泪经验,压测时才会暴露,单机手动点根本测不出来。
4.2 桌台状态不同步
现象:顾客下单成功,但收银台看桌台还是「空闲」,又给这桌开了一单。原因:下单和改桌台状态不在同一个事务里,或者前端下单成功后没刷新桌台列表。解决:把改桌台状态放进下单事务,保证原子性;前端下单成功后主动调一次桌台状态接口,或者用轮询/长连接刷新。别指望顾客手动刷新页面。
4.3 时间差 8 小时
现象:订单创建时间比实际时间早或晚 8 小时,报表按天统计全错。原因:MySQL 8.0 的 JDBC 连接没配serverTimezone,或者数据库时区和 JVM 时区不一致。解决:连接串加serverTimezone=Asia/Shanghai,同时确认服务器系统时区是CST。两个地方都要对,只改一个还会错。
4.4 前端打包后接口 404
现象:npm run serve一切正常,npm run build部署后所有接口 404。原因:开发环境的代理只在 devServer 生效,生产环境没有代理,前端请求的还是相对路径/api,但 Nginx 没配转发。解决:Nginx 里加location /api/ { proxy_pass http://后端IP:9090/; },注意proxy_pass结尾的斜杠,带斜杠会去掉/api前缀,不带会保留,配错就是 404 或 502。
4.5 多门店数据串了
现象:A 店的顾客看到了 B 店的菜。原因:查询菜单或订单时漏了store_id条件,或者store_id是从前端传的、被篡改了。解决:store_id必须由后端根据tableId反查得出,不能信前端传的值。所有涉及门店数据的查询,SQL 里强制带store_id,最好在 MyBatis 拦截器里统一加,避免手写漏掉。
5. 进阶:把点餐系统接上打印和后厨的实操技巧
跑通下单只是开始,真上线还得接小票打印机和后厨分单。小票打印机一般走 ESC/POS 指令,Java 侧用 socket 直连打印机 IP 的 9100 端口,把订单格式化成指令流发过去。这一步的坑在于编码:中文要用 GBK,用 UTF-8 打出来是乱码。下面是一个最小打印方法。
public void printTicket(Order order, String printerIp) throws IOException { try (Socket socket = new Socket(printerIp, 9100); OutputStream out = socket.getOutputStream()) { // 初始化打印机 out.write(new byte[]{0x1B, 0x40}); // 中文必须用 GBK,UTF-8 会乱码 String content = buildTicketText(order); out.write(content.getBytes("GBK")); // 切纸 out.write(new byte[]{0x1D, 0x56, 0x42, 0x00}); out.flush(); } }逻辑说明:0x1B 0x40是 ESC/POS 的初始化指令,每次打印前发一次,清掉上次残留的格式。0x1D 0x56 0x42 0x00是切纸指令,不带这个顾客拿到的是连着的长纸。buildTicketText里要控制每行宽度,58mm 打印机一行约 32 个英文字符或 16 个汉字,超了会换行错位。后厨分单的逻辑是:订单里带「热菜」「凉菜」分类,按分类把明细拆成多张票,分别发到对应打印机。这个映射关系建议做成配置表,别写死在代码里,换门店就要改代码是维护噩梦。
验证打印是否正常,别等真机,先用nc -l 9100在本地监听端口,把收到的字节 dump 出来看指令对不对,确认格式没问题再连真打印机。我自己的习惯是每接一个新型号打印机,先打一张测试页,把中文、数字、二维码、切纸各测一遍,确认无误再进业务流程。这套系统值不值得做,取决于你有没有改的需求——如果只是标准点餐,SaaS 更省事;如果要对接自有硬件、改业务流程、做多门店定制,拿源码自己搭是更划算的路。希望帮到你。
本文还有配套的精品资源,点击获取