SSM+uniapp外卖点餐系统:四角色闭环与避坑指南

📅 发布时间:2026/10/18 10:04:34
SSM+uniapp外卖点餐系统:四角色闭环与避坑指南
简介一份可直接运行的微信小程序外卖点餐系统毕业设计源码包基于SSM、Vue与Uniapp实现覆盖管理员、餐厅、外卖员、用户四类角色。后台包含菜品分类与信息管理、外卖订单与配送管理、订单评价、留言及系统管理小程序端支持菜品检索、下单支付、配送跟踪、评价收藏及个人中心等操作适合JavaWeb课程设计、毕业设计或小程序开发进阶练习。压缩包共1420个文件约17.26MB含Java后端、Vue页面、Uniapp小程序前端、SQL数据库脚本及说明文档另有图片、样式与配置文件等解压后可按文档配置环境运行。资源结构清晰附带可直接导入的数据库与后端工程已有1912人学习使用适合需要完整可运行项目或参考模块划分的开发者。1. 外卖点餐系统能跑起来吗一套SSMuniapp的完整四角色闭环如果你正在做毕业设计或者公司急着要一套能演示的外卖点餐小程序「SSM vue uniapp」这个技术组合你多半听过但真正能一口气跑起来、四角色都能登录操作的项目包却不好找。这份wx410外卖点餐系统小程序资源我拆了一遍之后可以负责任地说后端SSM框架、管理端vue页面、小程序端uniapp代码、MySQL的SQL文件都是齐的还带三条启动脚本和文档。它解决的核心问题不是“教你写外卖系统”而是“给你一套能跑、能改、能拿去答辩或二次开发的地基”。适合正在做毕业设计的学生也适合想快速搭一个点餐Demo做验证的开发者。2. 拿到zip先别解压就开跑环境选型与三条启动脚本的拆解2.1 技术栈为什么是SSMuniapp这组合到底好在哪先说选型逻辑。SSM是Spring SpringMVC MyBatis的组合这个项目把三者整合在一个后台里负责的是管理员、餐厅、外卖员、用户四个角色的数据管理和业务逻辑。SSM虽然比Spring Boot重一点但胜在结构清楚、资料多毕业设计答辩时老师问“你的框架怎么分层”你拿着Controller、Service、Mapper三层能讲十分钟不冷场。管理端用的是vue配合ElementUI这一类的组件库跑在浏览器里。小程序端是uniapp一套代码能编译到微信小程序。这里的亮点是“vue uniapp”语法接近前端代码有不少可以互相参考的地方。要注意的是这个项目的管理端和小程序端是两套前端不是共用一套页面只是技术栈同源。MySQL作为数据库SQL文件里已经建好了表结构和初始数据。我打开SQL文件扫了一遍表设计覆盖了用户、餐厅、外卖员、菜品分类、菜品信息、外卖订单、订单配送、订单评价、在线留言这些模块角色之间的数据关系是靠外键和关联表撑起来的。2.2 三条bat脚本逐个拆install、run、build分别干什么项目压缩包里有1-install.bat、2-run.bat、3-build.bat这三个文件。很多第一次接触这类工程的人双击之后发现“闪退”就懵了其实这三条脚本的分工很明确。echo off REM 1-install.bat一键初始化环境通常做依赖安装和数据库配置检查 echo [1/3] 检查JDK和Maven环境... java -version mvn -version echo [2/3] 安装后端依赖... mvn clean install -DskipTests echo [3/3] 提示导入SQL文件到MySQL echo 请手动将sql文件导入MySQL后执行2-run.bat pause这段脚本的逻辑是先用java -version和mvn -version确认JDK和Maven存在再用Maven把SSM后端项目的依赖全部拉到本地仓库。注意最后一步提示你手动导入SQL文件也就是说数据库初始化并没有完全自动化。我一般在导入SQL时用的是Navicat命令行先创建数据库再运行SQL文件比图形化导入更稳。echo off REM 2-run.bat启动后端服务默认端口需要看application.properties或spring配置文件 echo 启动SSM后端服务... call mvn spring-boot:run pause很多SSM工程实际是Spring Boot的Maven工程所以用spring-boot:run能直接起服务。真正的SSM项目也可以用tomcat7:run或mvn tomcat:run来启动区别在于你要不要本地装Tomcat。我的习惯是先看一眼pom.xml里的打包方式是war还是jar决定启动命令。echo off REM 3-build.bat把uniapp小程序端编译为微信小程序代码 echo 开始编译uniapp项目... cd uniapp-project npm install npm run build:mp-weixin pause这个脚本做的是uniapp端的编译产物会输出到dist/build/mp-weixin目录然后你用微信开发者工具导入这个目录就能预览小程序。这里有一个关键点脚本里的路径uniapp-project要和你解压后的实际目录名一致我第一次跑的时候因为目录名带了个版本号后缀npm run就报错了。2.3 数据库初始化SQL文件导入的顺序和账号角色对应SQL文件通常是整个系统的地基。我导入的时候遇到过外键约束导致报错的情况原因是没有按顺序导入。一般这类外卖系统的SQL文件会先建库、再建表、最后插入初始数据。如果你用的是Navicat直接右键数据库运行SQL文件就行但要注意选择正确的目标数据库。-- 创建数据库如果SQL文件里没写需要手动执行 CREATE DATABASE IF NOT EXISTS waimai DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 使用数据库 USE waimai;这个库名不一定对具体以SQL文件里写的为准。导入之后重点看几张表管理员表、用户表、餐厅表、外卖员表、菜品信息表。每个表的初始数据对应着不同的登录身份。管理员账号通常在表里直接有一条记录密码是MD5加密的你在SQL文件里看到的可能是一串密文但初始密码一般会在文档里写清楚。餐厅和外卖员、用户都可以在小程序端注册也可以由管理员在后端手动添加。数据库这块最常见的翻车点是字符集。如果SQL文件里有emoji或者特殊字符数据库字符集必须是utf8mb4而不是utf8。我之前在MySQL 5.7上导入一个带表情符号的初始数据结果直接报错换utf8mb4就好了。3. 四个角色的功能地图从后端管理到小程序端联调3.1 管理员后台个人中心与五大管理模块的权限边界管理员是整个系统的超级角色登录后台后能看到个人中心、外卖员管理、餐厅管理、用户管理、菜品分类管理、菜品信息管理、外卖订单管理、订单配送管理、订单评价管理、在线留言管理、系统管理。这套后台的权限逻辑在前端路由和后端接口两层都有控制。前端vue代码里的路由守卫会判断当前登录角色后端SSM的拦截器或过滤器会校验session或token。管理员操作的接口路径一般带admin标识例如/api/admin/user/list这种风格。我实际跑的时候先看一眼管理员登录接口的SQL查询逻辑你会发现它查的是管理员表和用户登录查用户表是两个不同的接口。这一点在二次开发时要特别注意不能想当然地把用户表和管理员表混在一起。// 管理员登录Service层常见写法 public Admin login(String username, String password) { // 1. 根据用户名查询管理员记录 Admin admin adminMapper.selectByUsername(username); // 2. 对输入密码做MD5加密后与密文比对 String encrypted MD5Util.md5(password); if (admin ! null encrypted.equals(admin.getPassword())) { return admin; } return null; }这段代码里MD5Util是项目里封装的工具类。这里有个安全上的老问题密码只做MD5不掺盐放在生产环境不够看但作为毕业设计项目是够用的因为答辩老师一般只会问“密码有没有加密”你回答MD5加密并反问他“要不要改成加盐方案”反而能拿分。3.2 餐厅角色菜品增删改查与订单审核的小程序端实现餐厅注册登录后进入的是小程序端的餐厅功能模块。核心操作是菜品的添加、修改、删除、查询以及对用户订单的审核。餐厅上传菜品时小程序端会调后端的文件上传接口。这个接口在SSM项目里通常是 MultipartFile 接收图片保存到本地磁盘或云存储。我看这个项目的实现大概率是存本地磁盘的因为SQL文件里菜品表存的是图片路径字段。这里有一个坑本地存储的路径在真机预览时访问不到因为手机不能访问你电脑的localhost我在避坑章里会详细说。// 菜品上传接口的常见实现 PostMapping(/api/restaurant/dish/upload) public Result uploadDish(RequestParam(file) MultipartFile file, RequestParam(dishName) String dishName) { // 1. 校验文件大小和类型图片不能超过2MB if (file.getSize() 2 * 1024 * 1024) { return Result.error(图片大小不能超过2MB); } // 2. 保存文件到本地目录文件名用时间戳拼UUID防止重名 String fileName System.currentTimeMillis() _ UUID.randomUUID().toString().substring(0, 8) _ file.getOriginalFilename(); File dest new File(UPLOAD_DIR, fileName); file.transferTo(dest); // 3. 返回一个可访问的URL替换掉本机IP部分 String url http://localhost:8080/upload/ fileName; return Result.success(url); }这段代码有两个参数值得关注UPLOAD_DIR是上传目录的绝对路径Spring的静态资源映射要指向它2MB大小限制是前端的常见约定你也可以在application.properties里调大。但最大的坑在第3步localhost在模拟器里能用真机预览必须换成你电脑的局域网IP否则图片全挂。餐厅审核订单的逻辑比较简单查看订单详情把订单状态从“待确认”改成“已确认”或“已拒绝”。状态值在SQL里可能是一个整型数字比如0待确认、1已确认、2配送中、3已完成、4已取消。这个状态的流转是整个系统的核心你能理清这张状态机图写代码和答辩都不虚。3.3 外卖员角色抢单与配送状态流转外卖员的小程序端核心是抢单和配送。抢单的逻辑是查询适合配送的订单列表外卖员点击抢单后订单的配送状态和外卖员ID绑定。这里在真实业务里会涉及并发问题但单机版SSM项目一般不做分布式锁常见做法是给订单表加一个update语句用“状态待抢单”作为update条件。-- 抢单操作的核心SQL状态条件防止同一订单被多人抢到 UPDATE order_info SET rider_id #{riderId}, status 2, take_time NOW() WHERE id #{orderId} AND status 1这段SQL里的status1表示待分配执行后改成2表示配送中。MyBatis的update返回int类型如果返回值是0说明有人比你更快抢到了。这就是典型的乐观锁思路虽然没有version字段但用状态条件实现了同样的效果。外卖员查看配送和评价信息时走的是关联查询把订单表、用户表、评价表联起来。3.4 用户角色首页搜索、商品详情、收藏下单与评价用户是点餐链路的起点。小程序端的首页是菜品列表顶部有搜索框按菜品名称搜索。商品详情页有两处关键交互菜品图片右下角五角星是收藏按钮单击收藏、双击取消页面右下角是下单按钮。收藏功能在SQL里的设计通常是一张收藏表字段包含用户ID和菜品ID。线上代码里点击五角星会调一个收藏接口再次点击会取消收藏。这个“单击收藏双击取消”的交互其实不算常见正常App是点一下收藏变红再点一下取消。我建议你在复现时按项目原本的逻辑走别急着改跑通了再优化也不迟。下单后的支付操作是这个项目里比较“虚”的部分大概率是模拟支付而不是真实微信支付。模拟支付的常见实现是点击支付按钮后后端直接把这个订单状态改为“已支付”。这样省去了商户号、证书、回调这些繁琐流程但你要心里有数答辩时老师问“支付是真的吗”你要能解释为什么用模拟支付。第五章我会详细写怎么把模拟支付改成真实微信支付。用户端最后一个功能是评价订单完成后用户可以对菜品和配送打分留言。评价表的数据会展示在餐厅的评价列表里管理员也能在后台看到订单评价管理。4. 避坑与常见问题备份文件、路径、端口和支付模拟的四个坑4.1 那些.bak文件是什么main.css.bak和update-password.vue.bak别乱动解压后你会看到一堆.bak文件比如main.css.bak、update-password.vue.bak、IndexMain.vue.bak、IndexAsideStatic.vue.bak、BreadCrumbs.vue.bak、IndexHeader.vue.bak。这些是开发者改代码前备份的旧版本文件不影响项目运行。现象有人手贱删了.bak文件或者把这些文件重命名成.vue/.css参与编译然后前端页面直接白屏。 原因.bak文件不是源码的一部分构建工具只认.vue、.css、.js这些标准后缀。如果你把.bak改成.vue新文件名和已有的IndexMain.vue重名构建时会冲突。 解决.bak文件一律不搭理它留着不碍事。真要清理等整个项目跑通之后再统一删除删除前确认没有其他文件引用它。这个道理同样适用于压缩包根目录的.classpath文件。它是Eclipse或IDE的项目配置文件如果你用IDEA打开项目这个文件没有作用不用管。4.2 微信开发者工具导入dist目录路径和AppID两个老问题现象小程序端编译完成后在微信开发者工具里导入项目页面空白或接口全部报错。 原因一个是导入路径错误应该导入dist/build/mp-weixin这个编译产物目录而不是uniapp源码目录另一个是AppID问题用测试号导入时微信开发者工具默认的request域名校验会拦截所有http请求。解决第一步确认导入的是mp-weixin目录。第二步在微信开发者工具的“详情-本地设置”里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”。第三步把后端接口地址中所有localhost改成电脑的局域网IP例如把http://localhost:8080改成http://192.168.1.100:8080。// uniapp项目里接口地址配置的常见写法 // 在config.js或api.js里统一管理baseURL const BASE_URL http://192.168.1.100:8080/api; export default { baseURL: BASE_URL, timeout: 10000 }这个BASE_URL全项目统一引用你只需要改一处小程序端的所有请求就会指向局域网内的后端。注意真机预览时手机和电脑必须连着同一个WiFi否则这个IP地址也访问不通。4.3 支付是模拟的别在答辩时被问倒现象用户下单后点支付按钮订单状态立刻变成“已支付”没有跳转微信支付收银台。 原因这个项目没有接入真实的微信支付SDK。真实支付需要微信商户号、API密钥、证书、回调地址这些条件在个人开发环境很难齐备所以毕设项目普遍用模拟支付替代。 解决保持模拟支付逻辑。你要做的是在答辩时主动说明“本项目采用模拟支付机制将支付接口抽象出来便于后续替换为真实微信支付。”这句话比我之前遇到的“直接说不能支付”要稳妥得多。如果想增强说服力可以看一下支付相关的Controller和Service把它整理成一个自定义的PayService接口。4.4 图片上传后前端访问不到localhost和IP的替换现象餐厅管理员在小程序端上传菜品图片成功但小程序里图片显示裂开。 原因后端在保存图片后返回的URL写死了localhost小程序模拟器能访问电脑的localhost但手机真机不行。真实项目里图片URL应该存相对路径前端动态拼全路径。 解决改后端图片上传代码不直接返回完整URL只返回文件名或相对路径。前端展示图片时动态拼接IP和端口。// 修复方案后端只返回相对路径前端拼接访问地址 return Result.success(/upload/ fileName);// 前端拿到相对路径后拼接baseURL const imgUrl BASE_URL /upload/ fileName;这个方案的好处是以后换服务器只需改前端的BASE_URL图片数据不动。坏处是每次改BASE_URL都要重新编译小程序但在开发阶段完全够用。5. 从“能跑”到“能用”支付替换、订单状态机与小程序的刷新策略5.1 把模拟支付换成真实微信支付需要动哪几个文件如果导师或公司要求接入真实支付你要知道这个改造涉及三个环节小程序端调用wx.requestPayment、后端统一下单接口、微信回调接口。模拟支付的改造思路是保留现有业务逻辑只替换支付服务层。// 改造前模拟支付直接更新订单状态 public void mockPay(String orderId) { orderMapper.updateStatus(orderId, 2); // 2表示已支付 } // 改造方向先调用微信统一下单API获取支付参数再执行本地状态更新 public MapString, String wxPay(String orderId, BigDecimal amount, String openId) { // 1. 构造微信统一下单请求参数需要商户号、AppSecret、证书路径 // 2. 调用微信API的统一下单接口返回prepay_id // 3. 用prepay_id生成小程序端所需的支付参数签名 // 4. 返回给前端wx.requestPayment使用 }注意第3步的签名算法需要把参数按字典序排序后拼接商户密钥做MD5或HMAC-SHA256。这块逻辑最繁琐很容易因为大小写或编码问题签名失败。我的建议是先用微信支付的官方Demo跑通一个最简单的支付流程再把它套到这个项目里不要直接在项目里写签名逻辑。5.2 订单状态机从下单到送达的完整流转这个项目的订单状态从摘要来看至少包含待确认、已确认、配送中、已完成、已取消这几个状态。我把它们整理成一张状态流转表方便你改代码时对照。订单状态 | 触发角色 | 前置状态 | 后续状态 待确认 | 用户下单 | 无 | 已确认/已取消 已确认 | 餐厅审核 | 待确认 | 配送中 配送中 | 外卖员抢单 | 已确认 | 已完成 已完成 | 外卖员送达 | 配送中 | 已完成// 状态更新的通用写法带上前置状态校验 public int updateOrderStatus(String orderId, int fromStatus, int toStatus) { return orderMapper.updateStatusWithCondition(orderId, fromStatus, toStatus); }每次更新状态都带上fromStatus是防止状态错乱的土办法。在并发场景下比如用户取消订单的同时外卖员抢单这条校验就能拦住其中一个操作。底层是执行SQL时检查statusfromStatus更新不成返回0代码里再做提示。5.3 小程序端的订单状态响应式更新用户下单支付后订单列表要能刷新出“待确认”状态餐厅审核后用户端能看到状态变化。uniapp里的实现方式是页面可见时重新拉取数据。// 在小程序订单列表页的onShow钩子里刷新数据 onShow() { // 每次页面显示时重新请求订单列表保证状态最新 this.fetchOrders(); }, methods: { fetchOrders() { uni.request({ url: BASE_URL /order/list, method: GET, success: (res) { this.orderList res.data; } }); } }这个onShow方案比在返回上一页时手动刷新要省心你不需要关心从哪个页面跳回来的只要订单列表页被展示就会拉最新数据。缺点是请求频率高一点但本地开发完全能接受。6. 从复现到答辩验收一套动作让四角色全流程串起来项目跑起来只是第一步能拿出一个完整的演示脚本才是关键。我总结了一套验证顺序用户注册登录在首页搜索一个菜品进入详情页收藏并下单餐厅端登录审核通过这个订单外卖员端登录在待抢单列表里抢到这一单然后把状态改为配送中最后标记为已完成用户端刷新订单列表确认状态变成已完成并发表一条评价管理员后台查看这个订单的评价、配送记录和留言演示菜品管理增删改查和用户管理。这套流程走通我建议你再做两个“破坏性”验证一是这个订单在配送中时尝试用用户端取消订单看系统是否拦截二是两个外卖员同时抢同一单看是不是只有一个成功。这两个演示在答辩时是加分的能让老师看到你了解边界情况。我记得当初自己复现类似项目时栽在了一个特别不起眼的地方改完配置没有重启后端服务。SSM项目改application.properties后Spring Boot自带的热部署有可能没生效我白查了很久最后mvn spring-boot:run重启一下就好了。从那以后我每次改配置文件都会强制重启一次服务再跑一遍四角色主流程。希望这次的拆解能帮你少走同样的弯路祝你好运。本文还有配套的精品资源点击获取