简介:面向毕业设计、课程设计及期末大作业场景的微信小程序点餐系统实战项目,集小程序前端、后台管理端与数据库于一体,提供从用户点餐、菜单浏览、购物车到订单处理的完整可运行方案。适合需要快速搭建项目链路、又希望理解业务代码做二次开发的学习者。压缩包共210个文件,大小仅1.84MB,内含72个Java源码文件、51张PNG图片,并搭配16个WXSS样式、16个JSON配置、15个JS逻辑、13个WXML页面模板与11个FreeMarker后台模板,同时附带SQL数据库初始化脚本、Maven配置及环境参数文件,覆盖前端交互、后端接口与数据存储的模块化代码结构。已有983人浏览学习,源码注释详细、部署简单,是导师认可的高分毕业项目。通过这份资源,读者既可以掌握小程序点餐系统的模块划分、接口设计与数据库建表思路,也能直接基于现有代码修改扩展,快速完成自己的毕业设计或课程答辩。
1. 微信小程序点餐系统拿到手先看什么:一套能交差的高分毕设长什么样
毕设周还剩一周、代码一行没写的焦虑,我见过太多次。这套微信小程序点餐系统就是为这个场景准备的:小程序端、后台管理端、数据库三件套齐全,从菜品展示、下单到后台订单管理是一条完整闭环,不是那种只有一个静态页面的 demo。它直接给到可运行的源码和 SQL,下载后按库里步骤部署就能用,代码带注释,新手跟着走也能在答辩前把项目跑起来,导师看到的是「全栈能力」而不是「调了三天 UI」。
这套资源适合三类人:正在做毕业设计、课程设计或期末大作业的学生,想借真实项目入门小程序全栈开发的开发者,以及需要一套干净底子做二次开发的从业者。它的价值不在于代码多牛,而在于「麻雀虽小五脏俱全」,每一层都能在答辩时讲出东西来。
2. 微信小程序点餐系统后端:从 Maven 工程结构到 FreeMarker 后台页的读法
拿到源码先别急着双击,花十分钟搞清后端长什么样,后面部署和答辩都能省不少事。这章按一个合格工程师拆项目的顺序来:先看工程结构,再看页面模板,最后对接口和数据库。
2.1 从 mvnw.cmd 和 pom.xml 读清技术栈
资源包一打开,如果看到 mvnw.cmd、pom.xml、src/main/resources/templates 这一串,基本可以断定后端是 Spring Boot + Maven 工程,页面模板用的是 FreeMarker。从 login.ftl、list.ftl 这些文件名就能识别出来,FreeMarker 模板文件约定用 .ftl 后缀。
这不是偶然选择。毕业设计选 Spring Boot 的原因很实在:Maven 管理依赖,不用手动下载 jar 包;Spring Boot 内嵌 Tomcat,一个命令就能启动,交付时不需要对方额外装服务器;再加上 MyBatis 操作 MySQL,这套组合在高校项目里普及率最高,遇到问题网上随便一搜就有答案。答辩老师必问「为什么选 Spring Boot」,你就答一句话:生态成熟、配置少、启动快,而且和 MySQL 配合做点餐这类业务系统非常顺手。
mvnw.cmd 是 Maven Wrapper 的 Windows 启动脚本,它的作用是让项目在没装 Maven 的机器上也能用固定版本构建。我一般这样用:
# Windows 下用 Maven Wrapper 直接启动,不需要本地装 Maven ./mvnw spring-boot:run # 如果想打包成 jar 再部署,先 clean package ./mvnw clean package java -jar target/xxx.jar第一条命令适合开发调试,改了代码重启即可;第二条命令适合部署到服务器或演示环境。注意xxx.jar的实际名字要去 target 目录下看,一般是项目名加版本号。如果资源里同时有 mvnw(无后缀)和 mvnw.cmd,说明项目考虑到了 Mac 和 Linux 用户,在 Mac 上要先执行chmod +x mvnw才有执行权限,这是个容易忽略的小坑。
2.2 后台页面除了管数据,还能帮你答辩讲 MVC
后台管理端这组 ftl 文件,本质上是「服务端渲染页面」的典型代表。login.ftl 是管理员登录页,index.ftl 是后台首页框架,list.ftl 是列表页(菜品列表和订单列表通常会共用这个模板),detail.ftl 是详情编辑页,nav.ftl 是左侧公共导航栏,被 index.ftl 引入。
FreeMarker 的逻辑很简单:模板里写 HTML 骨架,服务端把数据填充进去再吐给浏览器。跟 JSP 相比它更轻量,跟 Vue 那种前后端分离项目相比,它是服务端渲染。答辩时老师问到「你的后台页面是怎么渲染的」,这就是一个清晰的回答点——别小看这一句,很多学生答不上来自己页面的数据是怎么从数据库跑到浏览器上的。
典型的菜品列表页片段长这样,你可以对照自己资源里的 list.ftl 看看是不是同一个套路:
<!-- 菜品列表:FreeMarker 用 <#list> 遍历后端传过来的 dishList --> <#list dishList as dish> <tr> <td>${dish.id}</td> <td>${dish.name}</td> <td>${dish.price}</td> <td> <a href="/admin/dish/detail?id=${dish.id}">编辑</a> <a href="/admin/dish/delete?id=${dish.id}" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </#list>这里${dish.name}是取后端返回对象的字段值,<#list dishList as dish>是循环指令。关键是dishList这个变量名必须和 Controller 里往 Model 塞数据时用的 key 一致,不然页面上什么都渲染不出来。看到白板不要慌,先去 Controller 里找model.addAttribute("dishList", ...)这句话,名称对上了页面自然就出来了。
2.3 接口清单:先对接口再动页面
这种前后端混合的项目,接口一般分成两组:小程序端走/api/前缀,后台管理端走/admin/前缀。在动任何页面之前,我习惯先列一张接口清单,把请求路径、方法、作用对照源码核对一遍。下面是这类点餐系统最常见的一套接口设计,你的资源里可能有增减,以 Controller 里的@RequestMapping注解为准:
| 接口路径 | 方法 | 作用 |
|---|---|---|
| /api/dish/list | GET | 小程序首页菜品列表 |
| /api/dish/detail?id= | GET | 菜品详情 |
| /api/order/submit | POST | 提交订单 |
| /api/order/list | GET | 我的订单列表 |
| /admin/login | POST | 管理员登录 |
| /admin/dish/list | GET | 后台菜品列表 |
| /admin/dish/save | POST | 新增或编辑菜品 |
| /admin/dish/delete | GET | 删除菜品 |
| /admin/order/list | GET | 后台订单列表 |
这里有个很实用的排查技巧:小程序端报错时,先确认请求路径是不是以 /api 开头;后台页面打不开时,先确认路径是不是以 /admin 开头。分组清晰的项目,问题定位能快一半。另外,看到POST /admin/dish/save这种一个接口同时管新增和编辑的设计,不要觉得奇怪,这是很常见的写法——表单里带 id 就是编辑,不带 id 就是新增,后端用一个 save 方法搞定。
2.4 数据库最少三张表:菜品、订单、订单明细
点餐系统的数据模型并不复杂,拆开来看就三张核心表。菜品表存菜单,订单表存一次下单的总体信息,订单明细表存这次订单里具体点了哪几个菜、各几份。三者的关系是:一个订单对应多条订单明细。我在拆这类项目时发现,很多版本会再加一张用户表存微信用户的 openid,实现「谁下的单」这个诉求。
常见的设计是这样,你可以打开资源里的 SQL 文件对照:
-- 菜品表:注意价格用 DECIMAL,不要用 FLOAT,避免浮点误差 CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, price DECIMAL(10,2) NOT NULL, image VARCHAR(255), category_id INT, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单表:order 是 MySQL 保留字,建表时必须加反引号 CREATE TABLE `order` ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT, total_price DECIMAL(10,2), status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细表:记录订单和菜品的多对多关系 CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT NOT NULL, dish_name VARCHAR(50), price DECIMAL(10,2), count INT DEFAULT 1 );status字段是这套系统的状态机核心,菜品表里 1 代表上架、0 代表下架;订单表里一般 0 待付款、1 待取餐、2 已完成、3 已取消。这个字段设计一定要看懂,因为小程序端要拿它来控制页面显示什么按钮,后台要拿它做订单筛选。这里还有一个血泪教训:order是 MySQL 保留字,直接CREATE TABLE order必报语法错误,要么加反引号,要么把表名改成orders。你下载的这份资源如果用的表名是t_order或者orders,说明作者已经踩过这个坑了。
3. 微信小程序点餐系统小程序端:页面栈、请求封装与点单流程走读
后端看明白了,再看小程序端就会觉得轻车熟路。小程序端本质上是「只做交互和展示,数据都问后端要」。这章把目录结构、请求封装、购物车和下单流程拆开讲,走完一遍你就知道这个小程序是怎么跑起来的。
3.1 小程序目录结构:四个页面撑起点餐闭环
小程序端一般是用原生小程序写的,没有引入 uni-app 或 Taro,这对毕设来说是加分项——答辩时可以明说「原生开发,没有依赖额外框架」。拆开 pages 目录,典型结构是:index 首页(菜品列表)、detail 菜品详情、cart 购物车、user 我的订单,再配一个公共的 utils 目录放请求封装。
| 页面 | 文件 | 职责 |
|---|---|---|
| pages/index | index.wxml / index.js | 加载菜品列表、分类切换 |
| pages/detail | detail.wxml / detail.js | 菜品详情、加入购物车 |
| pages/cart | cart.wxml / cart.js | 购物车列表、提交订单 |
| pages/user | user.wxml / user.js | 我的订单、状态展示 |
app.json 里注册页面路径和 tabBar,这个文件决定了小程序底部导航长什么样。注意一个常见错误:新建了页面但忘了在 app.json 的 pages 数组里注册,运行时直接报「页面路径找不到」。我每次新建页面第一件事就是去 app.json 登记,这个习惯能省不少排查时间。
3.2 请求封装:BASE_URL 一个文件全局生效
小程序端最值得看的代码是 utils 目录下的请求封装。wx.request 是底层 API,直接到处调用会带来两个问题:一是后端地址散落在几十个文件里,换环境要全局替换;二是错误处理不统一。正规做法是封装成一个 Promise 方法,所有页面都走这一个入口。
// utils/request.js —— 所有请求都走这里,改后端地址只动这一个文件 const BASE_URL = 'http://localhost:8080'; // 真机预览时改成电脑的局域网 IP function request(url, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: method, data: data, header: { 'Content-Type': 'application/json' }, success(res) { // 后端返回统一格式:{ code: 200, data: ... } if (res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常,请检查后端服务', icon: 'none' }); reject(err); } }); }); } module.exports = { request, BASE_URL };封装之后,页面里调用就非常简洁,比如request('/api/dish/list', 'GET', { categoryId: 1 }),返回的 Promise 直接.then()拿数据。参数说明:BASE_URL是全局后端地址,开发工具里用http://localhost:8080;真机预览时手机不能访问电脑的 localhost,要改成电脑在局域网里的 IP,比如http://192.168.1.5:8080。这是小程序开发最经典的联调坑,后面的避坑章节会细说。
3.3 从点菜到下单:购物车缓存与提交订单的边界
点餐系统的核心交互链路是:用户在首页看到菜品列表,点进详情页把菜加入购物车,在购物车页确认数量后提交订单。这里有一个架构上的关键决策——购物车数据存在哪?
我拆过的多数点餐毕设,购物车是存在本地的,也就是用wx.setStorageSync存到微信的本地缓存里,只有提交订单时才往后端发请求。这样做的理由是:加购是高频操作但非关键数据,没必要每次都请求后端;订单才是关键数据,必须落库。这种「本地缓存做中间态、后端落库做最终态」的思路,答辩时能展现出你对数据一致性的理解。
// 加购物车:只写本地缓存,不往后端发请求 function addToCart(dish) { const cart = wx.getStorageSync('cart') || []; const exist = cart.find(item => item.id === dish.id); if (exist) { exist.count += 1; // 已存在的菜只加数量 } else { cart.push({ id: dish.id, name: dish.name, price: dish.price, count: 1 }); } wx.setStorageSync('cart', cart); // 覆盖式写入,不用手动删旧数据 } // 提交订单:一次性把购物车数据发给后端 function submitOrder() { const cart = wx.getStorageSync('cart') || []; if (!cart.length) { wx.showToast({ title: '购物车是空的', icon: 'none' }); return; } const totalPrice = cart.reduce((sum, item) => sum + item.price * item.count, 0); request('/api/order/submit', 'POST', { items: cart, totalPrice: totalPrice }) .then(res => { wx.removeStorageSync('cart'); // 下单成功清空购物车 wx.navigateTo({ url: '/pages/user/index' }); // 跳到订单列表 }); }逻辑说明:addToCart先读缓存,再判断购物车里有没有同 id 的菜,有就加数量、没有就 push 新条目,最后整体写回缓存。submitOrder先检查购物车是否为空,然后算总价,把items数组和totalPrice一起 POST 给后端。后端拿到后做两件事:在 order 表插入一条总单,在 order_item 表插入多条明细。这里要注意,后端收到的 items 是个数组,Controller 层要用List<CartItem>之类的对象去接,如果发现下单后数据库没有数据,多半是 JSON 数组的字段名和后端实体类对不上。
3.4 订单状态:status 字段驱动页面显示
订单提交后,最关键的逻辑就是这个 status 字段。小程序端从/api/order/list拉回订单列表,每个订单带一个 status 数字,页面根据数字显示不同的 UI。整套状态流转是这样的:
| status | 含义 | 小程序端显示 |
|---|---|---|
| 0 | 待付款 | 订单详情、去支付按钮 |
| 1 | 待取餐 | 等待取餐提示、取餐码 |
| 2 | 已完成 | 订单完成、再来一单 |
| 3 | 已取消 | 订单已取消 |
实现上就是一个wx:if或switch判断:
<!-- 订单列表页片段:根据 status 显示不同状态文字 --> <view wx:if="{{item.status === 1}}"> <text class="status">待取餐</text> <text class="pickup-code">取餐码:{{item.pickupCode}}</text> </view> <view wx:elif="{{item.status === 2}}"> <text class="status">已完成</text> </view>关于缓存时间的设置,这也是一个常被问到的小细节。购物车这种数据用wx.setStorageSync就好,但如果想给缓存加失效时间,常见做法是存一个时间戳,读取时用Date.now()做对比,超过时限就清掉重取。这个方法可以用在菜品列表缓存上,减少不必要的请求。小程序端到这里就通了,接下来就是把整个系统跑起来。
4. 部署与联调避坑排查:从本地起服务到小程序能点单的五处翻车点
资源下载下来能不能用,取决于部署这一步有没有走对。我帮人排过不少这类项目的故障,百分之八十的问题出在环境配置和路径匹配上。这章先给标准部署流程,再列五条高频踩坑记录,最后讲排查顺序。
4.1 部署三步走:导库、起后端、配小程序
第一步先把数据库建好。资源包里一般有一个 .sql 文件,名字可能是 shop.sql 或 db.sql,用命令行或 Navicat 导入都行:
# 命令行导入前提是 MySQL 已启动,且该 .sql 文件里可能包含建库语句 mysql -uroot -p < shop.sql导入后确认一下数据库名和后端配置文件里的连接串是否一致,这一步经常出问题,后面避坑清单里细说。第二步启动后端:
# 在项目根目录执行 ./mvnw spring-boot:run看到 Tomcat started on port 8080 之类的日志就算启动成功。第三步是打开微信开发者工具,导入小程序目录,修改utils/request.js里的BASE_URL。开发阶段用http://localhost:8080即可,注意在开发者工具右上角「详情」-「本地设置」里勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」,否则本地调试会被域名校验拦住。
4.2 五处翻车点:现象、原因、解决一条条说清
翻车点一:小程序请求报「不在以下 request 合法域名列表中」
现象:小程序页面能打开,但所有数据加载不出来,Console 里报错提示请求的域名不在合法域名列表里。
原因:微信小程序对请求域名有安全校验,默认只允许 HTTPS 且已在后台配置过的域名。我们本地开发用的是http://localhost,自然过不了校验。
解决:开发阶段在微信开发者工具里勾选「不校验合法域名」;真机预览时,如果手机连的是电脑热点或同一局域网,也要在预览弹窗里勾选「不校验合法域名」。正式上线才需要配 HTTPS 域名,毕业设计答辩用开发模式就够了。
翻车点二:后台登录页打开是白屏或 404
现象:访问http://localhost:8080/admin/login时页面空白,或者浏览器直接返回 404。
原因:FreeMarker 模板没放到正确位置,或者 Controller 返回的视图名和模板文件名对不上。Spring Boot 自动配置约定模板必须放在src/main/resources/templates目录下,放的路径错了就不会被渲染。
解决:先确认 login.ftl 在 templates 目录下,再看 Controller 里return "login"是否和文件名 login 对应。一个常见的手误是把模板放到了static目录下,那个目录只放静态资源,不解析 FreeMarker。
翻车点三:数据库中文全部变成问号
现象:后台菜品列表里中文名称显示为???,但英文正常。
原因:MySQL 连接串没有指定字符集,数据库默认 latin1 或连接时用了错误的编码。
解决:在后端的application.yml或application.properties里,把 JDBC 连接 URL 改成:
jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8同时确认数据库表本身是 utf8mb4 字符集。改完重启后端,重新导入一次 SQL,中文就正常了。
翻车点四:mvnw.cmd 双击后一闪而过,没有任何反应
现象:Windows 下双击 mvnw.cmd,黑窗口闪一下就没了,后端没启动起来。
原因:mvnw.cmd 本质是个批处理脚本,双击时如果有报错,窗口会立即关闭看不见错误信息。多数情况是没装 JDK,或者 JDK 版本和项目要求的对不上。
解决:先在命令行执行java -version确认 JDK 已安装。然后不要双击,直接在项目根目录开命令行执行mvnw.cmd spring-boot:run,这样错误信息会留在窗口里。如果报错说无法解析 spring-boot 依赖,多半是 Maven Wrapper 下载依赖超时,可以检查一下网络,或者直接把本地 Maven 的 settings.xml 镜像换成阿里云镜像。
翻车点五:下单后后台订单列表里没有记录
现象:小程序端下单提示成功,但后台订单列表是空的,或者数据库里 order 表没有新数据。
原因:最常见的两个原因。一是order是 MySQL 保留字,建表或插入时没加反引号导致写入失败,但异常被吞掉;二是前端提交的 JSON 字段名和后端实体类属性名对不上,后端接收到的对象是空的但接口没报错。
解决:先看后端 Console 有没有 SQL 异常日志。如果是保留字问题,用反引号包裹表名或者把表改成orders。如果是字段名问题,打开浏览器的 Network 面板,看提交的请求体里字段叫什么,再去比对后端的实体类——比如前端传了totalPrice,后端类里却是total_price,中间缺一个映射就会全丢。
4.3 排查链路:先看请求有没有出去,再看后端收没收到
| 排查顺序 | 看哪里 | 判断标准 |
|---|---|---|
| 第一层 | 小程序 Network 面板 | 有没有发出请求、返回什么状态码 |
| 第二层 | 后端 Console / 日志文件 | 有没有收到请求、SQL 有没有执行 |
| 第三层 | 数据库表 | 数据有没有真正落库 |
这个顺序能覆盖多数问题。请求根本没发出去,问题在小程序端,先查 BASE_URL 和域名校验;请求发出去了但返回 404,问题在路由,用接口清单核对路径;请求 200 但数据不对,问题在 SQL 或字段映射,去后端日志里找答案。后端在 IDEA 里跑就直接看 Console,命令行跑建议输出到日志文件:
# 把日志输出到文件,方便滚动排查 java -jar target/xxx.jar > app.log 2>&1 & tail -f app.log2>&1是把标准错误也重定向到日志里,这样堆栈信息不会丢。这套排查链路我每次联调都走一遍,基本能定位百分之九十的问题。
5. 把项目改出自己的味道:三个改动小、答辩加分大的点
拿到手的源码是「能跑」,但答辩要想拿高分,得让它变成「你的项目」。这章给三个改动成本低、但讲起来很有说头的方向,以及一套答辩前的自检流程。
5.1 改动一:换一套你自己的菜品数据
默认的菜品数据是作者之前导入的,你完全可以改成自己熟悉的场景——食堂、奶茶店、咖啡吧都行。操作很简单,写几条 INSERT 替换 dish 表里的数据:
-- 替换成自己设计的菜单,注意保持分类 ID 对应 INSERT INTO dish (name, price, image, category_id, status) VALUES ('招牌牛肉面', 18.00, '/images/dish/beef.png', 1, 1), ('经典拿铁', 15.00, '/images/dish/latte.png', 2, 1), ('鸡胸肉沙拉', 22.00, '/images/dish/salad.png', 1, 1);改完之后把图片也换掉,界面风格再调一下导航栏颜色,整个项目看起来就是「你的作品」。答辩时被问到数据哪来的,你可以说「针对学校食堂的场景重新设计了菜单和分类」,这句话比「用的源码自带数据」有力得多。
5.2 改动二:后台首页加一个统计看板
后台首页如果只是空架子,可以加三张统计卡片:菜品总数、今日订单数、待处理订单数。实现上就是三条 count 查询,放在 Controller 里返回给 index.ftl:
// 在后台首页 Controller 里加三个统计值 model.addAttribute("dishCount", dishService.count()); model.addAttribute("orderCount", orderService.countByStatus(1)); model.addAttribute("todayOrderCount", orderService.countToday());页面模板里用${dishCount}这种变量显示。这个改动的价值在于:答辩时你能主动聊「数据可视化」,展示你理解如何把数据库统计结果呈现给运营人员。哪怕实现简单,也比「后台只能增删改查」听起来完整。
5.3 改动三:订单加一个取餐码
线下点餐场景有个很接地气的需求——取餐码。订单提交后,后端在生成订单时生成一个 4 位随机数字,返回给小程序端展示,同时存到订单表里。前端做起来就是加一个字段展示:
// 提交订单成功后的回调里,把取餐码显示出来 .then(res => { wx.showModal({ title: '下单成功', content: '您的取餐码是 ' + res.pickupCode, showCancel: false }); wx.removeStorageSync('cart'); });这个改动小而完整,涉及后端字段、前端展示、交互反馈三个环节,答辩时讲出来就是「我完整地设计了点餐闭环里的一个用户触点」。
5.4 答辩前半小时的自检清单
最后给一份我自己的自检流程,每次演示前强制走一遍。这不是客套,是血泪教训换来的——我之前帮人改项目,答辩前一晚改了数据库密码没同步到配置文件,第二天当着三个老师的面后端起不来,场面极其尴尬。
| 检查项 | 操作 | 预期结果 |
|---|---|---|
| 数据库 | 连接 MySQL,查 dish 表 | 有数据,中文正常 |
| 后端 | 执行 mvnw spring-boot:run | 8080 端口启动成功 |
| 小程序 | 打开首页 | 菜品列表加载出来 |
| 核心链路 | 加购 → 提交订单 → 查后台 | 订单出现在后台列表 |
| 异常项 | 停掉后端再点小程序 | 有清晰的网络异常提示 |
从那以后,我每次演示都把「数据库 → 后端 → 小程序」这条链路完整跑一遍,确认下单能走到订单列表才放心。这套微信小程序点餐系统本身结构不难,难的是你够不够熟悉它——按这章给的方向改一改、走一遍,答辩时你就能讲出自己的东西了。希望帮到你。
本文还有配套的精品资源,点击获取