简介:这是一套面向高校计算机相关专业毕业设计的助农扶贫微信小程序完整项目,采用SSM框架搭建后台、Vue构建管理端页面,前端为微信小程序,数据库使用MySQL,基于JDK1.8开发,Eclipse、MyEclipse、STS、IDEA等工具均可导入运行。系统覆盖管理员、农户、用户三类角色:管理员负责注册农户与用户信息、统计订单、管理商品及评价退货;农户可管理在售商品、订单发货、配送管理与退货审核;用户则能收藏、购买、支付、申请退货并评价商品,功能闭环完整,适合作为毕设选题或课程设计参考。资源包共1208个文件,约55.64MB,包含111个Java源文件、129个Vue组件、82个WXSS与80个WXML小程序页面文件,以及PNG、SVG、JPG等图片素材和JSON、XML配置,另附源码、数据库脚本、论文、答辩PPT、环境工具包与同框架项目安装教程。目前已有87人学习,可帮助读者快速理解项目结构、理清业务逻辑并完成部署调试。
1. 从一份 SSM + 微信小程序的助农扶贫源码说起
助农扶贫类微信小程序这两年在计算机毕业设计选题里出现频率很高,原因不复杂:业务场景清晰(农户发布农产品、消费者下单、管理员审核)、技术栈主流(SSM 后端 + 微信小程序前端)、文档和源码容易形成完整交付物。但真正动手做的人会发现,网上能搜到的「java 助农扶贫微信小程序 + ssm 源码含文档含教程」大多是半成品——后端接口对不上、数据库字段缺失、小程序端登录逻辑写死、文档只有目录没有内容。这篇笔记不讲空泛的选题意义,而是把这类项目从环境搭建、SSM 分层、小程序对接、数据库设计到部署调试的完整路径拆开,让拿到源码的人能跑起来,让准备自己写的人知道每个模块该放什么。适合正在做计算机毕业设计、需要一套可演示可答辩的微信小程序项目的同学,也适合想用 SSM 练手全栈流程的 java 初学者。
2. 环境搭建与 SSM 骨架:先把后端跑通再谈小程序
2.1 JDK、Tomcat、MySQL 的版本选择与验证
SSM 项目对版本敏感,尤其是 JDK 和 Tomcat 的搭配。我一般用 JDK 8 + Tomcat 8.5 + MySQL 5.7 这套组合,原因是 SSM 的很多老依赖(比如 spring 4.x、mybatis 3.x)在 JDK 8 上最稳,Tomcat 8.5 对 Servlet 3.1 的支持也刚好匹配。如果你用 JDK 11 以上,javax.servlet 包名会变成 jakarta.servlet,老项目直接编译报错,这是最常见的翻车点。
验证环境是否就绪,按下面三步走:
# 1. 检查 JDK 版本,必须是 1.8.x java -version # 2. 检查 MySQL 是否启动并能登录 mysql -u root -p -e "SELECT VERSION();" # 3. 检查 Tomcat 端口是否被占用(默认 8080) netstat -ano | findstr 8080第一行输出如果是java version "1.8.0_xxx"就对了。第二行能返回 MySQL 版本号说明服务正常。第三行如果 8080 被占用,要么改 Tomcat 的 server.xml 里的 Connector port,要么杀掉占用进程。参数上,MySQL 连接建议在 jdbc.properties 里显式指定useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai,否则中文农产品名称入库会变问号,时区不对还会导致订单时间差 8 小时。
2.2 Maven 依赖与 SSM 三层配置的对应关系
SSM 的核心是 Spring 管 Bean、SpringMVC 管路由、MyBatis 管数据库。pom.xml 里必须有的依赖包括 spring-webmvc、spring-jdbc、mybatis、mybatis-spring、mysql-connector-java、druid(连接池)、jackson-databind(JSON 转换)。版本上 spring 用 4.3.30.RELEASE、mybatis 用 3.5.6、mybatis-spring 用 2.0.6,这套组合经过大量项目验证。
<!-- pom.xml 关键依赖片段 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.30.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency>依赖说明:spring-webmvc 提供 DispatcherServlet 和注解驱动;mybatis 负责 SQL 映射;druid 做连接池,比默认的 DriverManager 性能好很多,还带监控页面。注意 mybatis-spring 的版本必须和 mybatis 主版本匹配,2.0.x 对应 mybatis 3.5.x,版本错配会报NoClassDefFoundError: org/mybatis/spring/SqlSessionFactoryBean。
配置文件分三块:applicationContext.xml 配数据源和 MyBatis 的 SqlSessionFactory,spring-mvc.xml 配组件扫描和视图解析器,web.xml 注册 DispatcherServlet 和 ContextLoaderListener。很多人把组件扫描全写在 applicationContext 里,导致 Controller 被扫描两次,启动时报 Bean 重复定义,正确做法是 Controller 只在 spring-mvc.xml 里扫,Service 和 Dao 在 applicationContext.xml 里扫。
2.3 用 MyBatis 逆向生成实体类和 Mapper 的最小命令
助农扶贫项目表多(用户、农户、农产品、订单、扶贫政策、留言),手写实体类效率低。常见做法是用 MyBatis Generator 逆向生成。先写 generatorConfig.xml,指定数据库连接和 targetPackage,然后执行:
mvn mybatis-generator:generate -Dmybatis.generator.configurationFile=src/main/resources/generatorConfig.xml执行后会在指定包下生成Xxx.java、XxxMapper.java、XxxMapper.xml三件套。参数上,targetProject要写绝对路径或相对模块路径,写错会报Target project does not exist。生成后不要直接用,XxxExample类在复杂查询时很啰嗦,我一般保留基础 CRUD,复杂联表查询手写到 Mapper.xml 里。另外生成时间字段会带上java.util.Date,如果数据库用 datetime,记得在实体类上加@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8"),否则小程序端收到的是时间戳,前端还得再转一次。
3. 助农扶贫业务模块的数据库设计与接口落地
3.1 六张核心表的字段设计与索引取舍
助农扶贫小程序的业务闭环是:农户入驻 → 发布农产品 → 消费者浏览下单 → 管理员审核扶贫资质。对应六张核心表:
| 表名 | 关键字段 | 索引建议 |
|---|---|---|
| user | id, openid, nickname, phone, role | openid 唯一索引 |
| farmer | id, user_id, real_name, id_card, audit_status | user_id 普通索引 |
| product | id, farmer_id, name, price, stock, category_id, status | farmer_id + status 联合索引 |
| order | id, user_id, product_id, quantity, total_price, order_status | user_id + create_time 联合索引 |
| policy | id, title, content, publish_time | 无 |
| comment | id, user_id, product_id, content, create_time | product_id 普通索引 |
字段设计上有两个血泪经验:一是openid必须建唯一索引,微信登录时同一个用户可能并发请求,没有唯一约束会插入两条用户记录;二是order表的order_status用 tinyint 而不是 varchar,0 待付款、1 已付款、2 已发货、3 已完成、4 已取消,用数字查询快且省空间。price和total_price用 decimal(10,2),别用 float,金额计算 float 会出现 0.1+0.2≠0.3 的玄学问题。
3.2 农产品列表接口:分页、分类筛选与状态过滤
小程序首页需要按分类展示上架农产品,接口要支持分页和分类筛选。Controller 层接收 page、size、categoryId 三个参数,Service 层组装查询条件,Mapper 层用 limit 分页。
// ProductController.java @RequestMapping("/api/product/list") @ResponseBody public Map<String, Object> list( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) Integer categoryId) { Map<String, Object> result = new HashMap<>(); // 只查上架状态(status=1)的商品 List<Product> list = productService.queryByPage(page, size, categoryId, 1); int total = productService.countByCondition(categoryId, 1); result.put("code", 200); result.put("data", list); result.put("total", total); return result; }逻辑说明:page和size给默认值防止前端不传参报错;categoryId用required=false,不传就查全部;status=1硬编码在 Service 调用里,保证下架商品不会出现在列表。参数上,分页计算offset = (page - 1) * size,在 Mapper.xml 里用limit #{offset}, #{size}。注意 MySQL 的 limit 在数据量大时深分页会慢,毕业设计数据量小无所谓,但如果想优化,可以用where id > #{lastId} limit #{size}的游标方式。
3.3 订单创建接口的事务与库存扣减
下单是最容易出问题的环节。用户点击下单,要同时做三件事:插入订单记录、扣减商品库存、返回订单号。这三步必须在一个事务里,否则库存扣了订单没生成,或者订单生成了库存没扣。
// OrderServiceImpl.java @Transactional(rollbackFor = Exception.class) public Map<String, Object> createOrder(Integer userId, Integer productId, Integer quantity) { Map<String, Object> result = new HashMap<>(); // 1. 查询商品并校验库存 Product product = productMapper.selectById(productId); if (product == null || product.getStock() < quantity) { result.put("code", 400); result.put("msg", "库存不足"); return result; } // 2. 扣减库存(带乐观锁版本号) int affected = productMapper.reduceStock(productId, quantity, product.getVersion()); if (affected == 0) { result.put("code", 400); result.put("msg", "下单失败,请重试"); return result; } // 3. 插入订单 Order order = new Order(); order.setUserId(userId); order.setProductId(productId); order.setQuantity(quantity); order.setTotalPrice(product.getPrice().multiply(new BigDecimal(quantity))); order.setOrderStatus(0); order.setCreateTime(new Date()); orderMapper.insert(order); result.put("code", 200); result.put("orderId", order.getId()); return result; }逻辑说明:@Transactional(rollbackFor = Exception.class)保证任何异常都回滚,默认只回滚 RuntimeException,加上 Exception 更保险。库存扣减用乐观锁,SQL 是update product set stock = stock - #{quantity}, version = version + 1 where id = #{id} and version = #{version} and stock >= #{quantity},affected == 0说明版本号被改过或库存不够,直接返回失败让用户重试。参数上,totalPrice用 BigDecimal 的 multiply,别用 double 乘法。注意订单号如果要用时间戳生成,记得加随机数后缀,否则同一秒下单会重复。
4. 微信小程序端对接 SSM 接口的实操细节
4.1 登录获取手机号与 openid 的完整链路
微信小程序的登录流程是:小程序端调wx.login拿 code → 传给后端 → 后端用 code + appid + secret 调微信接口换 openid 和 session_key → 后端生成自定义 token 返回小程序。获取手机号则是用户点击按钮触发getPhoneNumber,拿到 encryptedData 和 iv,后端解密。
// 小程序端 login.js wx.login({ success(res) { if (res.code) { wx.request({ url: 'https://你的域名/api/user/login', method: 'POST', data: { code: res.code }, success(r) { // 存 token 到本地 wx.setStorageSync('token', r.data.token); } }); } } });逻辑说明:wx.login的 code 只能用一次,五分钟过期;后端换 openid 的接口是https://api.weixin.qq.com/sns/jscode2session,参数 appid、secret、js_code、grant_type=authorization_code。注意 secret 绝对不能放在小程序端,必须后端持有。手机号解密用 AES-128-CBC,session_key 做密钥,iv 做偏移量,解密后是 JSON,包含 phoneNumber。常见坑是 session_key 过期导致解密失败,解决方法是解密失败时重新走一遍 login 流程。
4.2 顶部导航栏高度适配与自定义导航
微信小程序的顶部导航栏高度在不同机型上不一样,尤其是带刘海屏的 iPhone。用wx.getSystemInfoSync()拿 statusBarHeight 和导航栏高度,自定义导航栏时要把这两部分加起来。
// 计算导航栏总高度 const sysInfo = wx.getSystemInfoSync(); const statusBarHeight = sysInfo.statusBarHeight; const menuButton = wx.getMenuButtonBoundingClientRect(); // 导航栏高度 = 胶囊按钮上下间距 + 胶囊高度 const navBarHeight = (menuButton.top - statusBarHeight) * 2 + menuButton.height; const totalHeight = statusBarHeight + navBarHeight;逻辑说明:getMenuButtonBoundingClientRect拿到右上角胶囊按钮的位置,menuButton.top - statusBarHeight是胶囊上边距,乘以 2 加上胶囊高度就是导航栏高度。参数上,这个计算在 onLoad 里执行一次存到 data 里,别在渲染时反复算。注意开发者工具和真机的 statusBarHeight 可能不同,一定要真机验证,否则导航栏会偏移。
4.3 小程序请求封装与 token 过期处理
小程序端所有请求都要带 token,且 token 过期要自动跳登录。封装一个 request 函数统一处理:
// utils/request.js const BASE_URL = 'https://你的域名/api'; function request(options) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'content-type': 'application/json', 'token': wx.getStorageSync('token') || '' }, success(res) { if (res.data.code === 401) { // token 过期,清缓存跳登录 wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/login' }); reject(res.data); } else { resolve(res.data); } }, fail: reject }); }); }逻辑说明:用 Promise 包一层,调用时await request({url:'/product/list'})更清爽。header 里带 token,后端拦截器校验失败返回 401,前端统一处理跳登录。参数上,BASE_URL 必须是 https,微信小程序正式环境不允许 http。注意wx.request的并发上限是 10,页面同时发太多请求会排队,首页数据建议合并成一个接口返回。
5. 部署调试与答辩演示的避坑清单
5.1 后端部署到 Tomcat 的 war 包与路径问题
Maven 项目打包用mvn clean package,生成 target 下的 war 包,丢进 Tomcat 的 webapps 目录。访问路径是http://ip:8080/项目名/api/xxx。常见坑是项目名带版本号(如zhunong-1.0.war),访问时要写全,解决办法是在 pom.xml 的 finalName 里指定zhunong。
<build> <finalName>zhunong</finalName> </build>参数说明:finalName 决定 war 包名和访问路径。注意 Tomcat 的 server.xml 里如果配了 Context path,访问路径会变,毕业设计建议用默认配置。另外数据库连接信息在 jdbc.properties 里,部署到服务器要改成服务器的 MySQL 地址,别用 localhost。
5.2 小程序真机调试的域名与证书配置
微信小程序真机调试要求请求域名在后台配置过,且必须是 https。开发阶段可以在开发者工具里勾选「不校验合法域名」,但真机预览必须配置。常见坑是证书链不完整,浏览器能访问但小程序报request:fail,解决办法是用 SSL 检测工具检查证书链,补全中间证书。另外域名不能带端口,必须是 443。
5.3 答辩演示时的数据准备与接口兜底
答辩现场网络不可控,我一般提前准备两套方案:一是本地起 Tomcat + 本地 MySQL,小程序开发者工具连本地;二是把关键接口数据缓存到小程序端,断网也能展示列表和详情。数据准备上,至少造 20 条农产品、5 个农户、10 条订单,覆盖各种状态,演示时按「浏览 → 下单 → 查看订单 → 管理员审核」走一遍完整流程。注意别用真实手机号和个人信息,用测试数据。
6. 从能跑到能讲:源码二次开发与答辩加分技巧
拿到一份助农扶贫 SSM 源码,能跑起来只是及格,答辩要拿高分得做二次开发。我的习惯是先加一个数据看板,用 ECharts 在小程序端展示农产品销量 Top10、订单趋势、扶贫农户数量,后端加一个/api/stats/overview接口,用 MyBatis 的聚合查询返回数据。这个改动不大,但演示效果直观,答辩老师一眼能看到「数据分析」能力。
-- 销量 Top10 聚合查询 SELECT p.name, SUM(o.quantity) AS total_sold FROM `order` o JOIN product p ON o.product_id = p.id WHERE o.order_status IN (1,2,3) GROUP BY p.id, p.name ORDER BY total_sold DESC LIMIT 10;这条 SQL 的关键是order_status IN (1,2,3)只统计已付款、已发货、已完成的订单,待付款和已取消不算销量。参数上,如果数据量大,可以在 order 表的 create_time 上加范围条件,避免全表扫描。
第二个加分点是加操作日志。用 Spring AOP 切 Controller 层,记录谁在什么时间调了什么接口、参数是什么、返回是否成功,存到 log 表。答辩时演示「管理员审核农户」这个操作,日志表里能查到记录,体现系统的可追溯性。AOP 切面用@Around注解,切点表达式execution(* com.zhunong.controller..*(..)),注意别把登录接口的密码记进日志。
第三个技巧是准备一份接口文档。用 Swagger 或者手写 Markdown 都行,把每个接口的 URL、方法、参数、返回示例列清楚。答辩时老师问「你这个下单接口怎么设计的」,直接翻文档讲,比临场回忆靠谱。我一般还会在 README 里写清楚环境版本、启动步骤、常见问题,这样即使换台电脑也能快速跑起来。
最后说个我踩过的坑:别在答辩前一天改核心代码。我有次为了加个功能改了订单状态机,结果事务边界没处理好,演示时下单成功但库存没扣,当场翻车。后来我的习惯是答辩前三天冻结代码,只做数据准备和演示脚本演练。源码是死的,讲清楚设计思路和取舍才是活的。希望帮到你。
本文还有配套的精品资源,点击获取