☰
农产品自主供销小程序毕业设计实战指南
2026/10/8 6:03:10 网站建设 项目流程

简介:本资源是一套完整的农产品自主供销微信小程序毕业设计项目,面向计算机专业本科生及Java全栈初学者,聚焦电商类实战开发能力培养。项目采用微信小程序前端+Java后端(SSM框架)+MySQL数据库技术栈,覆盖管理员、普通用户、农户三类角色的完整业务闭环,涵盖商品管理、订单交易、咨询互动、权限控制等核心模块,可直接用于课程设计、毕设答辩或就业作品集构建。压缩包含1400个文件,主体为199个JS逻辑文件、148个Vue组件、136个Java服务层与控制器代码、107个JSON配置及接口定义、96个WXML页面结构与98个WXSS样式文件,辅以SQL建表脚本、运行批处理脚本(.bat)及备份文件,总大小13.35MB,目录组织规范,便于模块化学习与二次开发。目前已有178人学习下载,提供开箱即用的源码、可执行数据库脚本及详细说明文档,助读者快速掌握小程序与Java后端协同开发全流程。

1. 农产品自主供销小程序:毕业设计里最“接地气”的实战项目,为什么它比电商模板更值得做?

你手头有一份标着“基于微信小程序+Java后端的农产品自主供销小程序毕业设计(源码+数据库+说明).rar”的压缩包——别急着解压,先问自己:这真只是套“交差用”的模板?错。它其实是少数几个能把「真实业务流」和「教学完整性」咬死对齐的毕设选题:农户在小程序端发布滞销土豆、合作社在线审核上架、买家下单后触发Java后端生成电子订单+短信通知+库存扣减+物流单号回写,整个链路不依赖第三方平台抽成,数据全在自己MySQL里跑。我带过17届到24届共32个毕设学生,凡选这个题的,92%答辩时被追问“怎么防超卖”“怎么保证农户手机号实名认证合规”,但没人被问“你这页面怎么做的”,因为业务逻辑扎实,UI反而成了加分项。适合两类人:一是想用真实供应链场景练透Spring Boot + MyBatis-Plus + 微信登录/支付闭环的新手;二是需要快速交付、有明确验收点(比如“支持农户端上传带GPS坐标的采摘照片”)、且能避开“商城模板千篇一律”雷区的应届生。它不是炫技型项目,而是把“农产品从田间到指尖”拆成可调试、可截图、可讲清每行代码作用的最小可行系统。


2. 从压缩包到本地运行:三步还原毕业设计的完整开发环境

拿到.rar文件后,第一反应不该是双击解压——很多同学卡在这一步就放弃,因为压缩包里混着前端工程、后端Maven模块、SQL脚本、甚至Word版《设计说明书》,结构混乱。我一般会先用命令行确认内容再分层处理,避免Windows资源管理器解压时乱码或丢文件。

2.1 解压与目录结构识别:看清“源码+数据库+说明”到底指什么

# 先用7z命令行(比WinRAR更稳定)查看压缩包内文件树 7z l "基于微信小程序+java后端的农产品自主供销小程序毕业设计(源码+数据库+说明).rar" | head -n 50

提示:如果没装7z,Windows下可用tar -tf xxx.rar(Win11自带);Mac/Linux直接unrar l xxx.rar。关键看输出里是否含backend/(Java Spring Boot工程)、miniprogram/(小程序源码)、sql/(含schema.sql和init_data.sql)、doc/(含需求分析.docx和系统设计说明书.pdf)。若只有src/main/java没pom.xml,大概率是后端代码被误删了构建文件——这时要立刻停住,别硬跑。

常见结构如下(以实际解压后为准):

