☰
无线点餐系统毕设实战:从技术选型到局域网联调全流程
2026/10/13 3:49:00 网站建设 项目流程

简介:这份资源是面向高校计算机相关专业毕业生的毕业设计参考项目——无线点餐系统,适合正在准备毕设、需要完整可运行案例的学生与开发者。项目围绕Android客户端与后台服务展开,涉及无线网络通信、菜单与桌台管理、订单处理、登录与更新等典型业务模块,可帮助读者理解移动端点餐系统的整体架构与实现思路。压缩包共365个文件,约9.99MB,以109个class与75个java源码为核心,辅以61张jpg与18个gif界面素材、30个xml布局、18个jar依赖、11个jsp页面及6份doc文档,另含apk安装包与数据库文件,结构完整便于二次开发。目前已有238人学习下载,可作为毕设选题、课程设计或Android入门练手项目,通过阅读源码与文档快速掌握无线点餐系统的开发流程与关键实现。

1. 无线点餐系统毕设:从选题到跑通的真实路径

每年毕业季前三个月,总有一批同学被同一个问题卡住:毕业设计选什么。无线点餐系统这个题目年年出现在选题列表里,不是没有原因——它同时踩中了移动端、后端服务、数据库设计和局域网通信四个方向,工作量可控,演示效果直观,答辩时老师一眼就能看懂你在做什么。但真正动手之后,翻车的方式五花八门:有人卡在手机连不上电脑的服务端,有人数据库建了十几张表结果只用了三张,有人在答辩现场演示时热点断了直接冷场。

这个题目的本质是:用一台设备当服务端(通常是笔记本),手机或平板通过局域网访问,完成浏览菜单、下单、查看订单状态这条核心链路。它不需要公网服务器,不需要云服务,一台笔记本加一部手机就能跑通全流程。适合基础一般但愿意动手的本科生,也适合想快速验证一个全栈方案可行性的开发者。下面按实际开发顺序,把选型、搭建、编码、联调和踩坑逐层拆开讲。

2. 技术选型与最小系统架构:别一上来就微服务

2.1 为什么毕设场景不适合上重型框架

我见过太多同学在开题报告里写“前端用 Vue3 + Vite + Pinia,后端用 Spring Boot + MyBatis-Plus + Redis,数据库 MySQL 8.0,部署用 Docker + Nginx”。听起来很完整,但实际开发周期只有六到八周,还要写论文。结果就是:环境配了两周,代码写了三周,联调发现跨域问题卡了五天,最后答辩时功能只完成了菜单展示。

毕设无线点餐系统的核心诉求是“能演示、能讲清楚、代码量适中”。常见做法是选一套轻量组合:后端用 Python Flask 或 Node.js Express,数据库用 SQLite 或 MySQL,前端用原生 HTML + 少量 JavaScript 或者 Vue 的 CDN 引入方式。这样你不需要配 Node 环境、不需要打包构建、不需要处理复杂的跨域代理,打开浏览器就能调试。

提示:如果你的学校明确要求使用 Java 或 Spring 体系,那就老老实实用 Spring Boot,但不要再加 Redis、消息队列这些中间件。把 Controller、Service、Mapper 三层写清楚,已经足够支撑一篇本科毕设。

2.2 最小系统的四个模块与数据流

一个能跑通的无线点餐系统,最少需要四个模块:

模块职责典型实现
菜单展示从数据库读取菜品分类和列表GET /api/menu
购物车前端本地暂存已选菜品localStorage 或内存对象
下单将购物车内容写入订单表POST /api/order
订单查询按桌号或订单号查状态GET /api/order/:id

数据流是这样的:手机浏览器访问服务端 IP 和端口,加载菜单页面;用户点击菜品加入购物车;提交订单时前端把 JSON 数据发给后端;后端写入数据库并返回订单号;用户凭订单号查询状态。整个链路不涉及 WebSocket 也能跑通,轮询就够了。

2.3 局域网通信的前提条件

无线点餐系统的“无线”指的是手机通过 Wi-Fi 访问服务端,不是指用了什么无线通信协议。所以第一件事是确保手机和电脑在同一个局域网内。最常见的方式是电脑开热点,手机连热点。Windows 下用“移动热点”功能,Mac 下用“互联网共享”,Linux 下用 nmcli 或 hostapd。

服务端启动时要监听 0.0.0.0 而不是 127.0.0.1,否则手机访问不到。以 Flask 为例:

