☰
校园外卖小程序毕设实战:SSM+MySQL+微信小程序全流程解析
2026/9/26 13:37:36 网站建设 项目流程

简介:校园外卖平台小程序毕业设计资源包,基于微信小程序+SSM+MySQL实现,面向计算机毕设学生或需要快速落地完整项目练手的开发者。项目涵盖管理员后台、商家端与用户端三类角色,支持菜品管理、购买下单、订单领取等核心流程,具备界面清晰、操作简单的特点。资源总计946个文件,压缩包约28.48MB,包含Java后端源码、Vue管理端页面、微信小程序wxml/wxss页面、SQL数据库脚本、论文文档及mp4演示视频,png/svg等图片素材用于前端界面。其中119个vue文件对应后台界面,121个js和37个wxml组成小程序端逻辑与页面,113个java文件构成SSM框架核心业务,2个sql为数据库初始化脚本。已有295人学习下载,整套资料涵盖可运行源码、数据库、毕业论文与视频演示,便于对照学习毕设框架、梳理外卖平台业务逻辑,也能直接作为课程设计或毕业设计的基础模板。

1. 校园外卖小程序这套毕设到底在做什么:三个模块,一条订单链路

拿到这个标题,很多人第一反应是“能不能直接改个名字交了”。我的回答是:能,但别急着改名字。这套校园外卖平台小程序的本质,是一个三端联动的学生项目:微信小程序负责下单入口,SSM 后端处理业务接口,MySQL 存用户、店铺、菜品、订单这些核心数据。它解决的场景很具体:宿舍楼里打开小程序,选好食堂档口,下单后等骑手送到取餐点。适合正在做计算机、软件工程相关毕业设计的同学,也适合想快速搭一套小程序 O2O demo 的从业者。真正卡人的地方不在页面长什么样,而在订单状态的流转、登录态怎么维持、数据库那几张关联表怎么设计。把这三件事吃透,这套源码才真正变成你能讲清楚、能答辩的毕业设计。

2. 用微信小程序+SSM跑通前后端最小闭环:目录结构、请求封装与登录

2.1 前端目录结构和请求封装:不要每个页面都写一遍 wx.request

拿到源码第一步,先把小程序目录结构捋一遍。常见做法是:pages 下放 home、order、shop、user 四个主页面包,utils 放请求封装和格式化工具,components 放商品卡片、数量步进器这类自定义组件。我最先会看 utils/request.js,页面里所有网络请求都应该从它走。源码里大概率已经封装好了 Promise 形式的 wx.request,把 baseUrl、token、错误码统一处理掉,而不是每个页面各写一份 wx.request。

// utils/request.js const BASE_URL = 'http://localhost:8080/api'; const request = (url, method, data = {}) => { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: method, data: data, header: { 'Content-Type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success: (res) => { if (res.statusCode === 200) { resolve(res.data); } else if (res.statusCode === 401) { wx.removeStorageSync('token'); wx.redirectTo({ url: '/pages/login/login' }); reject(new Error('登录已过期')); } else { reject(new Error(res.data.message || '请求失败')); } }, fail: (err) => reject(err) }); }); }; const get = (url, data = {}) => request(url, 'GET', data); const post = (url, data = {}) => request(url, 'POST', data); module.exports = { get, post };

这段代码里最需要注意的是 header 里的 token 字段。小程序没有 Cookie 的概念,后端拿到 token 后要解析出用户 id,再往下走业务逻辑。BASE_URL 在开发阶段用局域网 IP,比如 http://192.168.1.101:8080/api,真机预览时手机和电脑必须在同一网段,否则 connect fail 是必然的。wx.getStorageSync 每次同步读取本地缓存,登录态放这里比放全局 data 更可靠,小程序冷启动后依然能读到。

提示:凡是出现 wx.request 的页面,优先改成调用 request.js 里的 get/post 方法。答辩时老师问“请求怎么封装的”,你能指着这一个文件讲清楚,比在十个页面里翻代码强得多。

2.2 登录与账号体系:用 wx.login 换 openid,别在本地存明文密码

很多同学拿到项目第一件事是把登录页改成账号密码登录,这是把简单问题复杂化。校园外卖这种场景,微信登录是天然的账号体系入口。小程序端调用 wx.login 拿到一个临时 code,把 code 发给后端,后端用 code 向微信接口换 openid,再去 users 表查用户是否存在,不存在就自动注册。这比你自己做注册页省掉一半工作量。

// pages/login/login.js const { post } = require('../../utils/request.js'); Page({ onLogin() { wx.login({ success: async (res) => { if (!res.code) { wx.showToast({ title: '登录失败,请重试', icon: 'none' }); return; } const resp = await post('/login', { code: res.code }); wx.setStorageSync('token', resp.data.token); wx.setStorageSync('userInfo', resp.data.userInfo); wx.switchTab({ url: '/pages/home/home' }); } }); } });