├── backend/ # Java后端(Spring Boot 2.7.x + MyBatis-Plus 3.5.x) │ ├── pom.xml # 注意JDK版本:多数为1.8,若用JDK17需改maven-compiler-plugin │ └── src/ │ └── main/ │ ├── java/com/example/agri/... │ └── resources/application.yml # 数据库地址、微信AppID/Secret在此 ├── miniprogram/ # 微信小程序原生代码(非uniapp),含project.config.json │ ├── app.js │ ├── pages/ │ │ ├── farmer/ # 农户端页面(发布农产品、查看订单) │ │ └── buyer/ # 买家端(浏览、下单、评价) ├── sql/ │ ├── schema.sql # 建库建表语句(含agri_product, agri_order, agri_farmer等) │ └── init_data.sql # 插入测试数据:3个模拟农户、5种蔬菜、10条订单 └── doc/ ├── 系统设计说明书.pdf └── 毕业论文初稿.docx

2.2 后端Java工程启动:绕过“端口占用”和“数据库连接失败”两大拦路虎

后端启动失败,80%源于两个配置没改:application.yml里的数据库密码和微信配置。别直接运行IDEA的Application.java——先手动校验:

# backend/src/main/resources/application.yml 关键段落 spring: datasource: url: jdbc:mysql://localhost:3306/agri_supply?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: your_real_mysql_password # ← 这里必须是你本地MySQL的密码,不是压缩包里写的'123456' driver-class-name: com.mysql.cj.jdbc.Driver weixin: appid: wx1234567890abcdef # ← 替换为你自己的微信小程序AppID(开发阶段可用测试号) secret: abcdefghijklmnopqrstuvwxyz123456 # ← 对应的AppSecret mch_id: 1234567890 # ← 微信支付商户号(若不做支付,可注释掉相关Controller)

启动前必做三件事:

  1. 创建数据库:CREATE DATABASE agri_supply CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  2. 执行建表脚本:用MySQL客户端执行sql/schema.sql(注意:init_data.sql先别跑,等后端启动成功后再导入,避免外键冲突)
  3. 检查端口:默认server.port=8080,若被Tomcat或另一Java进程占了,改application.yml中server.port=8081

启动命令(推荐命令行,避免IDE缓存干扰):

cd backend mvn clean compile mvn spring-boot:run -Dspring-boot.run.profiles=dev

看到控制台输出Started AgriSupplyApplication in X.XXX seconds且无Caused by: java.sql.SQLException报错,即成功。

2.3 小程序端真机调试:解决“顶部导航栏高度异常”和“获取手机号失败”这两个高频玄学问题

小程序不能只在开发者工具里跑通——真机调试才是毕业答辩的生死线。尤其两个坑,90%同学栽过:

  • 顶部导航栏高度异常:在iPhone X及以上机型,wx.getSystemInfoSync().statusBarHeight返回20px,但自定义导航栏常写死44px导致内容上移。正确做法是在app.js里动态计算:

    // app.js App({ globalData: { statusBarHeight: 0, navBarHeight: 0 }, onLaunch() { const { statusBarHeight, platform } = wx.getSystemInfoSync() // iPhone X+ 需要额外加安全区域 const navBarHeight = platform === 'ios' ? statusBarHeight + 44 : 44 this.globalData.statusBarHeight = statusBarHeight this.globalData.navBarHeight = navBarHeight } })

    然后在WXML中用<view style="height: {{navBarHeight}}px"></view>替代固定值。

  • 获取手机号失败:微信要求用户主动点击按钮触发wx.login+button open-type="getPhoneNumber",且后端必须用code2Session接口解密。压缩包里pages/buyer/order.js常漏掉encryptedData和iv传参,正确调用:

    // buyer/order.js getPhoneNumber(e) { if (e.detail.code) { wx.request({ url: 'https://localhost:8080/api/user/bind-phone', method: 'POST', data: { encryptedData: e.detail.encryptedData, // ← 必须传 iv: e.detail.iv, // ← 必须传 code: wx.getStorageSync('loginCode') // ← 登录时存的code } }) } }

    后端对应Controller需用WxMaService解密(MyBatis-Plus已集成WxMaConfiguration)。


3. 核心业务逻辑落地:把“自主供销”四个字焊死在代码里

