微信小程序农产品销售平台毕业设计:源码架构、订单事务与避坑全集 简介面向微信小程序毕业设计与课程设计场景的农产品销售平台源码包适合需要完整前后端联调方案的计算机专业学生也可作为商城类项目二次开发的参考蓝本。项目按小程序客户端与管理后台拆分覆盖首页、农产品分类、每日特价、订单、用户、品种管理、管理员与系统管理等核心功能模块便于演示、答辩与扩展。整包共1197个文件、约33.22MB包含127个Java后端类、133个Vue后台页面、166个JS脚本、80个WXML与82个WXSS小程序页面以及2个SQL数据库脚本和231个PNG设计素材前端基于uniapp/原生小程序可用HBuilder X或微信开发者工具运行后端面向JDK1.8与MySQL5.7以上环境源码和数据库文件完整。压缩包另附说明文档、关键目录配置及1-install.bat、2-run.bat等批量脚本能帮助快速完成本地部署、环境初始化与整体结构理解。目前已有161人学习下载可作为毕业设计完整方案、课程设计项目参考或小程序商城开发练手材料。1. 从一份源码包到能答辩的农产品销售平台这中间差几步每年毕业季都会有人抱着「基于微信小程序的农产品销售平台源码」这类压缩包来找我问的第一句话几乎一样东西拿到了然后呢。这个标题拆开看其实很直白——一套能跑通的小程序前端、一套后端接口、一份 MySQL 数据库脚本外加说明文档和论文框架覆盖的是一个电商类毕业设计的完整闭环。它不是让你拿去抄完直接交而是给你一个「别人已经踩过一遍坑」的起点商品怎么上架、购物车怎么算钱、下单怎么扣库存、订单怎么流转这些骨架都在里面你要做的是看懂它、跑通它、再改出你自己的东西。这个平台能解决的核心问题只有一个很多做毕设的人不是不会写代码而是不知道一个「完整项目」该由哪些部分组成以及各部分怎么接起来。它适合两类人——准备交电商/小程序方向毕设的学生想快速了解一套小程序前后端工程怎么组织、联调和部署的开发者。后者即便不交毕设把这份源码当作「小程序电商最小可行系统」来拆解也是一条比看零散教程快得多的路径。但拿到源码只是开始从能打开到能演示、能答辩、能讲清楚为什么这么设计中间差的这几步才是这篇文章要带你走完的路。2. 架构与选型为什么「小程序 HTTP 后端 MySQL」是这类平台的稳妥答案2.1 三层结构背后的取舍逻辑先把这个平台的骨架画出来最上面是微信小程序前端负责商品展示、购物车、下单页这些用户看得见摸得着的界面中间是后端服务负责处理登录、商品查询、订单生成这类业务逻辑最下面是 MySQL 数据库存用户、商品、订单、配送地址这些需要持久化的数据。三层各干各的活通过 HTTP 接口通信这是目前小程序电商项目最常见也最不容易出错的组织方式。为什么不用纯前端方案比如把数据放在小程序本地存储里答案很简单本地存储一换手机就没了、多个用户之间完全隔离、也没有任何「服务端」的概念答辩时老师问一句「你的数据怎么在多用户间保持一致」就直接卡住。而引入后端和数据库之后哪怕后端逻辑写得很朴素架构上的完整性是站得住的这也正是毕业设计评审最看重的点。数据库选 MySQL 而不是别的关系型数据库原因也很实际它免费、装机率高、网上资料最多而且面试和答辩现场被问到的概率最大。农产品销售这块业务的数据量在毕设场景下也就几千条MySQL 的事务、索引、联表查询能力绰绰有余。后端语言反倒没那么关键——同一套源码里可能用的是 Spring Boot也可能是 Node.js 的 Express 或 Python 的 Flask选哪个取决于你哪个更熟。我的建议是别在这个阶段换语言源码原本用什么你就顺着什么改换框架的代价在毕设周期里是很大的。2.2 解压源码后按这套顺序把工程认全很多人的误区是一拿到压缩包就急着双击运行结果被缺依赖、报错信息淹没半小时后就放弃了。我一般会建议先不急着跑而是把解压出来的目录当成一张地图先看一遍。一个规范的前后端分离毕设项目通常会有这么几块内容目录/文件里面是什么你该先看什么小程序前端目录pages、app.js、app.json、project.config.jsonapp.json 里的页面注册和 tabBar 配置后端代码目录controller、service、dao 或 router 等分层启动入口和配置文件数据库目录.sql 脚本文件表结构重点看订单表和商品表说明文档与 LW需求分析、功能设计、界面截图、论文结构目录框架这是答辩讲稿的底稿别一上来就去读 controller 里的每一个接口实现那样信息量太大。正确顺序是先看数据库脚本搞清楚系统里有几张表、表之间什么关系这会让你对「平台管了什么数据」有全局概念再打开后端配置文件看端口、数据库账号密码、依赖最后进小程序目录看页面文件名猜每个页面对应什么功能。走完这三步你对这套系统的基本盘就有数了。2.3 最小环境搭建把工程从压缩包变成正在运行的系统环境搭建这件事细节决定成败。下面这套是我反复验证过的最短路径按顺序做任何一个环节卡住都说明前一步有问题。第一步装 MySQL 并导入数据脚本。用命令行客户端执行mysql -u root -p -h 127.0.0.1 database.sql如果你用的是 Navicat 或 MySQL Workbench直接打开脚本文件执行也可以。这里有一个容易翻车的地方脚本文件里如果有CREATE DATABASE语句导入前不需要手动建库如果没有你得先建一个同名数据库再导入。导入完成后用show tables;确认核心表都进去了。第二步改后端配置。找到后端的配置文件通常是.yml、.properties或.js格式把数据库连接信息改成本机的spring: datasource: url: jdbc:mysql://127.0.0.1:3306/farm_shop?useUnicodetruecharacterEncodingutf8 username: root password: your_password server: port: 8080这里的farm_shop是你导入脚本时用的数据库名your_password是你本机 MySQL 的 root 密码。很多项目跑不起来九成原因就是密码没改或者端口被占用。第三步启动后端。不同的后端框架启动命令不一样Spring Boot 直接运行主类Node 项目执行npm startPython 项目是python app.py。以 Node 为例npm install npm start看到日志输出listening on port 8080之类的内容说明后端起来了。这时候用浏览器访问http://127.0.0.1:8080或对应的接口路径能返回数据就说明后端和数据库已经打通。第四步用微信开发者工具导入小程序前端目录在「详情」设置里勾选「不校验合法域名」选项。这一步后面我会在避坑章节单独讲但现在是必须的否则本地调试时所有wx.request都会被拦截。提示环境搭建阶段任何一步报错都不要急着改代码。先确认 MySQL 能连、后端能起、小程序能编译这三件事彼此独立分开排查最快。3. 数据表与接口设计把订单、库存、商户这几块地基先立住3.1 六张核心表的结构拆解农产品销售平台在数据层面围绕一条主链路展开用户浏览商品 → 加购 → 下单 → 支付 → 商家发货 → 用户确认收货。支撑这条链路的最少表集合我认为是六张用户表、商品表、购物车表、订单表、订单明细表、配送地址表。如果平台要区分普通用户和商家管理员再在用户表里加一个角色字段即可不必拆成两张表。看建表语句是理解一个项目最快的入口。下面是我简化后最典型的用户、商品、订单三张表结构你可以拿来和源码里的 SQL 对照CREATE TABLE user ( id int unsigned NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信openid唯一, nickname varchar(50) DEFAULT NULL, phone varchar(20) DEFAULT NULL, role tinyint NOT NULL DEFAULT 0 COMMENT 0-普通用户, 1-商家管理员, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE goods ( id int unsigned NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, category varchar(50) DEFAULT NULL, price decimal(10,2) NOT NULL COMMENT 单价单位元, stock int unsigned NOT NULL DEFAULT 0 COMMENT 库存数量, image_url varchar(255) DEFAULT NULL, status tinyint NOT NULL DEFAULT 1 COMMENT 1-上架, 0-下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order ( id int unsigned NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号业务上唯一, user_id int unsigned NOT NULL, total_amount decimal(10,2) NOT NULL, status tinyint NOT NULL DEFAULT 0 COMMENT 0-待支付, 1-已支付, 2-已发货, 3-已完成, 4-已取消, address_id int unsigned DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有三处细节值得你答辩时主动讲出来第一price字段用decimal(10,2)而不是float因为浮点数在计算金额时会有精度误差电商订单金额绝对不能有误差第二user表对openid建了唯一索引保证同一个微信号只能注册一个账户第三order表里的status用tinyint存数字而不是直接存「已支付」这样的字符串一是省空间二是避免中文在不同编码环境下出问题前端再做一次状态码到文字的映射即可。购物车表和订单明细表我就不贴完整语句了它们的逻辑你要能讲清楚购物车表字段是id、user_id、goods_id、quantity、checked它本质上是「用户未确认的意向清单」下单完成后应该清空订单明细表字段是id、order_id、goods_id、goods_name、price、quantity注意它要冗余一份goods_name和price因为商品表里的信息后续可能被修改而历史订单必须保留下单那一刻的快照。3.2 统一接口返回格式前后端少吵一架的关键约定源码里的后端接口如果设计得规范你会看到几乎每个接口都返回同样结构的 JSON。这个结构通常长这样{ code: 0, message: success, data: {} }code是业务状态码message是给前端展示的提示信息data是真正的业务数据。这里的约定非常关键code 0表示成功非 0 表示各种异常比如400参数错误、401未登录、500服务端异常。前端拿到响应后只需要判断code是否为 0再决定走成功分支还是错误提示分支不需要每个页面各自处理异常逻辑。我见过不止一个项目每个接口返回的字段名都不一样有的返回{status: true}有的返回{success: 1}前端代码里全是if (res.data.status true)这种写法维护起来非常痛苦。如果你发现源码里已经是这种混乱状态花半天时间统一成上面的格式是很值的投入。后端封装一个统一响应工具类所有接口都走它返回public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 0; r.message success; r.data data; return r; } public static T ResultT error(Integer code, String message) { ResultT r new Result(); r.code code; r.message message; return r; } }有了这层包装接口的语义就统一了成功时data放业务数据失败时message给可读的提示文本。前端所有请求封装层可以直接复用同一个request函数统一做code判断、错误 toast、登录态失效跳转这是整个项目后期最容易体现出工程性的地方。3.3 下单扣库存一个必须用事务保护的核心操作农产品销售平台里业务复杂度最高、也是最容易被答辩老师追问的操作就是下单。它不是一个插入就完事的动作而是牵扯到多个数据变更生成订单记录、扣减商品库存、清空对应购物车项。这三个操作必须是一个原子操作——要么全部成功要么全部失败。如果先扣了库存订单创建失败商品就凭空少了一件如果先生成订单再扣库存并发场景下库存会被超卖。正确的实现思路是把这三个写操作放进同一个数据库事务里。用伪代码可以表达为BEGIN TRANSACTION # 1. 条件更新库存只有库存充足时才扣减成功 UPDATE goods SET stock stock - #{quantity} WHERE id #{goodsId} AND stock #{quantity} # 2. 若上一步影响行数为0说明库存不足回滚并报错 # 3. 插入订单主表记录 INSERT INTO order (order_no, user_id, total_amount, status, ...) VALUES (..., 0, ...) # 4. 插入订单明细记录 INSERT INTO order_item (order_id, goods_id, goods_name, price, quantity) VALUES ... # 5. 清空购物车中对应的商品 DELETE FROM cart WHERE user_id #{userId} AND goods_id #{goodsId} COMMIT TRANSACTION这段逻辑里最核心的是第二步那个WHERE stock #{quantity}条件。这也是「超卖」问题的标准解法不是先去查一遍库存够不够再决定是否扣减而是在更新语句里直接带上库存阈值条件让数据库的行锁来保证并发安全。如果你在源码里看到的是「先 select 查库存if 够再 update」那这个项目是有隐患的两个用户同时下单时各自都查到还剩 1 件最终就会卖出 2 件。这是个非常经典的反面教材你能在答辩时主动指出并修正它会是明显的加分项。下单接口还有一个细节order_no订单编号的生成。别用自增 id 直接当订单号给用户看否则用户能通过订单号推测平台总订单量。常见的做法是用时间戳加随机数比如yyyyMMddHHmmss加四位随机数再拼上一个业务前缀。4. 小程序前端登录、商品展示、购物车这三个闭环怎么落地4.1 小程序的工程入口先读懂 app.json 再动手改页面小程序前端工程拿到手第一步不是看页面代码而是打开根目录下的app.json。这个文件是小程序的全局配置它决定了哪些页面被注册、首页是哪个、底部 tabBar 长什么样、界面风格怎么定义。看明白它你就知道整个小程序由几个页面组成以及页面之间的跳转关系。{ pages: [ pages/index/index, pages/goods/detail, pages/cart/cart, pages/order/order, pages/user/user ], window: { navigationBarTitleText: 农产品商城, navigationBarBackgroundColor: #4CAF50, navigationBarTextStyle: white }, tabBar: { list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/cart/cart, text: 购物车 }, { pagePath: pages/user/user, text: 我的 } ] } }这个配置的意义在于pages数组里第一个元素就是小程序的启动页想改进入后的第一个页面就调整这一行的顺序tabBar最多配五个底部导航项这里配了首页、购物车、我的三个是最常见的小程序电商结构。你在改造这个平台时比如想把「订单列表」也加进底栏就是在这里加一项而不是新建页面。4.2 登录闭环用 wx.login 换 openid用 token 维持会话农产品销售平台必须知道「当前操作的人是谁」而微信小程序不像浏览器网页有显式的账号密码输入流程。它走的是微信生态特有的登录方式前端调用wx.login拿到一个临时凭证code把这个code传给后端后端拿code去微信接口换用户的openid然后下发一个自定义的 token 给前端前端后续所有请求都带上这个 token。// 小程序端登录封装 const login () { return new Promise((resolve, reject) { wx.login({ success: (res) { if (res.code) { // 把临时code发给后端后端换openid并生成token wx.request({ url: http://127.0.0.1:8080/api/auth/login, method: POST, data: { code: res.code }, success: (response) { const token response.data.data.token; wx.setStorageSync(token, token); resolve(token); } }); } else { reject(new Error(登录失败)); } } }); }); };这里有一个安全层级的问题你要明白code这个临时凭证有效期很短通常在五分钟级别并且只能用一次所以即使被截获风险也有限而openid是用户在当前小程序里的唯一身份标识后端拿到后存入数据库永远不应该在下发给前端的响应里原样返回。正确做法是后端自己维护一个openid到 token 的映射前端只持有 token。很多毕设项目图省事直接把 openid 返回给前端存起来这是答辩时讲出去会扣分的设计。token 的生成方式后端有很多选型简单起见用随机字符串就可以比如UUID存到一张会话表或 Redis 里设置过期时间。之后前端每次wx.request都在 header 里带上wx.request({ url: http://127.0.0.1:8080/api/goods/list, header: { Authorization: wx.getStorageSync(token) } });后端写一个拦截器从 header 里取 token、解析出用户身份这一步做完这个平台才算有了「登录态」的概念。4.3 商品列表与购物车页面渲染和接口数据的衔接商品列表页是绝大多数小程序电商项目里访问量最大的页面也是前端展示和后端接口衔接得最直接的地方。以首页商品列表为例核心逻辑是页面初始化时请求后端商品列表接口拿到商品数据数组通过setData放进页面的data状态里wxml 用wx:for循环渲染出卡片列表。view classgoods-list view classgoods-card wx:for{{goodsList}} wx:keyid bindtapgoDetail >Page({ data: { goodsList: [] }, onLoad() { this.fetchGoods(); }, fetchGoods() { wx.request({ url: http://127.0.0.1:8080/api/goods/list, data: { page: 1, size: 20 }, success: (res) { if (res.data.code 0) { this.setData({ goodsList: res.data.data }); } } }); }, goDetail(e) { const id e.currentTarget.dataset.id; wx.navigateTo({ url: /pages/goods/detail?id${id} }); } });这里有两个细节值得注意一是wx:key必须指定为列表项的唯一字段这里是id如果不写或写错小程序会有性能警告尤其是列表变动时重新渲染会出问题二是style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />