做校园外卖平台这个项目说实话是我近两年见过需求量最大的一个开发选题。微信小程序加外卖场景这两个词凑在一起几乎成了计算机类项目开发的顶流配置。原因也不难理解一方面小程序开发门槛相对较低能在较短时间内跑通前后端另一方面外卖平台业务链路完整从用户、商家到骑手每个角色都有清晰的业务逻辑天然适合用来展示技术能力。我手里这套基于微信小程序的校园外卖平台前后调试了差不多一个月帮几位开发者排查过各种奇奇怪怪的问题。今天干脆把整个项目的设计思路、技术选型、核心实现到调试心得完整地梳理一遍。不管你是正在为毕业设计发愁的同学还是想拿小程序项目练手积累经验的开发者这篇文章应该都能给你提供一些实打实的参考。1. 项目立项为什么校园外卖赛道适合用小程序做1.1 核心需求与真实业务场景还原校园外卖和普通的社会化外卖看起来都是用户下单、商家出餐、骑手配送这个套路但真要落到系统设计上两者的差异比想象中大得多。普通外卖平台面向的是开放街区商家的配送半径可能是三到五公里用户群体非常分散。校园不一样校园是一个相对封闭的场景用户高度聚集在宿舍区、教学区、食堂这几个固定节点。这意味着配送路径相对固定商家距离学生宿舍通常在一公里以内订单的高峰时段也非常集中——午餐和晚餐前后那两三个小时。这个差异直接决定了校园外卖平台的系统设计重心。我在规划这套项目时核心功能模块是围绕四个角色展开的学生用户浏览菜品、下单支付、订单跟踪、收货评价。商家维护菜品信息、处理订单、更新营业状态。配送骑手校园场景里兼职学生居多接单、取餐、送达、确认完成。平台管理员审核商家入驻、管理用户、处理异常订单、查看数据统计。说白了这就是一个完整的用户 - 商家 - 平台三角模型只是把配送环节也纳入了一起考虑。1.2 功能模块的边界划分与取舍做项目最忌讳的是什么功能都想做最后每个功能都写不深。我给这套项目定功能边界的时候参考的是小程序端的实际操作习惯同时兼顾了后台管理的完整性。用户端小程序的功能划分为首页商家列表按距离、销量、评分排序支持关键词搜索。菜品浏览一个商家对应一个菜单页菜品图文展示有分类筛选。购物车页面级购物车支持修改数量、清空自动计算总价。订单中心订单列表、订单详情、取消订单、确认收货。个人中心个人信息、收货地址管理、优惠券这个后面单独说。评价体系订单完成后可对商家和菜品打分评价。商家端没有做成独立的小程序而是基于同一个工程里做了角色鉴权后的视图切换。也就是说同一个微信账号根据身份类型进入不同的界面。这样处理的好处很明显不需要为了商家单独再维护一套代码库登录校验在服务端统一做前端通过路由守卫拦截即可。后台管理端则是单独的一个Web工程PC端页面功能偏向运营侧商家入驻审核、菜品类目管理、平台订单总览、用户封禁与解封、基础数据报表。这块我在设计时尽量精简因为对于毕业设计或者练手项目来说后台展示太多图表反而会冲淡核心业务逻辑的呈现。1.3 为什么是小程序不是App、H5我经常被问为什么非要选小程序直接做一个网页版或者App不香吗我的答案是小程序是当前校园场景下阻力最小、体验最完整的轻量级方案。学生不需要下载安装App微信扫一扫就能用用完即走这非常符合外卖这种高频低时长的使用场景。小程序从微信里天然获得了用户流量入口不用像独立App那样操心推广和获客问题。小程序开发框架本身就提供了一套前后端交互的标准范式对于一个人开发整个项目来说这种规约能省很多决策成本。同时微信小程序对开发者的友好程度确实高开发者工具自带调试器、模拟器、真机预览免费的学习资源也多。这跟在技术论坛里翻文档、自己搭建环境比起来新手友好的程度不在一个量级。2. 技术选型与系统架构设计2.1 前端小程序原生框架还是第三方框架我先说结论像校园外卖这种规模的系统第一选择始终是微信小程序原生框架而不是 Taro 或 uni-app。原生小程序的四件套WXML、WXSS、JS、JSON虽然被无数人吐槽难用语法不现代但在这种量级的项目中它的优势恰恰在于简单直接。原生框架没有中间层转换的损耗你在文档里看到的 API 直接就能调用排查问题的时候不用多绕一层编译链路。我也理解很多人想用 Vue 或 React 语法去写小程序这是 uni-app 或 Taro 的价值所在。但我个人的实操建议是如果是毕业设计或者个人练手项目原生框架是更好的选择因为项目文档和技术博客生态最成熟。等你真正理解了原生框架的组件生命周期和通信机制再迁移到跨端框架踩坑成本会低很多。前端目录结构我按功能拆成了这样├── pages │ ├── index // 首页-商家列表 │ ├── merchant // 商家详情/菜单 │ ├── cart // 购物车 │ ├── order // 订单列表 │ ├── order-detail // 订单详情 │ ├── user // 个人中心 │ ├── login // 登录 │ └── address // 地址管理 ├── components │ ├── merchant-card // 商家卡片组件 │ ├── food-item // 菜品项组件 │ ├── cart-bar // 底部购物车栏 │ └── order-status // 订单状态标签 ├── utils │ ├── request.js // 封装 wx.request │ └── auth.js // 登录态管理 ├── app.js ├── app.json └── project.config.json模板语法和事件绑定本身不复杂但有几个容易踩坑的细节我在后文调试部分会展开讲。2.2 后端最稳妥的组合方案后端用 Java Spring Boot 还是 Node.js这个问题几乎每个来找我咨询的人都会问。我做过的项目里两种方案都尝试过。从代码可读性和行业通用性来说Spring Boot 在这个场景下更合适。原因很现实项目中后端逻辑包含权限控制、订单状态流转、支付回调等复杂业务Spring Boot 的分层架构和事务管理机制在这类场景下确实更成熟、更可控。你自己写的时候也会发现Service 层和 Controller 层分离的模式能帮你理清思路。技术栈清单如下核心框架Spring Boot 2.x构建工具用 MavenJava 8 语言版本。ORMMyBatis-Plus配合 MySQL 5.7或 8.x 均可。权限控制Spring Security 处理密码加密和登录鉴权配合 JWT 做登录态。在这个项目里 JWT 确实比 Session 更适合小程序端因为小程序是无状态的请求模式每次请求带上 Token 让服务端校验不用考虑 Session 同步问题。缓存Redis主要用于两个地方一是缓存商家菜单列表减少数据库压力二是记录验证码等短期数据。文件存储菜品图片和用户头像本地存储方案项目部署目录下的上传文件夹在 Nginx 中配置静态资源映射即可。如果后续要上线到云服务器可以再替换为对象存储。数据库选型上千万不要在这个阶段过度设计。外卖项目涉及订单表、菜品表、用户表、商家表这几张核心表就够了字段也尽量贴近实际业务不要为了看起来很专业去加一些根本用不到的前置字段。2.3 数据库设计核心表结构与字段解释数据库设计直接决定你后面写业务代码的心情。我整理这套项目的核心表时遵循的原则是表尽量少但每张表的信息密度要高。核心表结构如下用户表sys_user字段名类型说明idbigint主键openidvarchar微信openid用于登录phonevarchar手机号可空nicknamevarchar昵称avatarvarchar头像URLuser_typetinyint1-学生用户、2-商家、3-骑手、4-管理员create_timedatetime注册时间商家表merchant字段名类型说明idbigint主键user_idbigint关联用户表IDnamevarchar店铺名称addressvarchar店铺地址imagevarchar店铺图delivery_feedecimal配送费noticevarchar公告如目前可配送statustinyint营业状态1-营业、0-歇业scoredecimal综合评分菜品表food字段名类型说明idbigint主键merchant_idbigint关联商家IDnamevarchar菜品名imagevarchar菜品图pricedecimal单价salesint月销量category_idint分类ID主食/饮品/小吃statustinyint1-在售、0-下架订单表orders字段名类型说明idbigint主键order_novarchar订单编号唯一user_idbigint下单用户merchant_idbigint商家total_amountdecimal订单总金额delivery_feedecimal配送费statustinyint0-待支付、1-待接单、2-配送中、3-已完成、4-已取消receiver_namevarchar收货人receiver_phonevarchar收货电话receiver_addressvarchar收货地址remarkvarchar备注消息create_timedatetime下单时间订单明细表order_item一对一订单明细记录每件商品的数量、单价、菜品快照。之所以要做快照是因为菜品价格或名称后续可能被商家修改订单详情必须保留下单那一刻的真实数据。为什么我做设计时一定要有 order_item 这一张表而不是把菜品信息直接塞进 orders 表的一个字段因为在业务层面这是完全不同的逻辑。如果订单里买了两份盖浇饭和一杯饮料你需要分别记录每份菜品的单价和数量等商家出餐时还要按明细备餐。字段塞在一个列表字符串里后续统计和商家端展示会非常痛苦。2.4 接口设计统一响应体与HTTP状态码约定后端接口设计我统一约定了一个返回格式{ code: 200, message: success, data: {} }code200表示成功code401表示未登录或Token失效code500表示服务端异常。前端封装的request.js里对所有响应做统一拦截拿到 code200 才放行code401 就跳转登录页。这样的好处非常明显前端写业务逻辑的时候完全不用关心 HTTP 层面的状态码只用判断业务 code。排查问题时也能一眼锚定到底是请求没发出去、路由没到还是后端逻辑错了。接口文档我习惯直接整理在项目的 README 目录下几个核心接口如下POST /api/user/login微信登录code换openidGET /api/merchant/list商家列表支持关键词、排序条件GET /api/merchant/{id}/foods商家菜单POST /api/order/create创建订单POST /api/order/pay模拟支付项目演示用区别于真实微信支付GET /api/order/list订单列表POST /api/order/confirm确认收货POST /api/merchant/order/accept商家接单POST /api/merchant/order/finish订单完成骑手送达3. 核心功能模块的实现一步步还原关键逻辑3.1 微信登录与权限控制小程序端最绕不过去的一步小程序登录的核心玩法是微信授权换 openid。流程是这样的小程序端通过wx.login()拿到一个临时code把它发给后端。后端拿着这个code去微信的接口换上openid用户在小程序生态中的唯一身份标识。系统再拿 openid 去sys_user表里查查到了就直接登录没查到就自动注册一个新用户然后返回一个 JWT Token 给前端。PostMapping(/api/user/login) public Result login(RequestBody LoginRequest request) { String code request.getCode(); // 调用微信接口用code换session_key和openid String openid wxService.getOpenid(code); SysUser user userMapper.selectOne( new LambdaQueryWrapperSysUser() .eq(SysUser::getOpenid, openid) ); if (user null) { user new SysUser(); user.setOpenid(openid); user.setNickname(微信用户 UUID.randomUUID().toString().substring(0, 6)); user.setUserType(1); // 默认学生用户 userMapper.insert(user); } String token jwtUtil.createToken(user.getId(), user.getUserType()); return Result.success(token); }需要注意的是真实的微信登录接口需要 appid 和 secret在小程序开发者工具里可以在详情-基本信息中查看。如果是做演示项目直接用测试号也能跑通但真机预览时需要把自己添加为开发者并拥有该小程序的体验权限。权限控制上我建议在后端的拦截器里对非公开接口统一做 Token 校验public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String path request.getRequestURI(); if (path.startsWith(/api/public/)) { return true; } String token request.getHeader(Authorization); if (token ! null jwtUtil.verify(token)) { return true; } response.setStatus(401); return false; }商家端接口还需要进一步校验userType权限字段保证学生用户不能调用商家接单接口。这个是权限设计的核心安全底线。3.2 购物车与订单创建从页面状态到数据库事务购物车在小程序端是纯前端的状态存储我用了全局变量加本地存储Storage两种方式结合。用户加了什么菜、数量多少同步存入wx.setStorageSync(cart, cartData)。这样用户退出小程序甚至杀掉进程后再进来购物车内容也不会丢。购物车的数据结构非常简单cartData [ { merchantId: 1, foodId: 10, name: 红烧肉盖饭, price: 16, count: 1 }, { merchantId: 1, foodId: 11, name: 冰可乐, price: 4, count: 2 } ]注意一个关键约束购物车里的商品必须属于同一个商家。如果用户从商家A的菜单里加了菜又跑去商家B的菜单加菜前端应该在加购物车时立刻给出提示购物车中已有其他商家的商品是否清空后添加。这个细节如果漏掉用户的订单逻辑会非常混乱——购物车结算时到底按谁的价格后端创建的订单又该归属哪个商家订单创建接口走后端时我建议强制用事务处理Transactional public Order createOrder(OrderCreateRequest req) { Merchant merchant merchantMapper.selectById(req.getMerchantId()); if (merchant null || merchant.getStatus() 0) { throw new BizException(商家不存在或已打烊); } ListOrderItem items req.getItems(); BigDecimal total BigDecimal.ZERO; for (OrderItem reqItem : items) { Food food foodMapper.selectById(reqItem.getFoodId()); if (food null || food.getStatus() 0) { throw new BizException(菜品已下架); } // 注意价格以后端为准不能让前端传价格 total total.add(food.getPrice().multiply(new BigDecimal(reqItem.getCount()))); } // 加上配送费 total total.add(merchant.getDeliveryFee()); Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(req.getUserId()); order.setMerchantId(merchant.getId()); order.setTotalAmount(total); order.setStatus(0); orderMapper.insert(order); // 批量插入订单明细 // ... 略 return order; }这里有一个非常非常重要的教训前端传过来的菜品价格后端绝对不能直接信任。一个懂行的人完全可以绕过前端直接拿接口工具往后端提交购买的菜品单价是0.01元这就是传说中的改包攻击。价格必须后端根据菜品ID重新从数据库查出来计算这个原则对所有电商类系统通用。下单后订单状态是待支付。由于项目演示环境通常不接入真实微信支付我这里的方案是做一个模拟支付接口前端点击立即支付时调用POST /api/order/pay后端把状态从待支付改为待接单并记录支付时间。这样既保持了业务闭环又不用折腾商户号申请。3.3 订单状态流转与消息通知机制订单状态流转是整个系统里最容易出错的环节。我设计的状态机和操作事件严格对应状态含义到达条件0-待支付下单成功未支付用户创建订单1-待接单支付成功等商家确认用户支付成功2-配送中商家已接单骑手配送商家点击接单3-已完成订单送达用户签收骑手完成/用户确认收货4-已取消用户/商家取消未支付超时/用户主动取消这个状态机里有两个边界情况值得注意一是待支付状态超过15分钟用定时任务扫描自动变成已取消避免脏订单堆积二是取消动作只能发生在待支付或待接单阶段一旦商家确认出餐就不能再取消了。实时通知这块我没有引入 WebSocket 或者消息队列而是用前端轮询的方式实现。学生端在小程序首页或订单页面停留时定时比如每5秒调用一次订单状态查询接口发现状态变了就更新界面并弹出提示。做毕业设计或学习项目这个方案在性能和实现复杂度之间取得了很好的平衡。你的后台服务里如果用了 Redis也可以考虑用 Redis 的订阅发布做状态变更通知但老实说在没有大量并发订单需求的项目里这个属于过度设计不建议花时间。3.4 商家端菜品管理与订单处理的联动逻辑商家界面在同一个小程序工程中通过 user_type 判断进入。商家登录后看到的底部导航是订单管理菜品管理数据统计。菜品管理逻辑清晰商家可以对菜品做上下架操作。实现上就是维护food表的 status 字段。我特意做了一个关联判断如果商家的营业状态 status0打烊那么用户端商家列表里该店铺就显示为休息中但菜品页可以正常浏览只是不能加购物车。这个设计贴近真实外卖App的使用逻辑。订单处理是商家端的重点。订单列表按时间分成新订单处理中已完成三个Tab。新订单待接单时商家点接单调用后端接口把订单状态从1待接单改为2配送中出餐后点完成配送或由骑手标记完成把状态从2改为3已完成。前端切换 Tab 时只用修改查询参数 status。后端对应接口GetMapping(/api/merchant/order/list) public Result list(RequestParam Integer status, RequestParam Long userId) { Merchant merchant merchantMapper.selectByUserId(userId); // 查订单表 where merchant_id ? and status ? }3.5 地址管理与配送范围判断容易被忽略的业务细节用户添加收货地址宿舍楼号、联系电话、备注这是很基础的功能。真正容易忽略的是配送范围判断。校园外卖理论上只送校园内部。我在系统里给商家配置了一个配送半径值比如 1.5 公里。用户下单时后端拿到用户填写的地址坐标和商家的坐标做距离计算超过半径就提示超出配送范围。坐标距离计算可以用简化的球面距离公式很多现成工具类里都有实现。因为校园范围小直接用经纬度欧氏距离也不会产生多大的偏差但建议还是用 Haversine 公式几行代码的事public static double distance(double lat1, double lng1, double lat2, double lng2) { double radLat1 Math.toRadians(lat1); double radLat2 Math.toRadians(lat2); double a radLat1 - radLat2; double b Math.toRadians(lng1) - Math.toRadians(lng2); double s 2 * Math.asin(Math.sqrt( Math.pow(Math.sin(a / 2), 2) Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2) )); return s * 6371.0; // 公里 }这个判断在项目里是一个展示完整度的加分项面试或答辩时能拿出来讲清楚会让评审觉得你是真的理解业务需求而不是只会 CRUD。4. 项目文档、源码结构与调试心得4.1 源码目录结构与文档体系这套项目交付的时候我习惯把目录整理成下面这样让接手的人一眼就知道哪里有什么project_root/ ├── backend/ // Spring Boot后端 │ ├── src/main/java │ ├── src/main/resources │ │ ├── application.yml // 配置文件 │ │ └── mapper/ // MyBatis XML │ └── pom.xml ├── miniprogram/ // 微信小程序前端 │ ├── pages/ │ ├── utils/ │ ├── app.js │ └── project.config.json ├── admin-web/ // 后台管理Web端 ├── sql/ │ └── init.sql // 数据库初始化脚本 └── 文档/ ├── 需求分析文档.md ├── 数据库设计说明.md ├── 接口文档.md └── 部署教程.md数据库初始化脚本一定要写好这是别人复现你项目的第一道关卡。脚本里除了建表语句最好包含一部分演示数据几个商家、几十个菜品、一个测试账号密码这样拿到项目的人启动后就能立刻看到界面和数据而不是对着空数据库一头雾水。部署文档里要写清楚环境要求JDK版本、Maven版本、MySQL版本、微信开发者工具版本。我踩过不少JDK8 跑得好好的代码到 JDK17 上编译报错的坑所以版本说明尽量详细。4.2 开发者工具调试技巧从报错到定位问题的时间线调试小程序时我最常被问的问题是小程序一打开就白屏什么都不显示怎么办。排查思路按照从外到内的顺序第一层看 Network 面板的请求记录。如果所有请求都是灰色失败状态基本可以确定是域名白名单或本机网络的问题。开发阶段最简单省事的方式是直接在开发者工具的详情-本地设置里勾选不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书。这一步做完大部分网络请求问题就消失了。第二层看 Console 面板的报错日志。区分JS逻辑层错误和渲染层错误。JS逻辑层错误基本都是你代码里的空指针或者未定义变量根据报错行号直接找代码就行。渲染层错误经常表现为 WXML 里访问了不存在的对象属性比如渲染商品列表时用了item.food.name但数据里其实只有item.foodId。第三层看 AppData 面板里的数据流。这是小程序开发者工具非常好用的一个功能相当于 Vue 里的 Vuex 调试。你点击页面上的每个交互按钮观察 AppData 里对应的数据是否变了。数据变了但界面没变说明是渲染层的问题数据没变说明是逻辑层的问题。用这个办法很快就能把问题定位到某一层。4.3 我帮你踩过的坑常见问题速查表我在陪跑这套项目的过程中收集了一大批高频问题这里整理成速查表应该能帮你省一晚上的排查时间。现象根因解决方案wx.request 请求一直 fail开发者工具未关闭域名校验本地设置中勾选不校验合法域名真机预览时接口500后端服务未部署到公网或局域网不可达用内网穿透工具以正式工具为准或把后端部署到云服务器分页数据重复分页参数传错offset 未累加检查当前页从1开始还是从0开始购物车金额和订单金额不一致前端计算与后端计算逻辑不一致订单金额以后端查询到的价格为准登录后刷新又回到登录页Token 未存到 Storage 或已过期检查 Storage 命名和 jwt 有效期商家接单后订单状态无变化更新语句 where 条件不完整更新时带上and status 1防止重复旋转菜品图片不显示图片路径是相对路径确认后端图片上传配置的静态资源映射后台接口返回 JSON 中有奇怪转义字符返回对象中字段包含HTML标签使用 JSON 序列化时过滤关于订单状态更新这个坑我专门多说一句。订单从待接单到配送中这个动作后端 SQL 应该写成UPDATE orders SET status 2 WHERE id #{id} AND status 1这样做的目的叫乐观锁思路。如果商家端开了两个页面多线程同时操作同一个订单时间戳判断可以防止状态被覆盖。虽然校园外卖平台实际并发量不大但养成这种更新时带上状态条件的习惯以后做任何状态机相关的系统都会少踩很多坑。4.4 从本地到上线部署与演示指南很多人以为项目写完就万事大吉了其实能跑起来和演示效果好之间还有一段路要走。本地跑通只需要三样东西MySQL 数据库导入init.sql、后端启动 Spring Boot 服务、微信开发者工具导入小程序目录并修改 app.js 里的接口地址为本机局域网 IP。但如果要把项目做成可远程演示、或者是给别人体验的程度后端就必须部署到一台公网服务器云服务器或轻量应用服务器都可以操作系统的选择上Ubuntu Docker Compose 或者直接 Java 环境安装都行。配置好 Nginx 反向代理把 HTTPS 证书配好小程序端再把 request 域名改成你备案过的 HTTPS 域名。这里必须强调一个合规前提小程序正式上线需要服务器域名备案、小程序类目审核、微信支付商户号申请等一系列流程。校园外卖这种涉及在线支付的平台个人开发者基本不可能直接上架到微信。所以项目演示阶段通常用测试号 真机预览的组合方案也就是把后端部署在公网或者用内网穿透工具使用正规服务小程序端用测试号扫码体验。这个方案足以支撑毕业设计答辩或者个人作品展示。真机预览是必须做的一步。开发者工具里运行得再丝滑真机上也可能出现兼容性差异不同手机的微信版本对小程序 API 的支持程度不同。至少要在 iOS 和 Android 两台设备上分别跑一遍主要流程浏览商家、加购物车、下单、接单、完成。5. 实操过程全记录从建表到跑通核心流程5.1 环境初始化与数据库准备我按当时清理完环境后第一次完整的部署步骤来写方便你对照着做。第一步安装并启动 MySQL。创建数据库实例字符集选择utf8mb4这个必须强调因为 emoji 表情和一些特殊字符只有 utf8mb4 能存用 utf8 存用户备注遇到表情会直接报错。第二步在sql/init.sql中执行建表和测试数据脚本。这里我建议把脚本拆成两段schema.sql建库建表和data.sql测试数据比单个大文件好用后续自己改配置也方便。第三步修改后端application.yml里的数据库账号密码、Redis 地址。spring: datasource: url: jdbc:mysql://localhost:3306/campus_order?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf-8 username: root password: 123456 redis: host: localhost port: 6379第四步启动 Redis如果项目用到了。本地没装的话直接下载一个Windows版本运行或者用 Docker 起一个容器都行。启动了之后后端服务才能正常处理验证码和缓存逻辑。第五步用 IDEA 打开 backend 目录等待 Maven 拉完依赖后运行主类。看到 Started Application 日志说明后端服务已经就绪。第六步打开微信开发者工具导入 miniprogram 目录把utils/request.js里的baseUrl改成http://localhost:8080在不校验合法域名打勾编译运行。到这里一个能看能点的小程序就起来了。5.2 完整采购流程演示从注册到订单完成我建议你按下面这条路径完整走一遍这是项目的主链路小程序端打开首页未登录状态下点击任意商家会强制跳转登录页面。前端通过wx.login()自动获取 code 调后端换 Token 完成注册登录。登录后回到首页能看到预制好的测试商家列表数据来自data.sql。点击进入商家菜单页选了三个菜品加入购物车购物车悬浮栏实时更新总金额。进入购物车页面点击去结算填写收货地址或者前一步在个人中心已填好。点击提交订单跳转到支付确认页点模拟支付订单状态变为待接单。打开小程序端商家视图用测试商家账号登录或者直接在后台管理页面操作看到新订单点击接单。回到用户端刷新订单详情状态已变为配送中。再模拟骑手点击完成状态变为已完成。用户端确认收货可以对订单进行评价。走完这条链路整个系统的核心业务就完全验证完毕了。我每次给开发者演示这个流程时都会说确认收货之后去 MySQL 里查一下orders和order_item两张表的数据你会看到状态字段已经从 0 流转到 3明细和总金额分毫不差。这种眼见为实的理解方式比看十遍代码都管用。5.3 一点经验后端端口被占用怎么办后端启动时报Port 8080 was already in use这个太常见了。Windows 下我通常直接执行netstat -ano | findstr 8080找出占用端口的进程 PID然后taskkill /PID [PID] /F但是这里我也反思过与其每次杀进程不如在application.yml里把服务端口直接改成 8081 或者 9090。毕竟有的开发者电脑上 8080 已经是很多框架的默认端口很容易冲突。改完之后记得同步把小程序的baseUrl端口号也改掉。6. 答辩与项目展示建议让你的代码发光6.1 项目讲解主线从业务痛点出发很多开发者答辩时最大的问题不是代码写得差而是讲的时候没有主线逻辑。站在评审的视角你一口气讲十分钟我用了 Spring Boot 和微信小程序远不如用一条业务主线把系统串起来有力。我建议的讲解顺序是第一层说清楚项目背景。校园外卖的痛点食堂排队时间长、商家没有配送渠道、学生懒得出门。你的系统解决了什么问题把这三点讲清楚引出项目定位。第二层说系统架构。用户端小程序、后端服务、管理端 Web 三者如何协同工作画一张简洁的架构图。这里不需要炫技把数据流向说清楚即可比如用户在小程序端加购菜品请求到了后端下单接口订单数据落到 MySQL。第三层挑一个最有技术含量的功能深挖。我推荐讲订单状态机或者购物车跨商家拦截。这两个功能虽然不复杂但能证明你思考过业务边界而不是只是写了个 CRUD 壳子。具体讲的时候可以结合代码里的事务注解和状态判断现场把核心代码投影出来指给评审看。第四层讲你遇到的最大技术困难以及怎么解决的。哪怕是联调的时候发现前后端字段名不一致导致数据传不过去这种问题只要讲得真实比讲一堆听起来高大上的技术名词更能打动人。6.2 项目可扩展方向把它变成一个持续演进的系统这套项目的边界是可控的但扩展空间我认为比想象中大。第一个方向是引入消息通知。当前轮询方案可以升级为 WebSocket 长连接订单状态一变服务端立刻推给对应端体验会明显提升。或者更轻量一些用微信小程序自带的订阅消息能力在下单、接单、完成时给用户发送模板消息通知。我建议优先考虑订阅消息因为微信的这个能力在校园场景中使用成本很低而且演示效果很强。第二个方向是把优惠券和积分体系加上。这个扩展方向对理解电商系统的营销模块非常有帮助。优惠券要处理领取、过期、抵扣、锁定等状态比订单状态还要繁杂做出来就是加分项。第三个方向是数据统计的可视化。后台管理端目前只有基础表格可以引入图表类开源库比如 ECharts展示每日订单量趋势热门菜品Top10各商家营收排名等图表。这是我个人大力推荐的一个扩展点它不涉及复杂的业务改造主要是数据的聚合查询和前端图表渲染但视觉效果提升非常明显。我在实际使用这套项目做演示时发现真正能让人印象深刻的往往不是某个复杂炫技的功能而是完整流畅的业务闭环和清晰的代码结构。走到已完成状态的那一单配合后台数据报表里有对应的记录整个故事就圆满了。最后再分享一个小技巧如果答辩现场的网络环境不稳定演示前务必把后端服务和数据库跑在本地前端开发者工具开模拟器模式而不是用真机扫码真机依赖局域网或公网容易受现场网络影响。我见过不少现场翻车全都是因为过度依赖外网环境这个准备工作谁做谁受益。