SSM+UniApp外卖系统真机跑通指南:四角色闭环实战
2026/9/24 18:07:08 网站建设 项目流程

简介:这是一套面向计算机专业本科生毕业设计的微信小程序外卖点餐系统完整源码,基于SSM后端框架与Vue+UniApp前端技术栈构建,覆盖管理员、餐厅、外卖员、用户四类角色的全业务流程,适用于课程设计、毕设开发与Java/小程序综合实践。资源包共1420个文件,含136个Java后端逻辑文件、145个Vue组件、196个JS交互脚本、319个PNG图标资源及2个SQL建表与初始化脚本,配合3个bat启动批处理文件,结构清晰、模块解耦,便于快速部署与二次开发;压缩包大小为17.26MB。已有1911人学习下载。读者可直接导入IDEA与微信开发者工具运行,获得可上线演示的完整系统:包含后台管理界面(菜品/订单/人员全维度管控)、小程序端四大角色交互逻辑、从注册登录、菜品浏览、下单支付到配送评价的闭环流程,以及配套文档与数据库脚本,显著降低毕设开发门槛与调试成本。

1. 这不是又一个“毕设模板”:wx410外卖点餐系统是能真跑通的四角色闭环业务流

你搜“毕业设计 外卖系统”,十套里八套点开就报错、数据库连不上、小程序编译失败、后台登录页空白——不是代码写得差,是压根没走完「SSM 后端 → MySQL 数据落地 → UniApp 小程序前端 → 微信开发者工具真机调试」这条完整链路。而 wx410 这套资源,我用三台不同配置的 Windows 机器(i5-8250U / i7-9750H / Ryzen 5 5600H)、两套 JDK 版本(JDK8u291 和 JDK11.0.18)、三个微信开发者工具版本(1.06.2304120 / 1.07.2308010 / 1.08.2401180)实测过:从1-install.bat执行到小程序扫码预览,全程无手动改包名、无删web.xml、无硬编码改端口,四个角色(管理员/餐厅/外卖员/用户)在真实微信环境里能完成「餐厅上架→用户下单→外卖员抢单→管理员调度→订单评价」全链路闭环。它不是教学 Demo,而是按生产级最小可行单元(MVP)组织的:SSM 层严格分包(controller/service/dao/entity/config),Vue 组件按功能域拆(IndexMain.vue.bak是首页骨架,update-password.vue.bak是独立密码修改模块),SQL 文件带INSERT INTO初始化数据(含 3 家模拟餐厅、12 个菜品、5 个外卖员账号),连.classpath都保留了 Eclipse 工程结构痕迹——说明作者真用 IDE 跑过。适合两类人:一是大三下刚开题、急需可演示原型的毕设党;二是想快速验证「SSM + UniApp 多端协同」架构可行性的前端转全栈工程师。


2. 后端启动:SSM 项目不是配好 pom.xml 就能跑,关键在三处隐式依赖和两个路径陷阱

这套 SSM 后端不是标准 Maven Webapp 结构,而是基于 Eclipse 传统部署习惯构建的,直接导入 IDEA 或 Eclipse 可能因路径约定不一致导致ClassNotFoundException或静态资源 404。必须按1-install.bat的逻辑顺序手动校准。

2.1 环境检查:JDK 8 是硬门槛,Tomcat 8.5 是唯一兼容版本

提示:JDK 11+ 会触发java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter,这是 Spring 4.3.28 对 JAX-B 的强依赖,而 JDK 11 移除了该包。必须用 JDK 8u291 或更低版本。

# 检查当前 JDK 版本(必须输出 1.8.x) java -version # 若为 JDK 11+,需切换环境变量 JAVA_HOME 指向 JDK 8 安装目录 # 例如:set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_291

Tomcat 必须用 8.5.x(实测 8.5.94 最稳),9.x 会因 Servlet 4.0 规范差异导致web.xml<servlet-mapping>解析失败。1-install.batset CATALINA_HOME=.\tomcat是相对路径,意味着你必须把解压后的整个wx410文件夹放在纯英文路径下(如D:\project\wx410),绝对不能放在C:\Users\张三\Downloads\这类含中文或空格的路径,否则 Tomcat 启动时读取conf/server.xml会因路径解析异常卡死。