# app.py from flask import Flask, jsonify, request app = Flask(__name__) # 关键:host='0.0.0.0' 让服务监听所有网卡 # port 选 5000 以上,避免和系统服务冲突 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)

启动后,在电脑上执行ipconfig(Windows)或ifconfig(Mac/Linux)查看局域网 IP,通常是 192.168.x.x。手机浏览器输入http://192.168.x.x:5000就能访问。如果访问不了,先检查防火墙是否放行了 5000 端口。

3. 数据库设计与后端接口实现:三张表就够用

3.1 菜品表、订单表、订单明细表的字段设计

很多同学一上来就设计七八张表:用户表、角色表、权限表、桌位表、菜品分类表、菜品表、订单表、订单明细表、日志表。毕设场景下,用户和权限通常不需要,桌位可以用一个字段代替。核心就三张表:

-- 菜品表 CREATE TABLE dish ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price REAL NOT NULL, category TEXT DEFAULT '热菜', image_url TEXT, available INTEGER DEFAULT 1 -- 1 表示在售,0 表示下架 ); -- 订单表 CREATE TABLE orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, table_no TEXT NOT NULL, -- 桌号,如 "A01" total_price REAL NOT NULL, status TEXT DEFAULT 'pending', -- pending / cooking / served created_at TEXT DEFAULT (datetime('now', 'localtime')) ); -- 订单明细表 CREATE TABLE order_item ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL, dish_id INTEGER NOT NULL, quantity INTEGER NOT NULL, FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (dish_id) REFERENCES dish(id) );

字段说明:available字段控制菜品是否可点,演示时可以先下架某个菜再刷新页面,展示实时性。status字段用三个状态就够了,不要搞七八个状态机,答辩时讲不清楚。created_at用本地时间,方便按时间排序。

3.2 用 Flask 写四个接口的完整代码

后端只需要四个接口:获取菜单、提交订单、查询订单、更新订单状态(可选)。下面是完整实现:

import sqlite3 from flask import Flask, jsonify, request, g app = Flask(__name__) DATABASE = 'restaurant.db' def get_db(): if 'db' not in g: g.db = sqlite3.connect(DATABASE) g.db.row_factory = sqlite3.Row return g.db @app.teardown_appcontext def close_db(exception): db = g.pop('db', None) if db is not None: db.close() # 接口1:获取菜单,支持按分类筛选 @app.route('/api/menu', methods=['GET']) def get_menu(): category = request.args.get('category') db = get_db() if category: rows = db.execute('SELECT * FROM dish WHERE available=1 AND category=?', (category,)).fetchall() else: rows = db.execute('SELECT * FROM dish WHERE available=1').fetchall() return jsonify([dict(r) for r in rows]) # 接口2:提交订单,接收 JSON 数组 @app.route('/api/order', methods=['POST']) def create_order(): data = request.get_json() table_no = data.get('table_no') items = data.get('items') # [{"dish_id":1,"quantity":2}, ...] if not table_no or not items: return jsonify({'error': '参数不完整'}), 400 db = get_db() total = 0 for item in items: dish = db.execute('SELECT price FROM dish WHERE id=?', (item['dish_id'],)).fetchone() if dish: total += dish['price'] * item['quantity'] cur = db.execute('INSERT INTO orders (table_no, total_price) VALUES (?, ?)', (table_no, total)) order_id = cur.lastrowid for item in items: db.execute('INSERT INTO order_item (order_id, dish_id, quantity) VALUES (?, ?, ?)', (order_id, item['dish_id'], item['quantity'])) db.commit() return jsonify({'order_id': order_id, 'total_price': total}), 201 # 接口3:查询订单状态 @app.route('/api/order/<int:order_id>', methods=['GET']) def get_order(order_id): db = get_db() order = db.execute('SELECT * FROM orders WHERE id=?', (order_id,)).fetchone() if not order: return jsonify({'error': '订单不存在'}), 404 items = db.execute('''SELECT oi.quantity, d.name, d.price FROM order_item oi JOIN dish d ON oi.dish_id=d.id WHERE oi.order_id=?''', (order_id,)).fetchall() return jsonify({'order': dict(order), 'items': [dict(i) for i in items]}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)

逻辑说明:get_db()用 Flask 的g对象管理数据库连接,避免每次请求都重新连接。create_order先算总价再插入订单,最后批量插入明细,整个过程在一个事务里。get_order用 JOIN 查询明细,返回给前端展示。

