ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

无线点餐系统毕业设计:三层架构与订单状态机实战

无线点餐系统毕业设计:三层架构与订单状态机实战

简介:这份资源是面向计算机相关专业毕业生的毕业设计项目——无线点餐系统,适合正在准备毕设、需要完整可运行案例的学生参考。项目以Android客户端为核心,结合后台服务端实现餐桌管理、菜单浏览、下单与支付等餐饮场景功能,帮助读者理解移动端与服务器端的数据交互流程。压缩包共365个文件,约9.99MB,包含75个java源文件、109个class编译文件、30个xml布局与配置、18个jar依赖包,以及61张jpg与18张gif界面素材,另有11个jsp页面、6份doc文档和apk安装包,覆盖源码、资源、文档与可执行程序,便于对照学习。目前已有239人学习下载。读者可从中获得一套结构完整的毕设参考方案,包括客户端界面与业务逻辑、服务端Servlet处理、数据库文件及项目说明文档,适合用来梳理系统架构、理解点餐业务流程,并在此基础上进行功能扩展或二次开发。

1. 无线点餐系统:一份能直接跑起来的毕业设计工程

每年到了毕业设计季,后台总有人问同一个问题:有没有那种不是"PPT 项目"、下载下来能真跑起来的完整工程?这份无线点餐系统就是冲着这个需求来的。它把顾客扫码点餐、后厨接单、前台结账这条最典型的餐饮链路做成了一个可运行的完整项目,技术栈覆盖前端页面、后端接口和数据库三层,适合拿来做毕业设计的主体工程,也适合想练手一个"有真实业务闭环"的开发者。你拿到手不是一堆散落的代码片段,而是一个能启动、能下单、能改菜单的完整系统。下面我按"它是什么、怎么跑、怎么改"的顺序,把这份工程拆开讲清楚。

2. 系统架构与目录结构:三层结构怎么落地

2.1 整体技术选型与分层思路

这类点餐系统的业务逻辑其实不复杂,核心就是"顾客端下单 → 服务端落库 → 后厨端拉单 → 前台结算"这条链路。真正决定工程好不好用的,是分层是否清晰。这份工程采用的是典型的前后端分离结构:前端负责页面渲染和交互,后端提供 REST 接口,数据库存菜单、订单、桌台三类核心数据。

常见做法是前端用 Vue 或原生 HTML+JS 搭页面,后端用 Spring Boot 或 Node.js 提供接口,数据库用 MySQL。这份工程按毕业设计的常见配置,后端接口层、业务逻辑层、数据访问层是分开的,这样你改菜单逻辑时不用动接口定义,改接口时不用碰 SQL。分层带来的直接好处是:答辩时老师问"你这个订单状态怎么流转的",你能指着 Service 层说清楚,而不是在一坨代码里翻。

从技术栈角度看,需要你本地具备的能力是:能跑 Java 或 Node 运行环境、能起一个 MySQL 实例、能用浏览器访问前端页面。这三样齐了,工程就能转起来。下面这张表是我整理的核心模块与职责对应关系,方便你先建立全局印象再往下看代码。

模块职责典型文件位置
顾客点餐端展示菜单、加购物车、提交订单frontend/customer
后厨接单端拉取待处理订单、更新制作状态frontend/kitchen
前台结算端查看桌台、结账、打印小票frontend/cashier
接口服务层提供菜单/订单/桌台 REST 接口backend/controller
业务逻辑层订单状态机、库存扣减、金额计算backend/service
数据访问层菜单、订单、桌台表 CRUDbackend/mapper
数据库脚本建表与初始菜单数据sql/init.sql

2.2 目录结构逐层说明

拿到源码包,第一件事不是急着跑,而是先看清目录。这份工程的目录大致是这样组织的:

wireless-ordering/ ├── frontend/ # 前端工程 │ ├── customer/ # 顾客扫码点餐页面 │ ├── kitchen/ # 后厨订单看板 │ ├── cashier/ # 前台结算页面 │ └── common/ # 公共样式与请求封装 ├── backend/ # 后端服务 │ ├── controller/ # 接口入口,处理 HTTP 请求 │ ├── service/ # 业务逻辑,订单状态流转在这 │ ├── mapper/ # 数据访问,SQL 映射 │ ├── entity/ # 实体类,对应数据库表 │ └── config/ # 数据库连接、跨域等配置 ├── sql/ │ └── init.sql # 建表语句 + 初始菜单数据 └── README.md # 启动说明

