ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微信小程序点餐系统毕业设计:Spring Boot+MyBatis全栈实现与部署避坑

微信小程序点餐系统毕业设计:Spring Boot+MyBatis全栈实现与部署避坑 简介面向毕业设计、课程设计及期末大作业场景的微信小程序点餐系统实战项目集小程序前端、后台管理端与数据库于一体提供从用户点餐、菜单浏览、购物车到订单处理的完整可运行方案。适合需要快速搭建项目链路、又希望理解业务代码做二次开发的学习者。压缩包共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} onclickreturn 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/listGET小程序首页菜品列表/api/dish/detail?idGET菜品详情/api/order/submitPOST提交订单/api/order/listGET我的订单列表/admin/loginPOST管理员登录/admin/dish/listGET后台菜品列表/admin/dish/savePOST新增或编辑菜品/admin/dish/deleteGET删除菜品/admin/order/listGET后台订单列表这里有个很实用的排查技巧小程序端报错时先确认请求路径是不是以 /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/indexindex.wxml / index.js加载菜品列表、分类切换pages/detaildetail.wxml / detail.js菜品详情、加入购物车pages/cartcart.wxml / cart.js购物车列表、提交订单pages/useruser.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 层要用ListCartItem之类的对象去接如果发现下单后数据库没有数据多半是 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 classstatus待取餐/text text classpickup-code取餐码{{item.pickupCode}}/text /view view wx:elif{{item.status 2}} text classstatus已完成/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?useUnicodetruecharacterEncodingutf8同时确认数据库表本身是 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 21 tail -f app.log21是把标准错误也重定向到日志里这样堆栈信息不会丢。这套排查链路我每次联调都走一遍基本能定位百分之九十的问题。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:run8080 端口启动成功小程序打开首页菜品列表加载出来核心链路加购 → 提交订单 → 查后台订单出现在后台列表异常项停掉后端再点小程序有清晰的网络异常提示从那以后我每次演示都把「数据库 → 后端 → 小程序」这条链路完整跑一遍确认下单能走到订单列表才放心。这套微信小程序点餐系统本身结构不难难的是你够不够熟悉它——按这章给的方向改一改、走一遍答辩时你就能讲出自己的东西了。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进