校园外卖小程序毕业设计:SSM+MySql全栈实战与避坑指南

📅 发布时间:2026/10/6 2:55:33
校园外卖小程序毕业设计:SSM+MySql全栈实战与避坑指南
简介这是一套面向高校计算机专业毕业设计的校园外卖平台小程序完整项目基于微信小程序、SSM框架与MySQL数据库开发适合正在准备毕设或需要全栈实战练习的学生参考。资源包共946个文件约28.48MB涵盖113个Java后端源码、119个Vue管理端组件、121个JS脚本、37个WXML与38个WXSS小程序页面文件以及2个SQL数据库脚本、2个MP4演示视频和毕业论文等文档前后端与数据库结构完整。系统区分管理员、用户、商家三种角色管理员负责用户、商家、菜品分类、菜品信息、购买与订单领取等管理商家可维护菜品并查看订单用户在小程序端浏览、购买菜品并查询订单。已有295人学习下载读者可据此获得可直接运行的源码、数据库脚本、论文参考与操作演示便于快速理解SSM与微信小程序的整合思路完成从环境搭建到功能调试的完整实践。1. 校园外卖平台小程序一套能跑通的毕业设计到底长什么样每年到了三四月计算机专业的群里就开始刷屏「校园外卖平台小程序-毕业设计基于微信小程序SSMMySql开发源码数据库毕业论文视频演示」这类标题。点进去一看有的只有几张截图有的源码跑起来直接报 500还有的数据库表结构跟论文里写的完全对不上。我帮学弟学妹改过不下十套这类项目最深的感受是能不能跑通比功能多不多重要一百倍。这套技术栈的组合其实非常经典微信小程序做用户端SSMSpring SpringMVC MyBatis做后端服务MySql 存数据。它解决的核心问题是——让学生在一个学期内独立完成一个「有真实业务闭环」的系统用户浏览菜品、下单、支付模拟、商家接单、骑手配送、后台管理。适合的人群很明确正在做毕业设计、需要一套完整可演示系统的本科生以及想拿一个真实项目练手 SSM 和微信小程序的初学者。但我要先泼一盆冷水网上流传的很多「源码论文视频」打包资源代码质量参差不齐直接拿来答辩大概率翻车。真正靠谱的做法是——把源码当成参考实现自己把关键链路重写一遍。下面我就按「环境怎么搭 → 后端怎么写 → 小程序怎么接 → 坑在哪」的顺序把这套东西拆开讲透。2. 环境搭建JDK、Tomcat、MySql 三件套的版本对齐2.1 为什么版本选错后面全是玄学SSM 项目最怕的就是版本冲突。我见过太多人 JDK 装了 17结果 Spring 4.x 的cglib直接抛InaccessibleObjectException也见过 MySql 装了 8.0但mysql-connector-java用的是 5.1.x 的驱动连接时一直报Unknown system variable query_cache_size。这些问题的根源不是代码写错了而是版本矩阵没对齐。对于毕业设计这种「求稳不求新」的场景我一般推荐下面这套组合实测在 Windows 10/11 和 macOS 上都能跑通组件推荐版本说明JDK1.88u301 以上SSM 生态最稳的版本别碰 11Tomcat8.5.x9.x 也能用但 8.5 的日志更友好MySql5.7.44 或 8.0.285.7 兼容性最好8.0 注意驱动和时区Maven3.6.33.8 对 http 仓库限制多容易卡依赖IDEA2021.3 或更高社区版够用注意配置 Tomcat 运行提示如果你用的是 MySql 8.0连接 URL 必须加?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue否则大概率连不上。2.2 从零建库校园外卖的六张核心表很多源码包里给的.sql文件要么缺字段要么外键乱设。我建议你按业务重新理一遍最少需要这六张表user用户、merchant商家、dish菜品、order订单、order_detail订单明细、address收货地址。下面是我常用的建表语句字段类型和索引都调过-- 用户表微信 openid 是核心不要用自增 id 做登录态 CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信唯一标识, nickname VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表status 用 tinyint 而不是 varchar方便索引 CREATE TABLE order ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, merchant_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2配送中 3已完成 4已取消, address_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_status (user_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建完表后往merchant和dish里塞几条测试数据别空着库去调接口——空结果集会让很多前端逻辑看起来「像是坏了」其实是没数据。2.3 SSM 整合三个配置文件别写反SSM 整合的核心就是三个 XMLapplicationContext.xmlSpring 容器、spring-mvc.xmlMVC 配置、mybatis-config.xmlMyBatis 设置。常见翻车点是包扫描路径写错导致 Service 注入不进去启动就报NoSuchBeanDefinitionException。我一般这样组织!-- applicationContext.xml 关键片段 -- context:component-scan base-packagecom.campus.service/ bean iddataSource classorg.apache.commons.dbcp2.BasicDataSource property nameurl valuejdbc:mysql://localhost:3306/campus_food?useSSLfalseamp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword value你的密码/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.campus.mapper/ /bean注意component-scan只扫 ServiceController 交给spring-mvc.xml扫否则事务注解会失效——这是最隐蔽的坑之一后面避坑章节还会展开。3. 后端接口从登录到下单的四个关键接口3.1 微信登录code 换 openid 的正确姿势小程序的登录流程是前端调wx.login()拿到临时code传给后端后端拿codeappidsecret去微信接口换openid和session_key。很多源码为了省事直接把openid写死在前端这是答辩时最容易被问倒的地方。后端接口我一般这样写PostMapping(/login) ResponseBody public Result login(RequestBody LoginDTO dto) { // 1. 用 code 换 openid真实项目应调微信接口毕设可先模拟 String openid wechatService.getOpenid(dto.getCode()); // 2. 查库没有就注册 User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(dto.getNickname()); userMapper.insert(user); } // 3. 生成 token毕设用 UUID 即可生产环境用 JWT String token UUID.randomUUID().toString(); redisTemplate.opsForValue().set(token: token, user.getId(), 7, TimeUnit.DAYS); return Result.success(new LoginVO(token, user)); }参数说明code是小程序端wx.login返回的五分钟内有效且只能用一次openid是用户在本小程序内的唯一标识不要把它当全局用户 ID换个小程序就变了。token我习惯存 Redis 并设 7 天过期毕设如果不想装 Redis用ConcurrentHashMap也能凑合但重启就丢。3.2 菜品列表与分页别在 SQL 里写死 limit校园外卖的菜品列表通常要按商家筛选、按销量排序、还要分页。新手最容易犯的错是SELECT * FROM dish LIMIT 0,10写死翻页就废了。正确做法是用 MyBatis 的RowBounds或者 PageHelper 插件。// Service 层 public PageInfoDish listByMerchant(Integer merchantId, int page, int size) { PageHelper.startPage(page, size); ListDish list dishMapper.selectByMerchant(merchantId); return new PageInfo(list); }!-- DishMapper.xml -- select idselectByMerchant resultTypecom.campus.entity.Dish SELECT id, name, price, image, sales, stock FROM dish WHERE merchant_id #{merchantId} AND status 1 ORDER BY sales DESC /select逻辑说明PageHelper.startPage必须紧跟在查询方法前一行中间不能插别的数据库操作否则分页会串到别的查询上。参数page从 1 开始size建议不超过 20小程序一屏放不下太多。status 1表示上架下架的菜不返回给用户端。3.3 下单接口事务和库存扣减是重灾区下单是整个系统最复杂的接口涉及订单主表、明细表、库存扣减、地址校验。我见过最离谱的源码是「先插订单再循环插明细最后扣库存」中间任何一步失败都不回滚导致订单和库存对不上。正确做法是用Transactional包住整个方法并且先扣库存再插订单扣减时用UPDATE ... WHERE stock num这种带条件的语句靠数据库行锁保证不超卖Transactional(rollbackFor Exception.class) public Result createOrder(OrderDTO dto, Integer userId) { // 1. 校验地址归属 Address addr addressMapper.selectByIdAndUser(dto.getAddressId(), userId); if (addr null) return Result.error(地址无效); // 2. 扣库存带条件返回影响行数 for (OrderItem item : dto.getItems()) { int rows dishMapper.reduceStock(item.getDishId(), item.getNum()); if (rows 0) throw new RuntimeException(库存不足 item.getDishId()); } // 3. 插订单主表 Order order new Order(); order.setUserId(userId); order.setTotalPrice(dto.getTotalPrice()); order.setStatus(0); orderMapper.insert(order); // 4. 插明细 for (OrderItem item : dto.getItems()) { item.setOrderId(order.getId()); orderDetailMapper.insert(item); } return Result.success(order.getId()); }参数说明rollbackFor Exception.class必须显式写否则遇到受检异常不回滚reduceStock的 SQL 是UPDATE dish SET stock stock - #{num} WHERE id #{id} AND stock #{num}返回 0 就说明库存不够直接抛异常触发回滚。3.4 订单状态流转状态机比 if-else 靠谱订单状态从 0 到 4中间有支付、接单、配送、完成、取消。很多源码用一堆if (status 0) ... else if (status 1)硬编码改一个状态就牵一发动全身。我建议用一个简单的状态机表或者枚举来约束合法流转public enum OrderStatus { UNPAID(0), PAID(1), DELIVERING(2), FINISHED(3), CANCELED(4); private final int code; // 合法流转0-1, 1-2, 2-3, 0-4, 1-4 public static boolean canTransfer(int from, int to) { if (from 0 (to 1 || to 4)) return true; if (from 1 (to 2 || to 4)) return true; if (from 2 to 3) return true; return false; } }这样每次改状态前先canTransfer校验一下非法流转直接拒绝能挡掉大量脏数据。4. 小程序端页面跳转、列表加载与支付模拟4.1 顶部导航栏高度别再用 44px 硬编码微信小程序的顶部导航栏高度在不同机型上不一样尤其是带刘海屏的 iPhone。很多模板直接写padding-top: 44px在安卓上就偏了。正确做法是用wx.getSystemInfoSync()拿状态栏高度再加上导航栏本身的高度一般 44pxconst sysInfo wx.getSystemInfoSync(); const statusBarHeight sysInfo.statusBarHeight; // 状态栏高度 const navBarHeight 44; // 导航栏固定高度 const totalHeight statusBarHeight navBarHeight; this.setData({ navHeight: totalHeight });然后在 WXML 里用stylepadding-top: {{navHeight}}px。这样在 iPhone 14 和红米上都能对齐。注意getSystemInfoSync在新版基础库里有性能警告可以换成wx.getWindowInfo()但毕设用同步版完全够。4.2 列表加载更多onReachBottom 的三个参数菜品列表、订单列表都需要下拉加载更多。小程序原生支持onReachBottom但很多人不知道它有个onReachBottomDistance配置默认 50px意思是距离底部 50px 就触发。如果你的列表项很高可能还没滑到底就触发了体验很怪。Page({ data: { list: [], page: 1, hasMore: true, loading: false }, onReachBottom() { if (!this.data.hasMore || this.data.loading) return; this.setData({ loading: true, page: this.data.page 1 }); this.loadList(); }, loadList() { wx.request({ url: http://localhost:8080/api/dish/list, data: { page: this.data.page, size: 10 }, success: (res) { const newList this.data.list.concat(res.data.list); this.setData({ list: newList, hasMore: res.data.hasNextPage, loading: false }); } }); } });参数说明hasMore由后端PageInfo.isHasNextPage()返回别自己算loading是防抖防止用户快速滑动触发多次请求。onReachBottomDistance可以在app.json的window里配我一般设成 100提前加载体验更好。4.3 支付模拟毕设不接真实支付怎么演微信支付需要企业资质学生项目基本申请不下来。所以毕设里通常是「模拟支付」点支付按钮后直接调后端接口把订单状态从 0 改成 1前端弹个「支付成功」的 toast。这里要注意别把支付逻辑写在前端否则答辩老师一问「你怎么保证支付安全」就露馅了。// 前端只负责发起请求 wx.request({ url: http://localhost:8080/api/order/pay, method: POST, data: { orderId: this.data.orderId }, success: (res) { if (res.data.code 200) { wx.showToast({ title: 支付成功, icon: success }); wx.redirectTo({ url: /pages/order/detail?id this.data.orderId }); } } });后端pay接口里做状态校验和流转前端只展示结果。这样即使老师追问你也能说清楚「支付状态由服务端控制前端不可信」。5. 避坑与排查五个让答辩翻车的经典问题5.1 现象启动报 NoSuchBeanDefinitionExceptionService 注入失败原因applicationContext.xml和spring-mvc.xml的包扫描路径重叠或写错。常见的是两个文件都扫了com.campus导致 Controller 被 Spring 容器初始化两次事务代理失效。解决applicationContext.xml只扫com.campus.service和com.campus.mapperspring-mvc.xml只扫com.campus.controller。两个文件的component-scan路径必须互斥。5.2 现象MySql 连接报The server time zone value ?D1ú±ê×?ê±?? is unrecognized原因MySql 8.0 的时区默认是系统时区中文 Windows 下会返回乱码时区名驱动解析不了。解决连接 URL 加serverTimezoneAsia/Shanghai或者去 MySql 里执行SET GLOBAL time_zone 8:00。前者更省事推荐。5.3 现象小程序请求后端一直失败报request:fail url not in domain list原因微信开发者工具默认校验合法域名而本地后端是http://localhost:8080不在白名单里。解决开发者工具右上角「详情」→「本地设置」→ 勾选「不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书」。注意这只是开发阶段上线必须配 HTTPS 域名。5.4 现象订单列表查出来是空的但数据库里明明有数据原因MyBatis 的resultType和实体类字段名对不上或者mapperLocations路径写错导致 XML 没加载。还有一种情况是查询条件里user_id传了 nullSQL 变成WHERE user_id null永远查不到。解决先看控制台有没有打印 SQL没有就是 XML 没加载有 SQL 但结果空把 SQL 复制到 MySql 客户端里跑一遍。WHERE条件里涉及用户输入的一定要在 Service 层判空。5.5 现象下单后库存没减或者减了但订单没生成原因Transactional没生效。常见原因是方法不是public或者同类内部调用this.createOrder绕过了代理或者异常被 catch 了没抛出去。解决确保事务方法public异常往上抛别在方法里try-catch吞掉。如果确实要 catch记得throw new RuntimeException(e)。另外tx:annotation-driven要配在applicationContext.xml里别配到spring-mvc.xml。6. 进阶技巧用 Postman 和日志把接口调明白6.1 先调通后端再碰小程序我见过太多人一上来就开微信开发者工具结果前端报错、后端也报错根本分不清是谁的问题。我的习惯是后端接口先用 Postman 跑通再写小程序页面。比如登录接口先在 Postman 里发一个POST请求body 传{code:test,nickname:张三}看返回的token和user对不对。对了再写小程序。Postman 里我一般建一个环境变量baseUrl http://localhost:8080/api所有请求用{{baseUrl}}/login换机器只改变量就行。对于需要登录态的接口在 Header 里加token: xxx后端用拦截器校验。6.2 日志级别调到 DEBUG看 SQL 和参数SSM 默认的日志级别是 INFOMyBatis 的 SQL 不会打印。在log4j.properties或logback.xml里把com.campus.mapper调成 DEBUGlog4j.logger.com.campus.mapperDEBUG log4j.logger.org.springframeworkWARN这样控制台会打印Preparing: SELECT ...和Parameters: 1(Integer)一眼就能看出参数传对没有。答辩前把日志级别调回 INFO不然控制台刷屏显得不专业。6.3 一个我踩过的坑别在实体类里用基本类型刚做这套系统时我把Order的status定义成int结果 MyBatis 查询时如果数据库里是NULL直接抛NullPointerException。后来全部改成Integer并且所有可能为空的字段都用包装类型。这个习惯救了我很多次——数据库允许 NULL 的列Java 侧就用包装类别图省事用int。6.4 答辩前必做的三件事第一把数据库导出成.sql文件连同源码一起打包确保换台电脑能重建环境。第二录一段 3 到 5 分钟的演示视频覆盖登录、浏览、下单、支付、后台管理全流程万一现场网络出问题还能放视频。第三把论文里的「系统实现」章节和实际代码对一遍别出现论文写了 Redis 缓存但代码里根本没有的情况——这是答辩老师最爱抓的点。我自己的习惯是每次改完代码就git commit一次commit message 写清楚改了什么。到答辩前一周把整个项目从零部署一遍记录每一步的耗时和报错这份记录比任何「视频演示」都值钱。希望帮到你。本文还有配套的精品资源点击获取