这个结构的关键点在于controller / service / mapper三层是物理分开的。你改一个"下单接口",只需要动 controller 里对应的方法签名和 service 里的业务方法,mapper 层基本不用碰。反过来,如果菜单表加了一个"辣度"字段,你只需要改 entity、mapper 的 SQL 和前端展示,controller 的接口路径不变。这种"改一处不影响全局"的特性,是判断一个毕业设计工程是否合格的重要标准,也是你二次开发时最省心的地方。

frontend/common里通常放的是请求封装,比如统一带上 token、统一处理错误码。很多同学拿到工程后发现自己加的新页面请求不通,八成是没走这个公共封装,跨域或鉴权没带上。这一点后面避坑章节会细说。

2.3 数据库表设计与订单状态机

点餐系统的数据模型不复杂,但订单状态机是灵魂。核心三张表:menu(菜单)、orders(订单主表)、order_item(订单明细)。订单状态一般设计成枚举:待接单、制作中、已完成、已结账。状态流转必须单向,不能从"已结账"倒回"制作中",否则前台和后厨的数据就对不上了。

-- 订单主表,status 用整数枚举控制流转 CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, table_no VARCHAR(16) NOT NULL, -- 桌台号,扫码时带入 status TINYINT NOT NULL DEFAULT 0, -- 0待接单 1制作中 2已完成 3已结账 total_price DECIMAL(10,2) NOT NULL, -- 下单时算好落库,避免重复计算 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细,一个订单多条记录 CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, menu_id BIGINT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL -- 下单时的单价快照 );

这里有两个参数值得说清楚。total_price在下单时就计算好落库,而不是每次查询时现算,原因是菜单价格可能被后台修改,如果现算会导致历史订单金额跟着变,这是血泪经验。order_item.price存的是下单时的单价快照,同理,菜单涨价不影响已下单的订单。status用 TINYINT 而不是字符串,是为了查询和索引效率,前端展示时再做枚举映射。这三处设计看着小,但决定了你后面改需求时会不会翻车。

3. 本地跑通:从建库到三个端能访问

3.1 环境准备与数据库初始化

跑这个工程,本地需要三样东西:运行环境(Java 或 Node,看后端用什么)、MySQL 数据库、一个能开多个标签页的浏览器。先把 MySQL 起起来,然后执行sql/init.sql。这一步很多人图快直接复制粘贴,结果中文菜单变乱码,所以建库时字符集要显式指定。

# 登录 MySQL 后执行 mysql -u root -p # 建库时指定 utf8mb4,避免中文菜单乱码 CREATE DATABASE ordering DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 导入建表与初始数据 mysql -u root -p ordering < sql/init.sql

utf8mb4而不是utf8,是因为后者在部分 MySQL 版本里只支持三字节,遇到生僻字或 emoji 会报错。初始数据里一般会预置几张桌台和十几道菜,导入成功后你可以SELECT * FROM menu;确认一下数据在不在。如果表建好了但菜单是空的,多半是init.sql里的 INSERT 语句没执行到,检查一下文件末尾有没有被截断。

3.2 后端启动与接口自测

数据库就绪后,改后端配置文件里的数据库连接信息,然后启动服务。以 Spring Boot 为例,配置在application.yml里:

spring: datasource: url: jdbc:mysql://localhost:3306/ordering?useUnicode=true&characterEncoding=utf8mb4 username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver server: port: 8080

启动命令按构建工具来,Maven 项目就是mvn spring-boot:run,或者直接跑打包后的 jar。启动成功后,先用浏览器或接口工具访问一个最简单的接口验证,比如GET http://localhost:8080/api/menu/list。能返回 JSON 数组,说明数据库连通、接口层正常。这一步是整个跑通流程的分水岭:接口通了,前端问题都好排查;接口不通,先别碰前端,回去看数据库连接和端口占用。

提示:如果启动报端口被占用,先确认 8080 有没有被别的服务占着,改server.port或关掉占用进程都行,别硬重启浪费时间。

3.3 三个前端端的访问与联调

前端如果是纯静态页面,直接用浏览器打开或起一个静态服务器即可;如果是 Vue 工程,npm install后npm run serve。三个端分别对应顾客、后厨、前台,访问路径不同但共用同一套后端接口。联调时最典型的操作是:顾客端下一单,切到后厨端刷新,看订单有没有出现;后厨点"开始制作",再切前台看状态变没变。

// frontend/common/request.js 里常见的请求封装 const BASE_URL = 'http://localhost:8080/api'; export function request(path, options = {}) { return fetch(`${BASE_URL}${path}`, { headers: { 'Content-Type': 'application/json' }, ...options }).then(res => { if (!res.ok) throw new Error(`请求失败: ${res.status}`); return res.json(); }); }