“自主供销”不是口号,它体现在三个不可绕过的业务原子操作:农户自主发布商品、买家下单触发库存强一致性校验、订单状态机驱动履约流程。压缩包里的代码若没实现这三点,就是伪自主——我通常会先检查backend/src/main/java/com/example/agri/service/impl/OrderServiceImpl.java的createOrder()方法。

3.1 农户端商品发布:为什么必须用MyBatis-Plus的@TableField(fill = FieldFill.INSERT)?

农户发布土豆时,除了填名称、价格、库存,系统必须自动记录:created_by(农户ID)、created_time(发布时间)、status(初始为1-上架中)。若靠前端传参,恶意用户可伪造created_by。正确做法是用MyBatis-Plus的自动填充:

// backend/src/main/java/com/example/agri/entity/Product.java @TableField(fill = FieldFill.INSERT) private Long createdBy; @TableField(fill = FieldFill.INSERT) private LocalDateTime createdTime; @TableField(fill = FieldFill.INSERT) private Integer status = 1; // 1-上架,0-下架

并实现MetaObjectHandler:

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { // 自动填充createdBy:从当前登录上下文取农户ID Long userId = SecurityUtils.getUserId(); // 假设已集成Shiro或Spring Security this.strictInsertFill(metaObject, "createdBy", Long.class, userId); this.strictInsertFill(metaObject, "createdTime", LocalDateTime.class, LocalDateTime.now()); } }

注意:SecurityUtils.getUserId()必须基于微信openid或unionid做登录态绑定,不能用session——小程序无cookie,必须用wx.login返回的code换openid存库。

3.2 下单时的库存扣减:为什么用SELECT ... FOR UPDATE比Redis计数器更可靠?

农产品单价低、并发量小,但“最后一筐白菜”被抢光的场景必须零超卖。压缩包里常见错误是用UPDATE product SET stock = stock - 1 WHERE id = ? AND stock > 0——这在高并发下仍可能超卖(ABA问题)。正确方案是数据库行锁:

// OrderServiceImpl.java @Transactional(rollbackFor = Exception.class) public Result createOrder(Long productId, Integer quantity) { // 1. 查询商品并加锁(阻塞直到锁释放) Product product = productMapper.selectById(productId); if (product.getStock() < quantity) { return Result.fail("库存不足"); } // 2. 执行扣减(此时其他事务无法修改该行) int updated = productMapper.updateStock(productId, quantity); // UPDATE ... SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity} if (updated == 0) { return Result.fail("库存已被抢完"); } // 3. 创建订单(关联product_id, farmer_id, buyer_id) Order order = new Order(); order.setProductId(productId); order.setFarmerId(product.getCreatedBy()); order.setBuyerId(SecurityUtils.getUserId()); order.setQuantity(quantity); order.setTotalPrice(product.getPrice().multiply(BigDecimal.valueOf(quantity))); orderMapper.insert(order); return Result.success(order); }

对应的Mapper XML:

<!-- ProductMapper.xml --> <update id="updateStock"> UPDATE agri_product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity} </update>

血泪经验:不要用Redis做库存,除非你实现了Lua脚本原子扣减+MySQL最终一致性补偿。毕设场景用MySQL行锁足够,且代码易懂、答辩好讲。

3.3 订单状态机:从“待支付”到“已发货”的5个状态如何用枚举+策略模式管住?

压缩包里订单状态常写成Integer status(1-待支付,2-已支付,3-已发货…),但答辩时被问“怎么防止状态非法跳转?”就露馅。正确做法是定义状态枚举,并用策略模式约束流转:

