
简介校园外卖平台小程序毕业设计资源包基于微信小程序SSMMySQL实现面向计算机毕设学生或需要快速落地完整项目练手的开发者。项目涵盖管理员后台、商家端与用户端三类角色支持菜品管理、购买下单、订单领取等核心流程具备界面清晰、操作简单的特点。资源总计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 配置文件到 Controller3.1 SSM 三件套的角色分配Spring 管对象、SpringMVC 管路由、MyBatis 管 SQLSSM 是 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 groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.20/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.10/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.29/version /dependency这里我特意列的是 SSM 组合不是 SpringBoot。如果你拿到的是老 pommybatis 和 mybatis-spring 的版本要配套mybatis 3.5.x 配 mybatis-spring 2.x 是常见稳定组合。连接池一般用 druid 或者 c3p0druid 1.2.x 对 MySQL 8 支持更好。3.2 最小可用的配置清单MySQL 8 的驱动和时区不能省源码包里一般会带 jdbc.properties 和 spring-mvc.xml。你只需要把数据库连接改成当前这台机器能跑的版本。现在新装的 MySQL 基本都是 8.x驱动类名和 5.x 不一样时区参数也省不得。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/campus_food?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456这段配置里最容易被坑的是 serverTimezone。MySQL 8 默认时区不是中国时区不加这个参数连接报错会显示一串乱码很多人误以为是编码问题其实只是时区没指定。useSSLfalse 是因为本地开发没有证书真机上如果后端配了 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 绑定不上拿到一堆 nullMyBatis 插入时直接报错。订单状态这一块推荐用整数枚举而不是字符串。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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;字段类型上注意几点金额用 DECIMAL(10,2)别用 FLOAT浮点算金额在累加时会有精度误差这种细节答辩护不了命但很加分。status 用 TINYINT 不用 VARCHAR配合 Java 枚举映射代码更清晰。create_time 用 DATETIME默认值直接 CURRENT_TIMESTAMP不用在 Java 代码里 new Date() 再塞进去。utf8mb4 是必须的菜品的特殊符号、用户昵称里的 emojiutf8 存不下会直接报字符集错误。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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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 rootlocalhost或者连接串里出现一串看不懂的乱码字符看着像字符集问题。原因分两层Access denied 是密码或用户不对刚装 MySQL 8 默认 root 密码是空的但 jdbc.properties 里写了个密码乱码是 serverTimezone 没设置MySQL 返回的系统时区是英文缩写Java 端解析不了直接抛异常。解决先确认 MySQL 能用 root 空密码还是密码登录把 jdbc.properties 改对。忽略乱码直接在 url 末尾加 serverTimezoneAsia/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 存储数据这张图是答辩时讲系统架构的底稿。自己把这套项目完整跑一遍再按上面的顺序改完你会发现这套校园外卖小程序已经从“别人的源码”变成了“你能讲清楚来龙去脉的作品”。希望这些实战经验能帮到你让你少踩几个我当年踩过的坑。本文还有配套的精品资源点击获取