这个封装的作用是把后端地址集中到一处,后面部署到服务器时只改BASE_URL一个地方。参数说明:path是接口相对路径,options透传 method、body 等。如果你新加的页面请求 404,先确认路径有没有拼错、有没有走这个封装。三个端能各自打开、下单后数据能串起来,这个工程就算真正跑通了,接下来才是改。

4. 核心模块拆解:下单链路与状态流转代码

4.1 下单接口:从购物车到落库

下单是整个系统最核心的一条链路,涉及金额计算、明细写入、订单主表写入,三步必须在一个事务里,否则会出现"主表有订单、明细是空的"这种脏数据。下面这段是 Service 层的典型写法:

@Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private OrderItemMapper orderItemMapper; @Autowired private MenuMapper menuMapper; // 下单核心方法,@Transactional 保证主表与明细同时成功或同时失败 @Transactional public Long createOrder(String tableNo, List<CartItem> cart) { BigDecimal total = BigDecimal.ZERO; List<OrderItem> items = new ArrayList<>(); for (CartItem c : cart) { Menu menu = menuMapper.selectById(c.getMenuId()); if (menu == null || menu.getStock() < c.getQuantity()) { throw new RuntimeException("菜品不存在或库存不足"); } // 单价快照,防止后续改价影响历史订单 OrderItem item = new OrderItem(); item.setMenuId(c.getMenuId()); item.setQuantity(c.getQuantity()); item.setPrice(menu.getPrice()); items.add(item); total = total.add(menu.getPrice().multiply(new BigDecimal(c.getQuantity()))); } Orders order = new Orders(); order.setTableNo(tableNo); order.setStatus(0); // 0 表示待接单 order.setTotalPrice(total); orderMapper.insert(order); // 插入后 order.getId() 回填 for (OrderItem item : items) { item.setOrderId(order.getId()); orderItemMapper.insert(item); } return order.getId(); } }

逐段看:循环里先查菜单、校验库存,这一步是防止超卖的第一道闸;item.setPrice(menu.getPrice())是单价快照,前面数据库设计里强调过;total用BigDecimal累加而不是 double,是因为金额用浮点会算出19.999999这种玄学结果;最后主表插入后order.getId()回填,再批量写明细。参数上,tableNo来自扫码时带上的桌台号,cart是前端传的购物车数组。整个方法加了@Transactional,任何一步抛异常都会回滚,这是保证数据一致性的后悔药。

4.2 订单状态流转:后厨与前台如何协同

订单落库后状态是 0(待接单),后厨端拉取状态为 0 的订单,点"开始制作"改成 1,做完改成 2,前台结账改成 3。状态更新接口要做的第一件事是校验当前状态,防止重复点击或越级流转。

// 状态流转,只允许按 0->1->2->3 单向推进 public boolean updateStatus(Long orderId, int targetStatus) { Orders order = orderMapper.selectById(orderId); if (order == null) return false; int current = order.getStatus(); // 只允许前进一格,禁止跳级和回退 if (targetStatus != current + 1) { throw new RuntimeException("非法状态流转: " + current + " -> " + targetStatus); } order.setStatus(targetStatus); return orderMapper.updateById(order) > 0; }

这段代码的关键是targetStatus != current + 1这个判断。没有它,后厨手快点两下"开始制作",状态可能从 0 直接跳到 2,前台看到"已完成"却根本没出餐,这就是典型的翻车现场。参数targetStatus由前端按钮决定,但真正的合法性判断必须在后端做,前端按钮只是引导,不能当保险。后厨端拉单的查询也很简单,SELECT * FROM orders WHERE status = 0 ORDER BY created_at ASC,先到先做,符合餐饮场景的实际节奏。

4.3 菜单管理:增删改查与前端联动

菜单是唯一需要频繁改动的数据,后台管理页面对应一组 CRUD 接口。这里要注意的是删除策略:菜单一旦被历史订单引用,物理删除会导致订单明细查不到菜名。常见做法是加一个is_deleted软删除标记,查询时过滤掉。

-- 软删除,保留历史订单的关联完整性 UPDATE menu SET is_deleted = 1 WHERE id = ?; -- 顾客端只查未删除且在售的菜品 SELECT id, name, price, stock, image_url FROM menu WHERE is_deleted = 0 AND on_sale = 1 ORDER BY category_id, id;