这段代码里的 res.code 是一次性的,几分钟内失效,后端拿到后要立刻请求微信接口换取 openid,整个链路里前端不用关心 openid 是什么。如果源码里后端是拿 code 直接查数据库,说明登录逻辑是模拟的,跑通没问题,但答辩时容易被问穿。换到后端时注意:微信接口返回的 openid 要存在 users 表的 openid 字段里;前端拿到的 token 可以是后端生成的 UUID,也可以直接用 JWT,但千万不要把 openid 明文返回给前端当 token,这是安全评分里最容易扣分的地方。

3. SSM 后端接口怎么落地:从 pom.xml 配置文件到 Controller

3.1 SSM 三件套的角色分配:Spring 管对象、SpringMVC 管路由、MyBatis 管 SQL

SSM 是 Spring + SpringMVC + MyBatis 的缩写,三者在项目里分工非常明确。Spring 的 IoC 容器负责创建 Controller、Service、Mapper 这些对象,你不用手动 new。SpringMVC 负责把 /api/order/create 这种 URL 映射到 Java 方法,同时完成 JSON 参数的自动绑定。MyBatis 负责把 Java 方法和 Mapper XML 里的 SQL 关联起来,查询结果再映射成对象。弄明白这个关系,改代码时就知道去哪找:改接口地址去 Controller,改业务逻辑去 Service,改查询语句去 Mapper XML。

pom.xml 是后端的第一道坎。这套项目最常见的翻车是 Spring 和 JDK 版本打架。JDK 8 配 Spring 5.3 没问题,换到 JDK 17 就要升到 Spring 6 并把 javax 包名全改成 jakarta。源码里如果是 Eclipse 时代的老项目,大概率还在用 javax.servlet,在新一点的 JDK 上编译直接报“程序包不存在”。

<!-- pom.xml 关键依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.20</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency>

这里我特意列的是 SSM 组合,不是 SpringBoot。如果你拿到的是老 pom,mybatis 和 mybatis-spring 的版本要配套,mybatis 3.5.x 配 mybatis-spring 2.x 是常见稳定组合。连接池一般用 druid 或者 c3p0,druid 1.2.x 对 MySQL 8 支持更好。

3.2 最小可用的配置清单:MySQL 8 的驱动和时区不能省

源码包里一般会带 jdbc.properties 和 spring-mvc.xml。你只需要把数据库连接改成当前这台机器能跑的版本。现在新装的 MySQL 基本都是 8.x,驱动类名和 5.x 不一样,时区参数也省不得。

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/campus_food?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456

这段配置里最容易被坑的是 serverTimezone。MySQL 8 默认时区不是中国时区,不加这个参数,连接报错会显示一串乱码,很多人误以为是编码问题,其实只是时区没指定。useSSL=false 是因为本地开发没有证书,真机上如果后端配了 SSL 证书,这里改成 true 不过。

3.3 Controller 接口设计:订单创建的返回结构

后端接口设计得好不好,看订单创建这个接口就知道了。校园外卖的订单创建要同时处理订单主表、订单明细表,还要扣减菜品库存,涉及到一个事务里多张表的写入。Controller 层应该保持轻量,只做参数接收和结果返回,真正的事务控制放在 Service 层。

@RestController @RequestMapping("/api/order") public class OrderController { @Autowired private OrderService orderService; @PostMapping("/create") public Result createOrder(@RequestBody OrderDTO dto, HttpServletRequest request) { Long userId = (Long) request.getAttribute("userId"); Long orderId = orderService.createOrder(dto, userId); return Result.success(orderId); } }

这里有个设计细节:userId 不是前端传的,而是拦截器解析 token 之后塞进 request 的。前端传 userId 很容易被伪造,换个请求参数就能刷别人的账号。OrderDTO 里一般包含 shopId、items 列表、remark 备注,字段名要和前端 JSON 一一对应,否则 SpringMVC 绑定不上,拿到一堆 null,MyBatis 插入时直接报错。

订单状态这一块,推荐用整数枚举而不是字符串。0 待支付、1 待接单、2 制作中、3 待取餐、4 已完成、5 已取消,存 varchar 看起来直观,但排查问题和做统计都不方便。答辩时被问“状态流转怎么控制的”,你回答“用 tinyint 存状态码,Service 层有状态机校验”,这句话的含金量比贴十行 if else 高得多。

4. 校园外卖场景的 MySQL 表设计:6 张核心表与关键索引

4.1 订单主表:不要让订单号用自增 id