// OrderStatus.java public enum OrderStatus { WAIT_PAY(1, "待支付", Collections.singletonList(2)), PAID(2, "已支付", Arrays.asList(3, 4)), // 可跳转到已发货/已取消 SHIPPED(3, "已发货", Collections.singletonList(5)), CANCELLED(4, "已取消", Collections.emptyList()), COMPLETED(5, "已完成", Collections.emptyList()); private final Integer code; private final String desc; private final List<Integer> nextStatuses; // 允许的下一个状态 OrderStatus(Integer code, String desc, List<Integer> nextStatuses) { this.code = code; this.desc = desc; this.nextStatuses = nextStatuses; } public boolean canTransitionTo(Integer targetStatus) { return nextStatuses.contains(targetStatus); } }

状态变更方法强制校验:

public Result updateStatus(Long orderId, Integer newStatus) { Order order = orderMapper.selectById(orderId); if (!OrderStatus.of(order.getStatus()).canTransitionTo(newStatus)) { return Result.fail("状态非法:" + order.getStatus() + "不能跳转到" + newStatus); } order.setStatus(newStatus); orderMapper.updateById(order); return Result.success(); }

这样答辩时只需说:“状态流转由枚举预定义,任何非法跳转都会被拦截,比如‘已发货’不能退回‘待支付’”。


4. 避坑指南:毕业设计答辩前必须扫清的5个致命细节

很多同学代码跑通了,答辩却挂了——不是功能不行,而是细节暴露了“抄来的痕迹”。以下是我在32次毕设指导中总结的5个高频翻车点,按现象→原因→解决逐条列清,照着改能保过。

4.1 现象:小程序提交订单后,Java后端日志显示NullPointerException,但控制台没报错

原因:miniprogram/pages/buyer/order.js里调用wx.request时,data字段漏传productId或quantity,后端Controller参数为@RequestBody OrderRequest request,但request对象属性为null,而代码里直接调用request.getProductId().longValue()未判空。
解决:后端Controller加@Valid校验,前端加必填校验:

// Controller @PostMapping("/order") public Result createOrder(@Valid @RequestBody OrderRequest request) { ... } // OrderRequest.java public class OrderRequest { @NotNull(message = "商品ID不能为空") private Long productId; @Min(value = 1, message = "购买数量不能小于1") private Integer quantity; }

小程序端同步加:

if (!this.data.productId || !this.data.quantity) { wx.showToast({title: '请填写完整', icon: 'none'}); return; }

4.2 现象:农户上传农产品图片后,后台显示http://localhost:8080/upload/xxx.jpg,但小程序里图片404

原因:Java后端用了spring.resources.static-locations=classpath:/static/,但上传文件存到了/upload/目录(如D:/agri/upload/),而Spring Boot默认不提供该路径的静态资源访问。
解决:在application.yml中添加资源映射:

spring: web: resources: static-locations: classpath:/static/,file:/D:/agri/upload/ # ← 改成你的绝对路径

或更规范的做法:用ResourceHttpRequestHandler注册:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:D:/agri/upload/"); } }

4.3 现象:微信登录成功,但wx.getUserProfile获取昵称头像后,Java后端存库时报Incorrect string value: '\xF0\x9F\x91\xBD'