参数说明:table_no是字符串类型,支持 “A01” 这种格式。items是数组,每个元素包含dish_id和quantity。如果前端传的dish_id不存在,总价会少算,但不会报错——这是有意为之,避免因为一个菜品下架导致整个订单失败。

3.3 前端页面的最小实现

前端不需要框架也能跑。一个 HTML 文件加一段 JavaScript 就够了:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>无线点餐</title> <style> body { font-family: sans-serif; padding: 12px; } .dish { border: 1px solid #ddd; padding: 10px; margin: 8px 0; border-radius: 6px; } .cart { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; border-top: 2px solid #333; padding: 10px; } </style> </head> <body> <h2>菜单</h2> <div id="menu"></div> <div class="cart"> <span id="cart-info">购物车:0 件</span> <button onclick="submitOrder()">提交订单</button> </div> <script> let cart = []; const API = 'http://' + location.hostname + ':5000'; // 加载菜单 fetch(API + '/api/menu') .then(r => r.json()) .then(dishes => { const menu = document.getElementById('menu'); dishes.forEach(d => { const div = document.createElement('div'); div.className = 'dish'; div.innerHTML = `<strong>${d.name}</strong> ¥${d.price} <button onclick="addToCart(${d.id}, '${d.name}', ${d.price})">加入</button>`; menu.appendChild(div); }); }); function addToCart(id, name, price) { const existing = cart.find(i => i.dish_id === id); if (existing) { existing.quantity++; } else { cart.push({ dish_id: id, name, price, quantity: 1 }); } updateCartInfo(); } function updateCartInfo() { const total = cart.reduce((s, i) => s + i.quantity, 0); document.getElementById('cart-info').textContent = `购物车:${total} 件`; } function submitOrder() { if (cart.length === 0) { alert('购物车为空'); return; } fetch(API + '/api/order', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ table_no: 'A01', items: cart }) }) .then(r => r.json()) .then(data => { alert('下单成功,订单号:' + data.order_id); cart = []; updateCartInfo(); }); } </script> </body> </html>

这段代码的关键点:API变量用location.hostname自动获取当前访问的 IP,避免硬编码。购物车存在内存里,刷新页面会丢失——毕设演示够用,如果要持久化可以改用localStorage。

4. 联调与演示环境搭建:让手机真正连上来

4.1 电脑开热点的三种方式和常见失败原因

Windows 下开热点:设置 → 网络和 Internet → 移动热点 → 打开。注意要选择“共享网络连接”的来源,通常是以太网或 Wi-Fi。如果热点开了但手机连上后无法访问服务,检查 Windows 防火墙是否阻止了 Python 或 Node 的入站连接。

Mac 下开热点:系统偏好设置 → 共享 → 互联网共享 → 选择来源和共享方式(Wi-Fi)。Mac 的热点有时会分配 192.168.2.x 网段,和某些校园网冲突,导致手机能连热点但访问不了服务端。解决办法是把服务端也连到同一个热点上,或者改用手机开热点、电脑连手机。

Linux 下开热点:用nmcli dev wifi hotspot ifname wlan0 ssid test password "12345678"。这条命令在大多数发行版上可用,但需要 NetworkManager 支持。

注意:不管用哪种方式,服务端启动后先在电脑浏览器访问http://localhost:5000/api/menu,确认本地能通,再用手机访问http://电脑IP:5000/api/menu。如果本地通、手机不通,九成是防火墙问题。

4.2 用 curl 和浏览器控制台排查接口问题

联调阶段最常见的现象是:手机页面加载出来了,但菜单是空的。这时候不要瞎猜,按顺序排查:

第一步,在电脑上执行curl http://localhost:5000/api/menu,看返回的 JSON 是否正常。如果报错,说明后端代码有问题,看 Flask 控制台的错误信息。

第二步,在手机上用浏览器访问http://电脑IP:5000/api/menu。如果返回 JSON 但页面不显示,说明前端 JavaScript 有问题。用手机浏览器的开发者工具(Chrome 手机版可以通过chrome://inspect远程调试)看 Console 报错。

第三步,如果手机访问接口超时,在电脑上执行netstat -an | grep 5000(Mac/Linux)或netstat -an | findstr 5000(Windows),确认服务确实在监听 0.0.0.0:5000 而不是 127.0.0.1:5000。

4.3 答辩演示的保底方案

答辩现场的网络环境不可控,一定要准备保底方案。我一般会做两件事:第一,提前在电脑上把整个系统跑一遍,截图保存关键页面;第二,准备一个本地 HTML 文件,把菜单数据写死在 JavaScript 里,不依赖后端也能展示界面。如果现场热点连不上,至少能打开本地文件演示交互流程。

另外,手机浏览器缓存可能导致页面不更新。演示前在手机浏览器设置里清除缓存,或者用无痕模式打开。

5. 避坑与常见问题:那些答辩前夜才发现的坑

5.1 手机连上热点但打不开页面

现象:手机显示已连接 Wi-Fi,但浏览器访问服务端 IP 一直转圈。

原因:Windows 防火墙默认阻止了 Python 的入站连接,或者服务端监听的是 127.0.0.1。

解决:在 Windows 防火墙高级设置里添加入站规则,放行 5000 端口。或者临时关闭防火墙测试(答辩时不要关,提前配好规则)。服务端代码确认host='0.0.0.0'。

5.2 订单提交后总价算错

现象:购物车里显示总价 58 元,提交后订单总价变成 56 元。

原因:前端计算总价用的是加入购物车时的价格,后端重新查数据库算总价。如果某个菜品在加入购物车后被下架或改价,两边就不一致。

解决:统一以后端计算为准,前端提交订单后刷新订单详情,不要自己算。或者在下单接口里加校验,价格不一致时返回错误让用户确认。

5.3 SQLite 并发写入报 database is locked

现象:两个人同时下单,其中一个报 500 错误,控制台显示sqlite3.OperationalError: database is locked。

原因:SQLite 默认只允许一个写连接,并发写入时会锁库。

解决:毕设场景下并发量极低,可以在连接时设置timeout参数:sqlite3.connect(DATABASE, timeout=10)。或者改用 MySQL,但会增加部署复杂度。更简单的办法是在 Flask 里加一个全局锁,把写操作串行化。

5.4 手机浏览器显示乱码

现象:页面中文显示成问号或方块。

原因:HTML 文件没有声明字符编码,或者 Flask 返回的 JSON 没有设置ensure_ascii=False。

解决:HTML 里加<meta charset="utf-8">。Flask 的jsonify默认会把中文转成 Unicode 转义,在app.config里设置JSON_AS_ASCII = False即可。

5.5 演示时热点突然断开

现象:答辩到一半,手机热点断了,页面刷新失败。

原因:电脑热点不稳定,或者手机自动切换到其他 Wi-Fi。

解决:提前把手机设为“始终连接此网络”,关闭自动切换。电脑热点设置里把频段固定为 2.4GHz,兼容性更好。最保底的办法是准备一根数据线,用 USB 网络共享,手机通过 USB 访问电脑的服务。

6. 从能跑到好用:三个提升演示效果的具体技巧

第一个技巧是加一个简单的订单状态轮询。用户下单后,页面每隔 3 秒请求一次/api/order/<id>,如果状态从pending变成cooking,就在页面上显示“后厨已接单”。这个效果在答辩时很加分,实现成本极低:

// 下单成功后启动轮询 function pollOrder(orderId) { const timer = setInterval(() => { fetch(API + '/api/order/' + orderId) .then(r => r.json()) .then(data => { document.getElementById('status').textContent = '订单状态:' + data.order.status; if (data.order.status === 'served') { clearInterval(timer); // 已上菜,停止轮询 } }); }, 3000); }

第二个技巧是用二维码代替手动输入 IP。在电脑上生成一个二维码,内容是http://192.168.x.x:5000,手机扫码直接打开。Python 里用qrcode库三行代码就能生成:

import qrcode img = qrcode.make('http://192.168.1.100:5000') img.save('menu_qr.png')

答辩时把二维码投到屏幕上,老师扫码就能看到菜单,比说“大家访问这个 IP”直观得多。

第三个技巧是给菜品加图片。毕设演示时纯文字菜单显得单薄,每道菜配一张图片,页面立刻丰满起来。图片放在服务端的static目录下,数据库里存相对路径,前端用<img src="${API}/static/${d.image_url}">加载。注意图片不要太大,每张控制在 100KB 以内,否则手机加载慢。

这三个技巧加起来不到 50 行代码,但演示效果提升明显。我自己的习惯是:功能跑通之后,先别急着写论文,花半天时间把演示流程走三遍,每遍记录卡顿的地方,然后针对性优化。论文里的“系统测试”章节,直接拿这三遍的记录来写,比编数据真实得多。

希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询