校园外卖平台的核心表大概六张:用户表、店铺表、菜品表、购物车表、订单主表、订单明细表。订单主表是整条链路的枢纽,字段设计直接影响后面联调。订单号一定不要用自增 id 直接展示给用户,自增 id 会暴露订单量,而且并发插入时容易被猜。

CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键,内部关联用', order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单号,用户可见,随机生成', user_id BIGINT NOT NULL COMMENT '下单用户', shop_id BIGINT NOT NULL COMMENT '所属店铺', total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '订单总金额', status TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态:0待支付 1待接单 2制作中 3待取餐 4已完成 5已取消', pickup_code VARCHAR(6) DEFAULT NULL COMMENT '取餐号,到店取餐用', remark VARCHAR(255) DEFAULT NULL COMMENT '用户备注', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_user (user_id), INDEX idx_shop (shop_id), INDEX idx_create_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';

字段类型上注意几点:金额用 DECIMAL(10,2),别用 FLOAT,浮点算金额在累加时会有精度误差,这种细节答辩护不了命但很加分。status 用 TINYINT 不用 VARCHAR,配合 Java 枚举映射代码更清晰。create_time 用 DATETIME,默认值直接 CURRENT_TIMESTAMP,不用在 Java 代码里 new Date() 再塞进去。utf8mb4 是必须的,菜品的特殊符号、用户昵称里的 emoji,utf8 存不下,会直接报字符集错误。

4.2 订单明细表:取餐号、状态流转与幂等设计

订单明细表存的是订单里每个菜品的快照。这里有个关键设计原则:菜品名称和价格要冗余到明细表里,不能只存 dish_id 再去关联菜品表。因为店铺改价或下架菜品后,历史订单的展示必须保持下单时的样子。这是电商系统里“快照”概念的简化版,说出来老师就知道你理解业务。

CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT '归属订单', dish_id BIGINT NOT NULL COMMENT '菜品id,仅用于跳转', dish_name VARCHAR(100) NOT NULL COMMENT '下单时菜品名称快照', dish_price DECIMAL(10,2) NOT NULL COMMENT '下单时单价快照', quantity INT NOT NULL DEFAULT 1 COMMENT '购买数量', image VARCHAR(255) DEFAULT NULL COMMENT '菜品图片快照', INDEX idx_order (order_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';

订单明细的设计可以直接回答一个高频答辩题:“前端下单时传的菜品价格,后端要不要信任?”答案是不要。正确的做法是:前端只传 dish_id 和 quantity,后端根据 dish_id 去菜品表查当前价格,重新计算总额,再写进订单。否则学生自己改前端请求把价格改成 0.01,订单总额就是 0.01,这在真实项目里是严重的漏洞。

4.3 索引和外键的取舍:读多写少的场景怎么建索引

校园外卖的查询场景集中在:用户查询自己的订单列表、店铺查询待接单订单、订单详情查明细。对应的索引就是 orders 表的 user_id、shop_id、status 组合。最常用的查询是“某个用户当前进行中的订单”,可以建一个联合索引 (user_id, status)。

外键这块,我的建议是干脆不建物理外键,用逻辑外键。物理外键会导致插入和删除都要校验关联记录,订单表删除时被用户表或明细表引用,直接报错。学生项目里最怕的是删数据删不掉,排查半天发现是外键约束。逻辑外键就是 Java 代码层面保证 user_id 存在,数据库不加 FOREIGN KEY 关键字。良好实践是:表关系图里画出外键关系,实际建表语句不写物理外键,答辩时能解释清楚为什么,反而显得有工程经验。

5. 避坑手册:从“数据库连不上”到“小程序白屏”的 5 次翻车排查

5.1 现象:Access denied for user 与乱码时区同时出现

第一次启动后端,Tomcat 起来后报 Access denied for user 'root'@'localhost',或者连接串里出现一串看不懂的乱码字符,看着像字符集问题。

原因分两层:Access denied 是密码或用户不对,刚装 MySQL 8 默认 root 密码是空的,但 jdbc.properties 里写了个密码;乱码是 serverTimezone 没设置,MySQL 返回的系统时区是英文缩写,Java 端解析不了直接抛异常。

解决:先确认 MySQL 能用 root 空密码还是密码登录,把 jdbc.properties 改对。忽略乱码,直接在 url 末尾加 serverTimezone=Asia/Shanghai。注意时区参数要跟在问号后面,和 useSSL 用 & 连接,漏掉 & 会导致前面参数失效。

5.2 现象:真机预览一进去就 connect fail

开发者工具里接口全部正常,点“真机预览”后小程序打开就白屏,请求全部失败。

原因:真机上 localhost 指向手机自己,不指向电脑。开发者工具默认不校验合法域名,所以本地调试没问题,但手机上微信小程序环境对网络请求的域名有严格限制。另一个可能是手机和电脑不在同一个 Wi-Fi 网段,学校宿舍的网络环境尤其容易出现这种隔离。

解决:把 BASE_URL 从 localhost 改成电脑的局域网 IP,用 ipconfig 查 IPv4 地址。开发者工具里勾选“不校验合法域名”,手机上打开调试模式。如果宿舍的 Wi-Fi 开启了 AP 隔离,手机连不上电脑,就开手机热点让电脑也连上去,再试一次。

5.3 现象:启动后报 Invalid bound statement (not found)

后端能启动,一调用订单接口就报 Invalid bound statement (not found),指向某个 Mapper 方法。

原因:Mapper 接口编译后找不到对应的 XML 文件。SSM 项目里 XML 放在 resources 目录下,但有时候源码把 XML 和 Java 文件放在一起了,构建时 XML 没被复制到 classes 目录。

解决:打开 target/classes 目录,看看 mapper 包下 XML 在不在。不在就说明 pom.xml 里少了 resources 配置,Spring 项目的构建配置里需要把 src/main/java 目录下的 XML 也作为资源打进 classpath,或者干脆把 XML 移到 src/main/resources/mapper 下,并在 spring-mvc.xml 里把 mapper-locations 指过去。

5.4 现象:本地图片正常、部署后图片 404

本地开发上传菜品图片正常,把项目打成 war 包部署到 Linux 服务器后,图片全部 404。

原因:上传的图片存的是本地绝对路径,比如 E:/upload/xxx.jpg,服务器上根本没有 E 盘。或者是存成了相对路径,但没映射静态资源访问,SpringMVC 默认不处理 /upload/ 这种路径。

解决:上传目录改成项目运行目录下的 upload 文件夹,图片 URL 存相对路径,再在 SpringMVC 配置里加一个资源映射,把 /upload/** 映射到实际存放目录。部署时确保启动脚本的当前工作目录和配置里的一致,最简单的方式是用 System.getProperty("user.dir") 拼出绝对路径,避免手工写死。

5.5 现象:订单状态一直停留在“待取餐”

下单、接单都正常,骑手点“确认送达”后,订单状态没有变化,前端刷新又变回原样。

原因:状态更新的 SQL 条件写错了。常见的写法是直接 UPDATE orders SET status = 4 WHERE id = #{orderId},没有校验当前状态是不是 3。前端把状态改成 4 显示了一次,但真正的状态在校验时发现当前状态不是 3,回滚了。

解决:更新语句里带上当前状态条件,UPDATE orders SET status = 4 WHERE id = ? AND status = 3。如果影响行数为 0,说明状态已经变了,返回“状态已更新,请刷新”。这个做法同时解决了并发重复点击的问题,是状态机校验的落地方案。

6. 收尾工程:把源码变成能答辩的毕业设计,先把这 4 件事做掉

6.1 全局改名与初始化数据库的顺序

整个项目跑通之后,先别急着改名字。正确的顺序是:先建库、跑 SQL 脚本、确认系统能完整运行一遍,再动手改包名和标题。全局改名用 IDEA 的全局替换,把 com.example 换成你自己的域名反写,注意替换范围要包含 pom.xml 里的 groupId 和 artifactId。改名后重新启动,你会发现 Mapper 扫描、Spring 注解扫描全部要重新验证一遍。这个阶段最容易出的问题是包路径改了,mybatis 的 typeAliasesPackage 没改,启动直接报错。

6.2 token 过期与用户身份校验:演示时不要翻车

答辩演示最怕的是中途退出登录。把 token 有效期设长一点,比如 7 天,演示过程就不要重新登录。演示前检查一遍服务器时间,如果后端服务器时间比实际时间慢,JWT 的过期时间校验会提前失效。这个小问题我当年翻过一次车,现场 profile 调不出订单列表,老师还以为我系统没做完,后来发现是服务器时区偏了,从此每次演示前先 date 看一眼。

6.3 演示视频的内容顺序与论文里的数据流图

视频演示部分是拍给评阅老师看的,录制顺序固定成一条链路:用户登录、首页浏览店铺、进店加购、确认下单、商家端接单、骑手配送、用户确认收货。每一步画面停留 3 秒以上,状态变化的地方用鼠标圈一下。论文里的数据流图不要画成页面跳转图,要画成三层:微信小程序发起请求、SSM 后端处理逻辑、MySQL 存储数据,这张图是答辩时讲系统架构的底稿。

自己把这套项目完整跑一遍,再按上面的顺序改完,你会发现这套校园外卖小程序已经从“别人的源码”变成了“你能讲清楚来龙去脉的作品”。希望这些实战经验能帮到你,让你少踩几个我当年踩过的坑。

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

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

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

立即咨询