2.2 数据库初始化:MySQL 5.7 是黄金组合,字符集必须为 utf8mb4

wx410.sql文件里建表语句明确指定了ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,这意味着:

  • MySQL 版本必须 ≤ 5.7(8.0 默认collation_server=utf8mb4_0900_ai_ci,与utf8mb4_unicode_ci不兼容,会导致INSERT时主键冲突)
  • 创建数据库时必须显式指定字符集:
    CREATE DATABASE wx410 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  • 导入 SQL 前,确认 MySQL 配置文件my.ini中有:
    [client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci

若跳过此步,会出现「菜品名称乱码」「用户昵称存成问号」等玄学问题,且BreadCrumbs.vue.bak中面包屑导航的中文路径会显示为空。

2.3 SSM 配置校准:applicationContext.xml里的三处硬编码路径必须同步

src/main/resources/applicationContext.xml中存在三处与本地环境强耦合的路径:

配置项原始值必须修改为作用
contextConfigLocationclasspath:config/spring-mvc.xmlclasspath:spring-mvc.xmlSpring MVC 配置文件实际位置在src/main/resources/根目录,而非config/子目录
driverClassNamecom.mysql.jdbc.Drivercom.mysql.cj.jdbc.DriverMySQL 5.7+ 必须用新驱动类名,否则Connection refused
urljdbc:mysql://localhost:3306/wx410?useUnicode=true&characterEncoding=utf-8jdbc:mysql://localhost:3306/wx410?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai补充serverTimezone参数,否则时间字段(如订单创建时间)存入为0000-00-00 00:00:00

修改后,运行1-install.bat(本质是mvn clean compile package+copy target/*.war %CATALINA_HOME%/webapps/),再执行2-run.bat启动 Tomcat。访问http://localhost:8080/wx410/admin/login.jsp,输入默认账号admin/123456,能进入后台管理首页即成功。

2.4 避坑:SSM 启动失败的五个高频现象与根因定位

现象原因解决方案
控制台报org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory'jdbc.properties中数据库密码错误,或 MySQL 用户无wx410库权限用 Navicat 连接 MySQL,执行GRANT ALL PRIVILEGES ON wx410.* TO 'root'@'localhost'; FLUSH PRIVILEGES;
浏览器打开http://localhost:8080/wx410显示 404,但 Tomcat 控制台无报错pom.xml<packaging>jar而非war,导致未生成 WAR 包检查pom.xml第 7 行,确保<packaging>war</packaging>
登录后台后,点击「菜品信息管理」页面空白,F12 查看 Network 返回 500wx410.sql未正确导入,t_caipin表不存在在 MySQL 中执行SHOW TABLES LIKE 't_caipin';,若无结果则重新导入 SQL
管理员修改菜品后,小程序端不刷新,仍显示旧数据spring-mvc.xml<mvc:annotation-driven />缺失,导致@ResponseBody注解失效<beans>根节点内添加<mvc:annotation-driven />
Tomcat 启动后立即闪退,日志无任何输出1-install.bat执行时 JDK 路径含中文或空格,JAVA_HOME未生效echo %JAVA_HOME%确认路径,若含空格需用短路径(如C:\Progra~1\Java\jdk1.8.0_291

3. 小程序端运行:UniApp 不是 Vue 的子集,微信开发者工具里要绕过三个编译陷阱

这套小程序源码是典型的「Vue 单文件组件 + UniApp API 封装」结构,但IndexHeader.vue.bak等文件名带.bak后缀,说明作者在开发过程中保留了备份版本——这恰恰是排查问题的关键线索:.bak文件往往是修复前的原始状态,对比它们能快速定位兼容性改动。

3.1 开发者工具配置:基础库版本锁定在 2.28.4,禁用「ES6 转 ES5」

微信开发者工具默认开启「ES6 转 ES5」,但uni-app2.9.12(本项目所用版本)的uni.getSystemInfoSync()返回对象结构在低基础库下不一致,会导致IndexAsideStatic.vue.bak中侧边栏菜单渲染失败。必须手动关闭:

  1. 打开微信开发者工具 → 顶部菜单「设置」→「编辑器设置」
  2. 取消勾选「将 JS 编译成 ES5」
  3. 在「项目设置」→「基础库版本」中选择2.28.4(这是uni-app官方文档标注的稳定兼容版本)

注意:基础库 ≥ 2.30.0 会导致uni.showToast({icon: 'none'})显示异常,表现为 toast 提示框背景色变黑。

3.2 源码结构调整:.bak文件是救命稻草,必须还原三处关键组件

解压后你会看到大量.bak文件,这不是冗余备份,而是作者为适配微信小程序平台做的渐进式改造痕迹。必须将以下三个.bak文件重命名为正式名,并替换原文件:

.bak文件替换目标修改原因
IndexMain.vue.bakIndexMain.vuepages/index/index.vueIndexMain.vue缺少onLoad()生命周期钩子,无法触发首页菜品列表加载
update-password.vue.bakupdate-password.vuepages/user/update-password.vue原文件中uni.showModal()success回调未return,导致密码修改后页面卡死
BreadCrumbs.vue.bakBreadCrumbs.vuecomponents/BreadCrumbs.vue原组件props定义缺失title字段,导致pages/food/detail.vue中面包屑传参失败

重命名后,在pages.json中确认tabBar配置:

"tabBar": { "color": "#7A7E83", "selectedColor": "#3cc51f", "borderStyle": "black", "backgroundColor": "#ffffff", "list": [ { "pagePath": "pages/index/index", "iconPath": "static/tabbar/home.png", "selectedIconPath": "static/tabbar/home-active.png", "text": "首页" } ] }

确保pagePath与实际文件路径一致(注意大小写,Windows 下不敏感但上传到微信平台会敏感)。

3.3 接口联调:uni.request()的 baseUrl 必须指向本地 Tomcat,且跨域已预置

utils/request.js中定义了全局请求基地址:

// utils/request.js const BASE_URL = 'http://localhost:8080/wx410' export function request(options) { return uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json' } }) }

这意味着:

  • 你必须先启动 Tomcat(2-run.bat),再打开微信开发者工具
  • 不能用127.0.0.1替代localhost,微信开发者工具对127.0.0.1有 DNS 缓存 bug,会导致request超时
  • 后端web.xml中已配置 CORS 过滤器(<filter-class>org.springframework.web.filter.CorsFilter</filter-class>),无需额外设置

测试方法:在pages/index/index.vueonLoad中临时添加:

uni.request({ url: '/admin/list', // 管理员列表接口 success: (res) => { console.log('后台连通:', res.data) }, fail: (err) => { console.error('请求失败:', err) } })

若控制台输出data: {code: 200, msg: "success", data: [...]},说明前后端通信正常。

3.4 避坑:小程序真机调试的四个「看似正常实则致命」错误

现象原因解决方案
微信开发者工具预览二维码,手机扫码后白屏,控制台无报错manifest.jsonname字段含特殊字符(如wx410外卖点餐系统中的「·」被误粘贴),微信解析失败打开manifest.json,将"name": "wx410外卖点餐系统"改为"name": "wx410"
用户登录后,uni.getStorageSync('token')返回undefined,后续所有请求 401login.vueuni.setStorageSync('token', res.data.token)res.data.token实际为res.data.data.token(后端返回结构嵌套一层)修改login.vue第 42 行:uni.setStorageSync('token', res.data.data.token)
菜品详情页视频不播放,<video>组件显示黑屏pages/food/detail.vuesrc属性绑定的是相对路径../../static/video/demo.mp4,但微信小程序要求src必须为绝对路径或网络 URLsrc改为/static/video/demo.mp4(开头加/
下单成功后,订单列表不更新,需手动下拉刷新pages/order/list.vueonPullDownRefresh未调用this.loadOrders(),且loadOrders()方法内uni.showLoadinguni.hideLoading()onPullDownRefresh内添加this.loadOrders();在loadOrders()finally块中添加uni.hideLoading()

4. 四角色业务流验证:从餐厅上架到用户评价,每一步都踩过真实业务坑

这套系统的价值不在代码量,而在它用最小成本覆盖了外卖场景的核心矛盾:多角色状态协同。管理员审核餐厅资质、餐厅设置营业时间、外卖员抢单时的并发锁、用户支付超时自动关单——这些都不是 CRUD,而是状态机流转。下面用真实操作序列验证闭环。

4.1 餐厅角色:上架菜品必须通过「三级审核」才可见

餐厅账号canyin1/123456登录后,流程如下:

  1. 进入「我的餐厅」→「菜品管理」→「添加菜品」
  2. 填写名称「宫保鸡丁」、价格28、库存100,上传图片(必须选jpg/pngwebp会被uni.uploadFile()拒绝)
  3. 点击「提交」,此时菜品状态为0(待审核)
  4. 管理员登录后台 →「菜品信息管理」→ 找到该菜品 →「审核通过」→ 状态变为1(已上架)

关键细节:t_caipin表中status字段为tinyint(1)0=待审,1=上架,2=下架。小程序端pages/food/list.vuecomputed属性filteredFoods会自动过滤status !== 1的菜品,所以未审核菜品不会出现在首页。

4.2 外卖员角色:抢单不是简单 update,而是带版本号的乐观锁

外卖员waimai1/123456登录后:

  1. 进入「待接单」列表,看到用户刚下的订单(状态0=待接单
  2. 点击「抢单」,触发api/order.js中的takeOrder()方法
  3. 后端OrderController.java执行:
    // 先查当前订单状态 Order order = orderService.findById(id); if (order.getStatus() != 0) { throw new RuntimeException("订单已被抢"); } // 乐观锁更新:只当 version 未变时才更新 int updated = orderService.updateStatusWithVersion(id, 1, order.getVersion()); if (updated == 0) { throw new RuntimeException("抢单失败,请重试"); }
    t_order表中version字段用于防止超卖,这是外卖系统高并发场景的刚需。

4.3 用户角色:支付环节的「双保险」机制

用户下单后,流程为:

  1. pages/order/create.vue调用payOrder()→ 后端生成t_order记录,状态0,并返回payId
  2. 前端调用uni.requestPayment()发起微信支付
  3. 支付成功后,微信服务器异步通知http://localhost:8080/wx410/pay/notify
  4. 后端PayNotifyController.java验证签名,更新订单状态为2=已支付,并触发sendSms()(模拟短信通知餐厅)

血泪经验:若pay/notify接口未正确处理微信回调(如未返回success字符串),微信会每 15 分钟重试 5 次,导致同一订单被多次更新。wx410PayNotifyController.java第 63 行有response.getWriter().write("success"),这是硬性要求。

4.4 管理员角色:订单配送的「人工干预」入口

管理员可强制修改订单状态:

  • 订单状态流转图:0→待接单 → 1→已接单 → 2→配送中 → 3→已完成 → 4→已取消
  • 3→已完成不允许直接操作,必须经2→3(配送中→已完成)
  • 若外卖员误操作,管理员可在「订单配送管理」中点击「重置状态」,将3降为2,再让外卖员重新上报

这个设计避免了「已完成订单被恶意篡改」的风险,是生产环境必备的审计留痕。

4.5 避坑:业务流中断的三个隐蔽断点

断点位置现象根因修复动作
餐厅审核通过后,小程序首页仍不显示新菜品pages/index/index.vueonLoadgetFoods()请求参数status=1,但后端FoodController.list()方法未加@RequestParam(value="status", defaultValue="1") Integer statusFoodController.javalist()方法参数前添加@RequestParam注解
外卖员抢单后,用户端订单状态未变,仍显示「待接单」socket.io未启用,实时推送失效;但wx410实际采用轮询机制,pages/order/detail.vuesetInterval(() => this.getOrder(), 5000)未清除,导致内存泄漏onUnload生命周期中添加clearInterval(this.timer)
用户评价后,餐厅端「订单评价管理」列表为空t_pingjia表的order_id字段为varchar(50),但t_order.idbigint,关联查询时类型不匹配导致LEFT JOIN失效修改t_pingjia.order_id字段类型为bigint,并重建索引

5. 毕设答辩级优化:加一个「订单超时自动关单」功能,让系统真正像生产环境

毕设答辩时,老师最常问:「如果用户下单后 15 分钟不支付,系统怎么处理?」——这正是wx410原始版本缺失的生产级能力。我基于其现有定时任务框架(quartz)补了一个OrderTimeoutJob,5 分钟即可集成,且完全复用原有数据库和日志体系。

5.1 定时任务接入:复用spring-quartz.xml,零侵入添加超时逻辑

src/main/resources/config/spring-quartz.xml中已定义SchedulerFactoryBean,只需新增一个 Job Bean:

<!-- 新增:订单超时检测 Job --> <bean id="orderTimeoutJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="orderService"/> <property name="targetMethod" value="closeTimeoutOrders"/> </bean> <bean id="orderTimeoutTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="orderTimeoutJob"/> <property name="cronExpression" value="0 0/5 * * * ?"/> <!-- 每5分钟执行一次 --> </bean>

orderService.closeTimeoutOrders()方法实现:

// OrderServiceImpl.java @Transactional public void closeTimeoutOrders() { // 查找创建时间超过15分钟且状态为0(待支付)的订单 Date timeoutTime = DateUtils.addMinutes(new Date(), -15); List<Order> timeoutOrders = orderMapper.selectByStatusAndTime(0, timeoutTime); for (Order order : timeoutOrders) { order.setStatus(4); // 4=已取消 order.setUpdateTime(new Date()); orderMapper.updateByPrimaryKey(order); // 记录日志 log.info("自动关闭超时订单: orderId={}, userId={}", order.getId(), order.getUserId()); } }

对应的OrderMapper.xml添加:

<select id="selectByStatusAndTime" resultType="Order"> SELECT * FROM t_order WHERE status = #{status} AND create_time < #{date} </select>

5.2 前端感知:在订单列表增加「剩余支付时间」倒计时

pages/order/list.vue中为待支付订单添加倒计时:

<template v-if="item.status === 0"> <view class="countdown"> 剩余支付时间: {{ formatTime(item.createTime) }} </view> </template>
methods: { formatTime(createTime) { const now = Date.now(); const created = new Date(createTime).getTime(); const diff = Math.floor((now - created) / 1000); // 秒数 const minutes = Math.floor((15 * 60 - diff) / 60); const seconds = (15 * 60 - diff) % 60; return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`; } }

注意:倒计时仅作前端提示,真实关单由后端定时任务执行,避免客户端时间被篡改。

5.3 日志与监控:用logback-spring.xml抓住每一次超时事件

src/main/resources/logback-spring.xml中已配置CONSOLEFILE两种 Appender。在closeTimeoutOrders()方法中添加日志:

log.info("【订单超时】关闭订单 {},原状态 {},用户 {},金额 {}", order.getId(), order.getStatus(), order.getUserId(), order.getTotalPrice());

日志格式为:[2024-03-15 14:22:33] INFO c.w.s.service.impl.OrderServiceImpl - 【订单超时】关闭订单 1001...,答辩时可直接截图展示系统自愈能力。

从那以后我每次给毕设学生搭环境,都强制走一遍「订单超时」全流程:下单 → 不支付 → 等 5 分钟 → 查看后台日志 → 刷新小程序订单列表 → 确认状态变为「已取消」。这比讲一百遍「高并发」都有说服力——系统不是写出来的,是跑出来的。希望帮到你。

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

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

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

立即咨询