简介:本资源为基于微信小程序的外卖点餐系统毕业设计完整资料包,面向计算机相关专业学生及Java全栈初学者,解决传统餐饮点餐效率低、信息不透明等问题。系统前端采用微信小程序与Vue框架,后端基于SpringBoot,数据库选用MySQL,涵盖用户浏览美食、下单购买、购物车与订单查询、优惠券查看,以及商家美食信息管理、订单处理、优惠券配置和管理员分类审核、订单统计等模块,可作为课程设计或毕业设计的参考方案。资源包为zip格式,压缩包约33.44MB,内含源码、数据库脚本与运行说明等文件,便于快速部署与二次开发。目前已有84人学习下载,适合需要完整项目实战、理解前后端数据交互与业务逻辑处理的学习者参考借鉴。
1. 从一份能跑起来的外卖小程序源码说起
很多做毕业设计或接私活的朋友都遇到过这种局面:需求文档写得天花乱坠,真到动手时卡在环境搭建、接口联调、数据库导入这些琐事上,一周过去连登录页都没跑通。这份「基于微信小程序的外卖点餐系统」源码包,恰好是冲着这个痛点来的——它把用户端、商家端、管理员端三套角色的完整业务链路都做完了,前端是微信小程序加 Vue 技术栈,后端 SpringBoot,数据落 MySQL,附带数据库脚本和运行说明。拿到手之后,你不需要从零设计表结构,也不用纠结订单状态怎么流转,直接导入、改配置、跑起来,就能看到一个能下单、能管理菜品、能核销优惠券的完整系统。它适合三类人:赶毕业设计进度的学生、想拿一个成熟业务骨架做二次开发的开发者、以及需要快速验证餐饮类产品逻辑的创业者。下面我按「先看懂架构、再动手跑通、最后避开坑」的顺序,把这份资源拆开讲。
2. 三端角色与 SpringBoot 分层架构:先看懂再动手
2.1 用户、商家、管理员三端到底各管什么
外卖点餐系统看着简单,真正落地时最容易乱的就是权限边界。这份源码把角色拆得很清楚,你导入数据库后能看到对应的角色表和权限字段。
用户端跑在微信小程序里,核心动作是浏览美食分类、把菜品加进购物车、提交订单、查看历史订单和优惠券。这里有个细节值得注意:购物车数据在小程序端是本地缓存加服务端同步双写的,也就是说用户退出小程序再进来,购物车不会丢,这个设计比纯本地存储靠谱得多。
商家端负责的是美食信息维护、优惠券配置和订单处理。商家只能看到自己店铺的数据,这个隔离是靠后端在查询时拼shop_id条件实现的,不是靠前端隐藏菜单,安全性上更稳。
管理员端权限最大,管美食分类、审核商家提交的信息、管理全平台优惠券和订单统计。三端共用一套 SpringBoot 后端,通过角色字段做接口级鉴权。
| 角色 | 核心功能 | 数据可见范围 |
|---|---|---|
| 用户 | 浏览、下单、购物车、订单查询、优惠券 | 仅本人数据 |
| 商家 | 美食管理、优惠券配置、订单处理 | 仅本店铺数据 |
| 管理员 | 分类管理、信息审核、订单统计 | 全平台数据 |
2.2 SpringBoot 后端的分层与接口组织
后端用的是典型的 Controller-Service-Mapper 三层结构,配合 MyBatis 做数据库映射。你打开源码的src/main/java目录,能看到按模块拆分的包:controller负责接收小程序请求,service写业务逻辑,mapper对应 SQL 操作,entity是数据库实体类。
这种分层的好处是改需求时定位快。比如要加一个「满减优惠」规则,你只需要在CouponService里改计算逻辑,Controller 层几乎不用动。常见做法是把订单金额计算、优惠券抵扣这些容易出错的逻辑单独抽一个工具类,这份源码也是这么处理的。
接口返回统一用了一个Result封装类,包含code、msg、data三个字段。小程序端拿到code判断成功失败,data里才是真正的业务数据。这个约定很重要,后面你调接口时如果发现数据取不到,先看code是不是 200。
// 典型的 Controller 写法,接收小程序端请求 @RestController @RequestMapping("/api/order") public class OrderController { @Autowired private OrderService orderService; // 用户提交订单,参数从请求体里取 @PostMapping("/submit") public Result submit(@RequestBody OrderDTO orderDTO) { // 校验购物车是否为空、收货信息是否完整 if (orderDTO.getItems() == null || orderDTO.getItems().isEmpty()) { return Result.error("购物车为空"); } // 调用 Service 层处理下单逻辑,返回订单号 String orderNo = orderService.createOrder(orderDTO); return Result.success(orderNo); } }这段代码里@RequestBody表示参数以 JSON 格式传入,OrderDTO是数据传输对象,里面装了菜品列表、用户 ID、收货地址等。orderService.createOrder才是真正干活的地方,它会做库存扣减、优惠券核销、订单号生成这些操作。你二次开发时如果要加「预约下单」功能,就在这个 DTO 里加个时间字段,然后在 Service 里判断即可。
2.3 数据库表结构与关键字段设计
数据库脚本在源码包的sql目录下,导入 MySQL 后大概有十几张表。核心表包括用户表、商家表、美食表、分类表、购物车表、订单表、订单详情表、优惠券表。
订单表的设计有个地方值得留意:订单主表和订单详情表是分开的。主表存订单号、用户 ID、总金额、订单状态、创建时间;详情表存每一道菜的名称、单价、数量。这样设计的好处是查订单列表时不用联表查菜品,速度快;查订单详情时再用订单号去详情表捞数据。
订单状态字段用的是数字枚举,常见做法是 0 待付款、1 已付款、2 配送中、3 已完成、4 已取消。你在改状态流转逻辑时,一定要在 Service 层加状态校验,防止出现「已取消的订单又被改成已完成」这种脏数据。
-- 订单主表关键字段 CREATE TABLE `orders` ( `id` int NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '订单编号', `user_id` int NOT NULL COMMENT '下单用户', `total_amount` decimal(10,2) DEFAULT '0.00' COMMENT '订单总金额', `status` tinyint DEFAULT '0' COMMENT '0待付款 1已付款 2配送中 3已完成 4已取消', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;order_no加了唯一索引,防止重复下单生成相同订单号。total_amount用decimal而不是float,这是金额字段的基本要求,避免浮点精度丢失。utf8mb4字符集能存 emoji,用户昵称里带表情也不会报错。
3. 从导入数据库到小程序联调:完整跑通流程
3.1 环境准备与数据库导入
动手之前先把环境对齐。后端需要 JDK 1.8 或以上、Maven 3.6+、MySQL 5.7 或 8.0。小程序端需要微信开发者工具,Vue 部分如果单独跑需要 Node.js 14+。
第一步是建库导数据。用 Navicat 或者命令行都行,我一般用命令行,快且不容易出错。
# 登录 MySQL 并创建数据库 mysql -u root -p CREATE DATABASE takeout DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE takeout; # 导入源码包里的 sql 文件,路径按你实际存放位置改 source /path/to/takeout.sql; # 确认表都建好了 SHOW TABLES;导入完成后执行SHOW TABLES应该能看到十几张表。如果报错说字符集不支持,检查 MySQL 版本,5.7 以下对utf8mb4支持不完整,建议升级。导入后先别急着跑后端,用SELECT * FROM user LIMIT 5看看有没有初始账号数据,通常源码会带一个管理员账号,密码是加密存储的。
3.2 后端配置修改与启动
数据库通了之后,改后端配置文件。找到src/main/resources/application.yml,把数据库连接信息换成你自己的。
spring: datasource: url: jdbc:mysql://localhost:3306/takeout?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver # 文件上传路径,商家上传菜品图片会存到这里 servlet: multipart: max-file-size: 10MBserverTimezone=Asia/Shanghai这个参数必须加,否则插入订单时间会差 8 小时,这个坑我见过太多次。max-file-size控制图片上传大小,默认 1MB 传菜品图容易失败,改成 10MB 比较稳妥。
配置改完用 Maven 打包启动:
# 在项目根目录执行,跳过测试加快速度 mvn clean package -DskipTests # 启动 jar 包 java -jar target/takeout-0.0.1-SNAPSHOT.jar看到控制台输出Started Application in x seconds就说明后端起来了。如果启动报Table 'takeout.xxx' doesn't exist,说明数据库导入不完整,重新导一次。如果报端口占用,改application.yml里的server.port。
3.3 小程序端配置与接口联调
小程序端用微信开发者工具打开源码里的miniprogram目录。第一件事是改请求地址,找到utils/request.js或者config.js,把baseUrl改成你后端跑起来的地址。
// 小程序端请求封装,baseUrl 指向本地后端 const baseUrl = 'http://localhost:8080/api'; function request(options) { return new Promise((resolve, reject) => { wx.request({ url: baseUrl + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json' }, success: (res) => { // 后端统一返回 code/msg/data,这里做拦截 if (res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data.msg); } }, fail: reject }); }); }这里code === 200的判断和后端Result类是对应的。联调时如果小程序一直提示网络错误,先在开发者工具里勾选「不校验合法域名」,因为本地localhost不在微信白名单里。真机调试时要把localhost换成电脑的局域网 IP,手机和电脑连同一个 WiFi。
联调顺序建议是:先测登录接口,再测美食列表,最后测下单。下单接口涉及库存和优惠券,最容易出问题,放到最后调。
4. 订单状态流转与优惠券核销:业务逻辑里的硬骨头
4.1 订单状态机与并发下单处理
订单状态流转是外卖系统里最容易写乱的地方。这份源码把状态变更集中在OrderService里,每次改状态前先查当前状态是否允许变更。
常见做法是定义一个状态流转表,比如待付款只能变成已付款或已取消,已付款只能变成配送中,配送中只能变成已完成。你在 Service 里加一个checkStatusTransition方法,不满足条件直接抛异常。
并发下单是另一个坑。两个用户同时抢最后一份菜品,如果不在数据库层面加锁,会出现超卖。源码里用的是乐观锁方案,在美食表加一个version字段,更新库存时带上版本号。
// 扣减库存的乐观锁写法 @Update("UPDATE food SET stock = stock - #{num}, version = version + 1 " + "WHERE id = #{foodId} AND stock >= #{num} AND version = #{version}") int reduceStock(@Param("foodId") int foodId, @Param("num") int num, @Param("version") int version);如果返回的影响行数是 0,说明库存不足或者版本号对不上,Service 层要捕获这个情况并提示用户「手慢了,菜品已售罄」。这个方案比synchronized性能好,适合外卖这种读多写少的场景。
4.2 优惠券的领取、绑定与核销
优惠券逻辑分三步:领取、下单时绑定、支付后核销。源码里优惠券表有个status字段,0 未使用、1 已使用、2 已过期。
用户领券时插入一条记录,下单时查询该用户所有未使用的券,按满减条件筛选出可用的,让用户选一张。这里要注意:优惠券的user_id必须和下单用户一致,否则会出现 A 用户用了 B 用户券的漏洞。
核销动作放在支付回调里。支付成功后,把优惠券状态改成已使用,同时记录使用的订单号。如果支付失败或者订单取消,要把券退回去,状态改回未使用。这个「退回」逻辑很多源码会漏掉,你二次开发时记得补上。
// 核销优惠券,支付成功后调用 public void useCoupon(int couponId, String orderNo) { Coupon coupon = couponMapper.selectById(couponId); // 校验券是否属于当前用户、是否未使用 if (coupon == null || coupon.getStatus() != 0) { throw new BusinessException("优惠券不可用"); } coupon.setStatus(1); coupon.setUseOrderNo(orderNo); couponMapper.updateById(coupon); }BusinessException是自定义异常,配合全局异常处理器返回统一错误信息。useOrderNo字段记录券用在哪一单,方便后续对账和退款时回退。
4.3 购物车与订单的数据一致性
购物车在小程序端有本地缓存,用户点「去结算」时才把数据提交到后端生成订单。这里有个一致性问题:用户在小程序里改了购物车数量,但本地缓存没同步到服务端,结算时金额对不上。
源码的处理方式是结算前先调一个「同步购物车」接口,把本地数据推给后端,后端以服务端数据为准计算金额。这样即使本地缓存被篡改,金额也不会错。
你测试时可以故意在小程序里把某道菜数量改成 999,然后点结算,看后端返回的金额是不是按实际库存和价格算的。如果直接用了本地传过来的金额,那就是个安全漏洞,需要改。
5. 避坑与排查:那些让我熬夜的报错
5.1 小程序请求后端一直 404
现象是小程序端所有接口都返回 404,但浏览器直接访问后端地址是通的。原因通常是baseUrl配错了,比如多写了一个/api或者少写了。解决方法是打开微信开发者工具的 Network 面板,看实际请求的完整 URL,和后端 Controller 上的@RequestMapping路径逐段比对。另外注意小程序请求默认走 HTTPS,本地调试要在开发者工具里关掉域名校验。
5.2 数据库中文乱码
现象是菜品名称在数据库里显示正常,但小程序端拿到的是问号。原因是数据库连接 URL 没加characterEncoding=utf8,或者表创建时用了latin1字符集。解决方法是检查application.yml里的连接串,确保有useUnicode=true&characterEncoding=utf8,同时用SHOW CREATE TABLE food确认表的字符集是utf8mb4。
5.3 订单金额出现 0.01 元误差
现象是用户结算金额和后台统计金额差几分钱。原因是用了float或double存金额,浮点运算有精度损失。解决方法是把所有金额字段改成decimal(10,2),Java 里用BigDecimal做加减乘除,并且指定保留两位小数、四舍五入模式。
5.4 商家上传菜品图片失败
现象是商家端选完图片点保存,后端报MaxUploadSizeExceededException。原因是 SpringBoot 默认上传限制是 1MB,手机拍的菜品图轻松超过。解决方法是在application.yml里把spring.servlet.multipart.max-file-size和max-request-size都调到 10MB 或更大,同时检查服务器磁盘空间。
5.5 优惠券领取后不显示
现象是用户点了领取,提示成功,但优惠券列表里没有。原因是领取接口写入了数据库,但列表查询接口的user_id条件写错了,比如用了商家 ID 去查。解决方法是打开后端日志,看列表查询实际执行的 SQL 和参数,确认user_id是不是当前登录用户。另外检查优惠券的status字段,如果领取时默认写成了 1(已使用),列表按未使用筛选自然查不到。
6. 二次开发进阶:把外卖系统改成校园食堂订餐
这份源码的骨架足够干净,改造成校园食堂订餐系统只需要动三个地方。第一个是角色模型,食堂场景下商家就是窗口,一个食堂有多个窗口,你需要在商家表加一个canteen_id字段,把窗口归属到食堂下面。第二个是取餐方式,外卖是配送到地址,食堂是到窗口自取,订单表要加一个pickup_time字段记录预约取餐时间,前端下单页把地址选择换成时间选择。第三个是支付方式,校园场景常用余额支付,你可以在用户表加balance字段,下单时先扣余额再走微信支付兜底。
改造时建议先跑通一条最小链路:用户选窗口、选菜品、预约时间、余额支付、生成取餐码。这条链路通了,再补优惠券和统计报表。数据库改动用ALTER TABLE增量执行,别直接删表重建,否则测试数据全丢。
验证改造是否成功,我一般用三个检查点:一是并发下单同一窗口同一时段,看会不会超卖;二是取餐码生成后重复扫码,看会不会重复核销;三是余额扣减和订单金额是否一致,用SELECT对账。这三个点过了,基本就能交付。
从那以后我每次拿到一份新源码,都强制先跑通「登录-列表-下单」这条最短路径,再去看架构和扩展点。顺序反了,很容易陷在配置里出不来。希望这份拆解能帮你少走点弯路,源码包里的运行说明写得还算清楚,配合上面的步骤应该能顺利跑起来。
本文还有配套的精品资源,点击获取