ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于微信小程序的校园外卖平台毕业设计:从数据库到订单状态机的完整实现

基于微信小程序的校园外卖平台毕业设计:从数据库到订单状态机的完整实现 简介基于微信小程序的校园外卖平台设计与实现完整毕业论文面向计算机相关专业毕业生及有外卖平台开发需求的学生开发者。文档紧扣校园外卖场景系统阐述从需求分析与可行性研究、系统功能与数据库设计到基于Java SSM框架、MySQL数据库及微信开发者工具实现全过程并详细说明管理员、用户、商家三类角色的核心功能如用户管理、菜品信息管理、订单领取管理以及小程序端的浏览下单、支付查阅等操作同时涵盖系统安全性、扩展性和易用性方面的设计考量。平台采用模块化架构后端使用SSM框架实现稳定高效的数据处理前端依托微信小程序提供便捷交互数据库以MySQL保障数据一致性整体设计贴合校园外卖场景的实际运营需求。既可作为毕业设计完整范本参考也能作为小程序外卖项目初期的需求与实现蓝本。资源包内含1个doc文档大小约1.58MB章节编排规范完整包含中英文摘要、目录及全篇正文。已有98人学习下载适合需要获取完整项目框架、借鉴技术选型与模块划分、对照撰写论文的读者。1. 为什么“基于微信小程序的校园外卖平台”能写出一篇合格的毕业设计校园外卖看起来是一个很小的业务用户点单、商家出餐、骑手配送三个角色一条流程但把它放在微信小程序这个载体上技术链路并不短小程序的登录态要和后端Session打通商品和购物车的状态要能跨页面同步订单在“待支付、待接单、备餐中、配送中、已完成”之间的迁移要处理超时和并发支付回调要验签防伪造。做毕业设计的同学往往一开始以为难点在UI和动画实际写代码后才发现难点在数据模型和状态一致性上。这篇文章会把从页面结构、数据库设计到接口鉴权和订单状态机的完整做法拆开讲给准备做这类课题的同学一条能走到答辩的路。这个题目的及格线不是页面有多好看而是三条角色链路能不能在真实数据下跑通异常情况能不能被兜住。2. 校园外卖平台的业务边界角色、页面与登录2.1 用户、商家、骑手三个角色怎么进同一个小程序校园外卖平台的业务参与方通常分三类学生用户、商家、配送骑手。在毕设场景里骑手不需要做成独立App而是复用同一个小程序通过角色权限进入不同页面。常见做法是在用户表加一个role字段登录后由后端返回角色小程序再根据角色切换到对应首页。这套方案的好处是一套后端同时服务三类用户前端通过wx.reLaunch或wx.switchTab跳转到不同入口代码工作量可控也方便在论文里写“多角色权限控制”这个技术点。后端接口的权限校验不能只依赖前端隐藏按钮。小程序端代码可以被抓包或反编译任何人都可能直接调用接口。所以在登录返回role之后每个后端接口都要先解析当前用户的角色比如商家订单列表接口必须校验rolemerchant骑手接单接口必须校验rolerider。角色字段的作用是前端入口分流真正拦截要在后端做。2.2 用一张路由表冻结页面清单项目初期的页面规划建议直接用一张表定下来前后端并行开发时就不容易改来改去角色入口页面核心页面进入方式学生用户首页商家列表、商品详情、确认订单、订单列表、个人中心底部TabBar商家工作台菜品管理、待处理订单、订单详情、营业统计登录后按role跳转骑手任务大厅待接单、执行配送、订单详情、业绩记录登录后按role跳转在app.json里配置页面路径时有一个容易被忽略的限制注册在tabBar里的页面只能被wx.switchTab调用用wx.navigateTo会报错tabBar页面之间传参不能靠URL只能借助getApp().globalData或wx.setStorageSync。如果一开始把订单详情页也放进tabBar后面做“从订单列表跳到详情”就会很别扭。正确做法是tabBar只放四个一级页面订单详情、商家详情、确认订单都作为普通页面用wx.navigateTo打开。2.3 登录返回的字段里藏着权限边界微信小程序登录的标准流程是wx.login拿到code后端用code去jscode2session接口换openid然后签发自定义token。登录接口的返回结构建议统一为{ code: 0, message: ok, data: { token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxx, userInfo: { uid: 10086, nickname: 2024级同学, avatar: https://thirdwx.qlogo.cn/xxx, role: student } } }小程序端把token存入本地缓存之后每个wx.request都在请求头带Authorization。role字段决定前端跳转到学生首页还是商家工作台。要特别说明的是这个role需要由后端根据管理后台的配置返回而不能让用户自己传角色。假如登录接口接受前端传roleadmin那整个权限体系就形同虚设。3. 校园外卖数据库怎么建订单表、菜品快照与支付单3.1 核心表拆分与字段说明数据库是这个项目的底盘。使用MySQL时核心表一般至少有用户表、商家表、菜品表、订单表、订单明细表、购物车表、支付表。用户表里区分三种角色商家表单独存店铺信息菜品表挂在商家下。以订单相关为例下面是一组可以直接参考的建表语句CREATE TABLE sys_user ( id bigint NOT NULL AUTO_INCREMENT, openid varchar(64) DEFAULT NULL, phone varchar(20) DEFAULT NULL, role varchar(16) NOT NULL DEFAULT student, nickname varchar(64) DEFAULT NULL, status tinyint NOT NULL DEFAULT 1, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_shop ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL, user_id bigint NOT NULL, merchant_id bigint NOT NULL, rider_id bigint DEFAULT NULL, total_amount decimal(10,2) NOT NULL, status tinyint NOT NULL COMMENT 10待支付 20待接单 30备餐中 40配送中 50已完成 60已取消 70退款中, receiver_name varchar(32) NOT NULL, receiver_phone varchar(20) NOT NULL, receiver_address varchar(255) NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL, dish_id bigint NOT NULL, dish_name varchar(128) NOT NULL, price decimal(10,2) NOT NULL, quantity int NOT NULL, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;表结构里有几个点需要展开说明。order_no是业务单号用于前端展示和对账必须加唯一索引主键id只做存储和关联不暴露给前端。order_shop.status用tinyint加注释比直接存字符串可读性更高也方便以后加状态。receiver_name、receiver_phone、receiver_address这三个字段是冗余设计哪怕学生用户修改了默认地址历史订单里的收货信息也不会被改变。3.2 订单明细为什么必须做菜品快照order_item表里存了dish_name和price这也是外卖平台里很常见的设计。菜品可能改价、改名、下架如果订单明细只存dish_id将来查询历史订单时就不得不去当前菜品表里取名称和价格一旦菜品被删除订单详情页就会显示空数据。更麻烦的是商家改价后旧订单的金额记录也会被“篡改”。所以下单那一刻后端必须把菜品名称、单价、数量完整地写入订单明细表。提交订单时的处理顺序是先校验购物车里每个菜品是否属于当前商家再重新查询价格并计算总金额最后写入订单和明细。前端传过来的总价只能作为展示参考不能作为落库依据。价格在后端重算配合数据库事务才能保证订单金额可信。这部分的校验逻辑在论文里可以单独写成“金额可信度”小节评委会比较看重。3.3 订单号、幂等与库存约束每天在同一家店点两次同一份饭系统里应该出现两条订单而不是因为用户手滑点了两次按钮就生成两条一模一样的订单。解决重复下单的方式是让前端在提交按钮按下后进入加载状态同时后端用order_no做幂等控制。但按钮置灰只能减少误操作真正可靠的做法是预下单用户确认订单时后端先生成一条状态为待支付的订单并返回order_no前端拿到订单号后再调支付或模拟支付。支付接口要求传入order_no如果用户重复提交支付后端发现订单已经是待接单直接拒绝重复入账。库存扣减也是同一个思路。菜品表里要有stock字段扣减时不能“先查再改”而是执行一行条件更新UPDATE dish SET stock stock - 1 WHERE id #{dishId} AND stock 0stock 0这个条件配合受影响行数判断可以在并发场景下避免超卖。如果更新影响行数为0说明库存不足整个下单事务回滚。4. 微信小程序端怎么接后端登录、请求封装、购物车与支付4.1 用wx.login换后端token的最小流程小程序端登录的代码可以封装成一个Promise方便后续所有页面调用function loginAndGetToken() { return new Promise((resolve, reject) { wx.login({ success: (res) { const code res.code wx.request({ url: ${getApp().globalData.baseUrl}/api/auth/login, method: POST, data: { code }, success: (resp) { if (resp.data.code 0) { wx.setStorageSync(token, resp.data.data.token) wx.setStorageSync(userInfo, resp.data.data.userInfo) resolve(resp.data.data) } else { reject(resp.data) } }, fail: reject }) }, fail: reject }) }) }后端收到这个code后再去调用微信的jscode2session接口拿openid查用户、建用户、发token。需要注意两点code只能用一次且有效期很短如果后端频繁报40029优先检查小程序端是否在多个地方重复使用同一个codeAppSecret只能保存在后端不能写进小程序代码否则只要有人抓包就能拿到密钥。4.2 统一请求封装把token过期拦在入口每个页面都写一遍wx.request很痛苦更痛苦的是token过期时每个页面都要重复处理。我一般在utils/request.js里封装一个方法function request(path, method, data) { return new Promise((resolve, reject) { wx.request({ url: ${getApp().globalData.baseUrl}${path}, method: method || GET, data: data || {}, header: { Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200 || res.statusCode 300) { reject(res) return } if (res.data.code 0) { resolve(res.data.data) } else if (res.data.code 401) { wx.removeStorageSync(token) // 重新走登录流程登录成功后提示用户重试 loginAndGetToken().then(() { wx.showToast({ title: 登录已过期请重试, icon: none }) }) reject(res.data) } else { wx.showToast({ title: res.data.message || 请求失败, icon: none }) reject(res.data) } }, fail: reject }) }) }这里把接口返回格式统一为code、message、data业务上是否成功只看codeHTTP状态码只表示网络请求有没有通。401安排在业务码里而不是HTTP状态码里可以避免微信开发者工具底层的状态码处理差异。拦截401后先清掉旧token再重新登录是为了防止用户下一步操作又带着过期token继续报错。4.3 购物车放本地没错但提交时必须后端重算购物车可以放在小程序本地缓存也可以放在后端。从毕设工作量来看本地购物车实现更简单但会带来两个问题不同商家之间的购物车需要按merchantId分组商品价格或库存变化后本地购物车展示的数据可能是过期的。解决办法是在进入购物车页或提交订单前调用一个后端校验接口把本地购物车里的dish_id列表发给后端由后端确认价格、库存、上下架状态然后返回最新的购物车结算数据。提交订单时前端提交的应该是“后端校验过的数据”而不是直接把本地购物车原样提交。严格一点的做法是前端提交订单时只提交购物车车型列表后端在事务里重新查询商品价格再算出订单金额并落库。这样即使有人修改小程序提交参数把菜品价格改成1分钱后端也不会接受。4.4 支付流程不要在前端直接判断成功毕设如果不接真实微信支付可以做一个/api/pay/mock接口来模拟支付成功后端在接口内直接更新订单状态和支付记录。如果接真实微信支付标准流程是后端先创建订单再调用微信支付统一下单接口拿到timeStamp、nonceStr、package等参数返回给前端前端再调wx.requestPayment拉起收银台。这里的坑在于支付结果不要以前端回调为准。用户在收银台点完支付微信返回的success只能说明“拉起收银台成功”不能代表钱一定到账。正确做法是后端接收微信支付回调验签、核对金额、更新支付单和订单状态小程序端在支付完成后轮询订单详情直到后端确认订单进入“待接单”页面才展示支付成功。这个设计在答辩时很容易讲清楚也能引出“回调幂等”的话题。5. 订单状态机超时、防超卖与支付回调节点5.1 状态定义与迁移约束订单状态不要散落在业务代码里到处用if-else建议先定义一张状态迁移表当前状态触发动作目标状态发起方待支付支付成功待接单支付回调待支付用户取消已取消用户待支付超时未付已取消定时任务待接单商家接单备餐中商家待接单商家拒单已取消商家备餐中商家出餐配送中商家配送中骑手送达已完成骑手配送中骑手异常上报退款中骑手状态更新时建议用“带旧状态条件”的更新语句# 订单状态更新必须带旧状态条件防止并发覆盖 updated Order.query.filter_by( order_noorder_no, statuscurrent_status ).update({ status: target_status, update_time: datetime.now() }) db.session.commit() if updated 0: raise BizError(订单状态已变化请刷新后重试)如果先查询订单再直接赋值保存两个请求同时操作同一订单时后一个请求会把前一个请求的状态覆盖掉。提交订单、支付回调、骑手接单都走这个条件更新状态机就不会被并发请求打乱。5.2 超时未支付怎么关单待支付订单超过15分钟仍未支付需要自动关闭。毕设项目不引入消息队列也能做到常见做法是定时任务或者惰性关闭。惰性关闭指的是用户查询订单详情时判断是否超时但数据统计会不准确更推荐加一个每分钟执行一次的定时任务扫描待支付订单。from apscheduler.schedulers.blocking import BlockingScheduler from datetime import datetime, timedelta def close_expired_orders(): expire_time datetime.now() - timedelta(minutes15) rows Order.query.filter( Order.status PAY_WAIT, Order.create_time expire_time ).all() for row in rows: row.status ORDER_CANCELED row.cancel_reason 支付超时自动关闭 # 释放库存需要遍历订单明细逐个补回 release_stock(row) db.session.commit() scheduler BlockingScheduler() scheduler.add_job(close_expired_orders, interval, minutes1) scheduler.start()这段代码在单实例部署时没有问题。定时任务扫描条件里已经限定了status待支付不会把已支付订单关掉。库存回补的逻辑要放在同一个事务里避免订单关闭成功但库存没加回来。5.3 库存扣减用一条SQL解决超卖下单时扣减库存取消时回补库存扣减操作必须使用原子SQLUPDATE dish SET stock stock - 1 WHERE id #{dishId} AND stock 0这一步在写入订单明细之前执行如果库存不足更新影响行数为0订单创建失败。把“校验库存”和“扣库存”合并成一条SQL是避免超卖最简单可靠的方式。如果改成先查询stock再在代码里判断是否大于0最后更新stock stock - 1那么两个并发请求可能同时读到stock1然后都执行更新最终库存变成-1。5.4 支付回调到达时先落支付单再改订单支付回调是订单流程里最容易出问题的环节。微信支付或模拟支付成功时回调可能因为网络原因重复推送所以接口必须做幂等。处理顺序建议先写支付单再更新订单状态先校验签名、确认金额金额对不上直接返回失败用transaction_id或pay_no作为唯一键插入支付记录重复插入会走唯一索引报错插入成功说明这是第一次回调立即更新订单状态为待接单插入失败说明是重复回调直接返回成功。支付记录表要建独立唯一索引CREATE TABLE order_pay ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL, pay_no varchar(64) NOT NULL, pay_type varchar(16) NOT NULL, amount decimal(10,2) NOT NULL, status tinyint NOT NULL COMMENT 10待支付 20已支付 30已退款, transaction_id varchar(64) DEFAULT NULL, pay_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_pay_no (pay_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;pay_no是支付平台生成的业务号不能复用。即使某笔支付失败下次发起支付也要生成新的pay_no因为旧单号可能已经在某个中间状态被记录过。6. 答辩前的验收表与两个反常规演示6.1 准备一张功能验收表论文定稿前建议把关键功能跑一遍并记录结果表格放在论文附录里很有说服力功能点验证方式预期结果微信登录测试号登录小程序获取token并正确返回角色角色路由学生、商家、骑手三个账号登录进入各自首页不能访问他端页面下单与库存库存剩1份同时下两单一个订单成功另一个提示库存不足模拟支付调用mock支付接口订单状态从待支付变为待接单取消订单待支付、待接单状态下取消状态变为已取消库存回补超时关单修改订单创建时间后等待定时任务待支付订单自动关闭验收表里的“并发防超卖”一定要预先准备脚本或者用两个微信账号同时下单答辩时能现场复现最好。如果现场网络不稳至少要把录屏留好直接播放给评委看。6.2 论文里值得单独成章的三个点一篇合格的毕业论文不需要把每个页面都写一遍但下面三点值得单独成章订单状态机设计包括状态定义、迁移条件和约束库存的原子扣减解释为什么用条件更新而不是先查再改支付回调的幂等设计说明如何用唯一约束防止重复入账。这三个点分别对应业务逻辑、并发安全和支付一致性是评委会深挖的高频题目。6.3 演示时不要只走顺利路径演示时故意展示两个“反常规”动作效果比一路畅通好得多。第一个动作是反复点击提交订单按钮演示后端生成的订单只有一条用order_no唯一索引兜住了重复提交第二个动作是把某个菜品库存修改为0演示下单失败时页面给出的库存不足提示。这两个演示直接回应用了“并发”“异常”“幂等”这些关键词比介绍页面更让评委记住你的系统。演示前记得在真机上完整跑一遍“登录到支付到配送完成”的链路并把录屏保存因为在答辩现场最容易出现的翻车点是手机连不上后端服务器而开发者工具里一切正常。本文还有配套的精品资源点击获取
返回列表