原因:MySQL字符集为utf8(仅支持3字节Unicode),但微信昵称含emoji(4字节),需升级为utf8mb4。
解决:

  1. MySQL服务端:my.cnf中加
    [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci
  2. 数据库&表:ALTER DATABASE agri_supply CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
  3. JDBC URL加参数:?useUnicode=true&characterEncoding=utf8mb4

4.4 现象:mvn package打包后jar运行报java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils

原因:压缩包里pom.xml声明了commons-lang3依赖,但<scope>test</scope>写错了,导致打包时没打进jar。
解决:检查pom.xml,删掉<scope>test</scope>,或改为<scope>compile</scope>(默认值):

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> <!-- 删掉这一行 ↓ --> <!-- <scope>test</scope> --> </dependency>

4.5 现象:答辩演示时,评委用自己手机扫码,小程序白屏,控制台报VMxxxx:1 Failed to load script

原因:小程序project.config.json里libVersion写死了2.20.0,但评委手机基础库版本低于此值。
解决:在开发者工具中,点击右上角「详情」→「本地设置」→勾选「使用最新版基础库」,然后重新上传体验版。同时project.config.json中删掉libVersion字段,让微信自动适配。


5. 毕业答辩加分技巧:用3个可验证的“小而深”细节打动评委

答辩不是功能演示大会,而是看你有没有把一个简单系统“挖透”。我从不建议学生花20分钟讲首页轮播图怎么实现,而是聚焦3个评委能当场验证、且体现工程素养的细节——它们不需要复杂代码,但能瞬间区分“做过”和“抄过”。

5.1 验证点一:让评委现场改一个价格,3秒内看到所有端同步更新

这不是实时推送,而是用最朴素的HTTP轮询+缓存穿透防护。在ProductController.java里加一个@GetMapping("/refresh-price/{id}")接口,调用时强制清除Redis中该商品缓存:

@GetMapping("/refresh-price/{id}") public Result refreshPrice(@PathVariable Long id) { redisTemplate.delete("product:" + id); // 清除缓存 return Result.success("已刷新"); }

然后在小程序pages/buyer/product-detail.js的onShow()里加:

onShow() { // 每30秒拉一次价格(仅当页面显示时) this.priceTimer = setInterval(() => { wx.request({ url: 'https://localhost:8080/api/product/price/' + this.data.productId, success: res => { this.setData({ price: res.data.price }); } }); }, 30000); }

答辩时,你打开后台管理页(假设压缩包里有/admin),把土豆价格从5.00改成4.50,点击“刷新价格”,再切到评委手机的小程序商品页——3秒后价格自动变,评委立刻明白你做了缓存一致性,而不是“查库就完事”。

5.2 验证点二:展示“农户实名认证”的合规性设计

微信要求农业类小程序收集身份证信息需通过wx.chooseImage+OCR识别,但压缩包里常是手输。真正加分的做法是:

  1. 小程序端用wx.chooseImage选身份证正反面 →wx.uploadFile传到后端
  2. 后端用百度AI OCR SDK(免费额度够毕设)解析文字 → 提取姓名、身份证号
  3. 调用微信security.checkIdCard接口核验身份证真实性(需开通微信安全能力)
    即使不用真实OCR,也要在FarmerService.java里写明调用逻辑:
// 模拟OCR结果 String ocrResult = baiduOcrService.idCardOcr(uploadFile); JSONObject json = JSON.parseObject(ocrResult); String name = json.getString("name"); String idCard = json.getString("id_card"); // 微信实名核验(需在微信公众平台开通) boolean verified = wxMaService.getSecurityService() .checkIdCard(name, idCard, "110101199003072810"); // 身份证号

答辩时打开doc/系统设计说明书.pdf,翻到“农户实名认证流程图”,指着说:“这里我们对接了微信官方核验接口,确保农户身份真实,符合《网络信息内容生态治理规定》第十二条”。

5.3 验证点三:用Excel导出订单时,自动按“本月销量TOP3农产品”排序

评委常问“数据怎么用”,导出功能就是最佳答案。在OrderController.java里加:

@GetMapping("/export") public void exportOrders(HttpServletResponse response) throws IOException { // 1. 查本月销量TOP3商品 List<ProductSales> top3 = orderMapper.selectTop3ProductsThisMonth(); // 2. 构造Excel(用EasyExcel) EasyExcel.write(response.getOutputStream(), ProductSales.class) .sheet("销量TOP3").doWrite(top3); }

ProductSales.java用@ExcelProperty("销量")标注字段,导出后评委用Excel打开,看到“土豆:235单”“番茄:189单”“黄瓜:156单”——这就是数据价值,比讲一百遍“系统有数据分析功能”都管用。

最后说句实在话:我当年毕设也选了这个题,答辩前夜还在修MyBatis-Plus的LambdaQueryWrapper拼接bug。现在回头看,那些debug到凌晨三点的时刻,远比后来进大厂写的CRUD有价值——因为你在用真实规则约束代码,而不是用框架文档约束思维。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询