is_deleted和on_sale是两个不同概念:前者是"这道菜从系统里下架了",后者是"暂时售罄"。前端顾客端只展示on_sale = 1的,后台管理能看到全部未删除的。这个区分在答辩时是个加分点,说明你考虑过数据生命周期。菜单改动后前端不刷新是常见现象,因为很多实现里菜单是页面加载时拉一次,改完要手动刷新或加轮询,这一点在下一章的排查里会提到。

5. 避坑与常见问题排查

5.1 中文菜单乱码

现象:数据库里菜单名显示正常,但前端页面显示成问号或方块。原因通常是数据库、连接串、页面三处字符集不一致,最常见的是建库时用了utf8而连接串没指定characterEncoding。解决:建库用utf8mb4,JDBC 连接串加useUnicode=true&characterEncoding=utf8mb4,前端页面<meta charset="utf-8">,三处对齐后重启服务。改完记得清一下浏览器缓存,不然看到的还是旧页面。

5.2 前端请求跨域被拦

现象:浏览器控制台报 CORS 错误,接口单独访问正常,页面里请求失败。原因:前端和后端端口不同,浏览器同源策略拦截。解决:后端加跨域配置,允许前端来源。Spring Boot 里加一个配置类或在 controller 上加@CrossOrigin,开发阶段图省事可以放开所有来源,但上线前要收紧到具体域名,这个习惯我建议从毕设阶段就养成。

5.3 下单后后厨端看不到订单

现象:顾客端提示下单成功,后厨端刷新没有新订单。原因通常有两个:一是后厨端查询条件写死了某个状态值,而下单落库的状态和它不一致;二是后厨端页面没有轮询或手动刷新机制,数据是打开页面那一刻的快照。解决:先直接查数据库确认订单在不在、状态对不对,再看后厨端的查询 SQL。如果数据在但页面不显示,就是前端刷新问题,加个定时拉取或手动刷新按钮即可。

5.4 金额出现小数误差

现象:订单总价出现0.30000000000000004这类结果。原因:用 double 或 float 做金额运算。解决:全链路改用BigDecimal,数据库字段用DECIMAL(10,2),前端展示时再格式化。这个坑在答辩演示时特别容易暴露,因为老师往往会盯着金额看,提前改掉能省不少尴尬。

5.5 端口冲突导致启动失败

现象:后端启动报Port 8080 was already in use。原因:上一次启动的进程没退干净,或者本机有其他服务占了端口。解决:先查占用进程并结束,或者直接改server.port换一个端口,同时记得同步改前端BASE_URL里的端口号,否则前端会请求到旧端口上,表现为"服务起来了但页面全报错"。

6. 二次开发:把毕设改成能讲出亮点的作品

拿到这份工程,最忌讳的是原封不动交上去。答辩老师看一眼就知道是模板,真正拉开差距的是你改了什么、为什么改。我的建议是从三个方向动手,每个方向都能讲出技术点。

第一个方向是加"桌台状态看板"。现在前台只能看订单,看不到哪些桌台在用、哪些空着。你可以在orders表基础上加一个桌台状态查询接口,按table_no聚合出"有未结账订单即占用"的逻辑,前台页面用不同颜色区分。这个改动涉及一次聚合查询和前端状态渲染,工作量不大但演示效果直观。

第二个方向是把轮询改成 WebSocket 推送。现在后厨端靠刷新或定时拉取,体验一般。加一个 WebSocket 端点,下单成功后服务端主动推给后厨端,这是能写进论文"系统优化"章节的实打实改进。改造时注意连接管理和断线重连,别为了演示效果把稳定性丢了。

第三个方向是补一层简单的权限。现在三个端谁都能访问,加一个基于角色的登录:顾客扫码免登录、后厨和前台要登录。用拦截器校验请求头里的 token 即可,不用上重型框架。这一步能让你的系统从"能跑"变成"像个真系统"。

改造方向涉及模块难度答辩可讲点
桌台状态看板订单查询 + 前台页面低聚合查询、状态可视化
WebSocket 推送后端 + 后厨端中实时通信、连接管理
角色权限拦截器 + 登录页中鉴权流程、token 校验

改造完一定要自己走一遍完整链路验证:下单 → 后厨接单 → 制作完成 → 前台结账,每一步都确认数据对得上。我见过太多人改完一个模块就急着演示,结果结账时金额对不上,当场卡壳。从那以后我每次改完核心链路,都强制自己从下单走到结账完整跑一遍,再去看边角功能。这份工程的价值不在于它多复杂,而在于它给了你一个能改、改完能验证的底座,剩下的亮点靠你自己往上加。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表