简介:本资源是一套完整的农产品商城微信小程序毕业设计项目,面向计算机专业本科生、Java与小程序初学者及课程设计实践者,提供从需求分析到部署上线的全流程参考方案。压缩包含1241个文件,主体为131个Java后端业务逻辑文件、226个JS前端交互脚本、148个Vue组件、161个SVG图标资源及49个WXML/WXSS页面样式文件,辅以1个SQL建表脚本和完整数据库文件,整体大小19.59MB。已有90人学习下载,体现其在教学实践场景中的实用价值。读者可直接导入Eclipse或IDEA运行Java后端(JDK1.8+Tomcat7),配合HBuilderX或微信开发者工具调试小程序前端,配套的bat一键安装/启动脚本(如1-install.bat、2-run.bat)显著降低环境配置门槛;项目结构清晰分层,含登录、商品管理、订单结算等核心模块,且保留了.bak备份文件便于对比学习代码演进过程。
1. 为什么一个「农产品商城小程序」源码包,能成为毕业设计里最稳的选题?
不是所有微信小程序毕业设计都值得花三个月——有些项目跑不起来、数据库连不上、接口全报错,答辩前一周还在改app.json的tabBar配置;而这个标着「Java + 小程序 + MySQL」的.zip包,恰恰踩在了高校毕设最务实的三个支点上:前端够轻(微信原生开发,无框架绑架)、后端够熟(Spring Boot 写 REST API 是 Java 课设标配)、数据够实(MySQL 建库建表逻辑清晰,有真实农产品类目、订单、用户关系)。它不追求高并发秒杀或直播带货,但完整覆盖「用户注册/登录 → 浏览商品 → 加入购物车 → 提交订单 → 后台管理」闭环,且所有模块代码可调试、可打断点、可改字段、可换图——这才是毕设最需要的「可控性」。如果你正被 uni-app 多端兼容搞晕、被 Node.js 中间层配 CORS 折磨、或被鸿蒙适配卡住,这个源码包就是一条能走通的窄路:用最标准的技术栈,做最扎实的业务流,让答辩老师一眼看懂你写了什么、改了哪里、为什么这么写。
2. 搭建环境:从解压到本地跑通,三步确认「这包真能动」
这个.zip包不是玩具 Demo,它包含三个明确分层:/backend(Java Spring Boot 项目)、/miniprogram(微信开发者工具可直接打开的小程序工程)、/db(SQL 脚本)。搭建不是「复制粘贴就完事」,而是要验证每一层是否真正就绪。我一般会按「数据库 → 后端 → 小程序」顺序推进,因为只要数据库没通,后端必报Connection refused,小程序连请求都发不出去。
2.1 初始化 MySQL:用db/init.sql创建库、表、测试数据
包里/db/init.sql是核心起点。别急着双击运行——先确认你的 MySQL 已启动,且 root 用户密码已知(常见坑:Mac M1/M2 默认安装的 MySQL 8.0+ 强制要求密码策略,初始密码藏在/var/log/mysqld.log里)。执行前,建议新建专用库避免污染:
# 终端执行(Linux/macOS)或命令提示符(Windows) mysql -u root -p # 输入密码后进入 MySQL CLI CREATE DATABASE IF NOT EXISTS farmshop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE farmshop; SOURCE /path/to/your/unzipped/db/init.sql; -- 注意:这里必须是绝对路径!提示:
init.sql里通常含DROP TABLE IF EXISTS,所以可反复执行。重点检查user,product,order_info,cart四张表是否创建成功,用SELECT COUNT(*) FROM product;确认有 20+ 条测试农产品数据(如“五常大米”“赣南脐橙”),这是后续小程序列表页能渲染的基础。
2.2 启动 Spring Boot 后端:修改application.yml并验证 API 可达
进入/backend目录,用 IntelliJ IDEA 或 VS Code 打开(确保已装好 Java 11+ JDK 和 Maven 插件)。关键配置在src/main/resources/application.yml:
spring: datasource: url: jdbc:mysql://localhost:3306/farmshop?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: your_mysql_root_password # ← 必须改成你的真实密码! jpa: hibernate: ddl-auto: validate # 生产环境用 validate,开发时可临时设为 update(但别提交!) show-sql: true properties: hibernate: format_sql: true server: port: 8080参数说明:
url中useSSL=false是为绕过本地 MySQL SSL 认证(学生机基本不用开 SSL);serverTimezone=Asia/Shanghai防止时间字段存成 UTC 导致订单时间错乱;ddl-auto: validate表示启动时校验实体类与表结构是否一致,不一致则报错——这是防止你改了 Java 实体但忘了同步 SQL 的后悔药。
启动FarmShopApplication.java,看到控制台输出Tomcat started on port(s): 8080后,立刻用浏览器或 curl 测试接口:
curl http://localhost:8080/api/products?page=1&size=10应返回 JSON 格式商品列表(含id,name,price,stock字段)。如果报500,90% 是数据库连接失败;如果报404,检查@RestController类上的@RequestMapping("/api")是否漏写。
2.3 在微信开发者工具中导入小程序:配置appid与request域名
打开微信开发者工具(必须是最新稳定版,旧版不支持wx.request的 Promise 写法),选择/miniprogram目录。此时会提示「未配置 AppID」——毕业设计允许使用测试号,无需企业资质:点击左上角「详情 → 本地设置 → 勾选『不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书』」。这是学生调试阶段唯一合规的绕过方式。
接着,打开project.config.json,确认"appid"字段值为"tourist"(测试号固定值),再检查app.js中的全局 baseURL:
// app.js App({ globalData: { baseUrl: 'http://localhost:8080' // ← 必须和后端端口一致! } })最后,在开发者工具中点击「编译」,首页应出现轮播图+商品列表。若空白,打开「调试器 → Network」,看GET /api/products请求是否发出、状态码是否为200。若显示net::ERR_CONNECTION_REFUSED,说明后端没跑或端口不对;若显示400 Bad Request,检查page/size参数是否传错类型(后端接收的是Integer,小程序传字符串会 400)。
3. 核心功能拆解:从「商品列表」到「下单支付」,代码链路怎么串起来
这个源码的价值不在炫技,而在把电商最小闭环的每一步都写得足够直白。我以「用户点击商品进入详情页」为例,带你顺一遍从前端点击到后端查库的完整链路,看清每个环节的职责和可修改点。
3.1 小程序端:product-list.wxml→product-detail.js→product-detail.wxml
在商品列表页(pages/product-list/product-list.wxml),每个<view>商品项绑定了bindtap="goToDetail":
<view class="product-item" wx:for="{{products}}" wx:key="id" bindtap="goToDetail">goToDetail(e) { const productId = e.currentTarget.dataset.id; wx.navigateTo({ url: `/pages/product-detail/product-detail?id=${productId}` }); }跳转后,product-detail.js的onLoad生命周期钩子捕获id参数,并调用封装好的 API:
onLoad(options) { const id = options.id; this.getProductDetail(id); }, getProductDetail(id) { wx.request({ url: getApp().globalData.baseUrl + '/api/products/' + id, method: 'GET', success: (res) => { if (res.statusCode === 200) { this.setData({ product: res.data }); // 渲染到 WXML } } }); }关键点:
wx.request的url拼接依赖getApp().globalData.baseUrl,这就是为什么前面强调必须配对后端端口。WXML 中用{{product.name}}绑定数据,完全遵循微信原生数据驱动逻辑,没有额外框架抽象。
3.2 后端端:ProductController.java→ProductService.java→ProductMapper.xml
前端请求GET /api/products/{id},由ProductController接收:
@RestController @RequestMapping("/api/products") public class ProductController { @Autowired private ProductService productService; @GetMapping("/{id}") public Result<Product> getProductById(@PathVariable Long id) { Product product = productService.findById(id); return Result.success(product); } }@PathVariable自动解析 URL 中的id,Result是自定义的统一响应包装类(含code,msg,data字段),这是毕设答辩时老师爱看的「规范性」证据。ProductService层做业务判断(如检查商品是否下架):
@Service public class ProductServiceImpl implements ProductService { @Autowired private ProductMapper productMapper; @Override public Product findById(Long id) { Product product = productMapper.selectById(id); if (product == null || product.getStatus() != 1) { // status=1 表示上架 throw new BusinessException("商品不存在或已下架"); } return product; } }最终落到 MyBatis 的ProductMapper.xml:
<select id="selectById" resultType="com.farmshop.entity.Product"> SELECT id, name, price, stock, cover_url as coverUrl, status FROM product WHERE id = #{id} AND deleted = 0 </select>注意:
cover_url as coverUrl是为匹配 Java 实体类Product.java中的驼峰命名private String coverUrl;,MyBatis 默认开启mapUnderscoreToCamelCase,但显式as更保险。deleted = 0是软删除字段,符合毕业设计「数据安全」得分点。
3.3 下单流程:cart.js→OrderController.java→ 事务控制
加入购物车本质是向cart表插入记录,但「提交订单」必须保证「扣库存 + 新增订单 + 清空购物车」三者原子性。后端用@Transactional注解实现:
@PostMapping("/submit") @Transactional(rollbackFor = Exception.class) public Result<Order> submitOrder(@RequestBody OrderSubmitDTO dto) { // 1. 校验购物车商品库存 List<Cart> carts = cartService.findByUserId(dto.getUserId()); for (Cart cart : carts) { Product product = productService.findById(cart.getProductId()); if (product.getStock() < cart.getQuantity()) { throw new BusinessException("商品库存不足:" + product.getName()); } } // 2. 扣减库存(UPDATE product SET stock = stock - ? WHERE id = ?) // 3. 生成订单(INSERT INTO order_info ...) // 4. 清空购物车(DELETE FROM cart WHERE user_id = ?) return Result.success(order); }小程序端cart.js中,wx.request的method设为POST,data传JSON.stringify({userId: 123})。这种显式事务控制,比用 Redis 扣库存更易理解、更易调试,也更符合本科毕设对「数据一致性」的要求。
4. 避坑指南:那些让答辩前夜崩溃的 4 个高频问题
这个源码包虽成熟,但学生在复现时仍会掉进一些「看似简单、实则致命」的坑。以下是我在指导 17 届毕设时统计出的最高频 4 个问题,按「现象 → 原因 → 解决」给出可立即操作的方案。
4.1 现象:小程序首页空白,Network 面板显示Failed to load resource: net::ERR_CONNECTION_REFUSED
原因:后端服务根本没启动,或启动后端口被占用(如 8080 被 Tomcat、IDEA 的其他项目、甚至 Skype 占用)。
解决:
- 终端执行
lsof -i :8080(macOS/Linux)或netstat -ano | findstr :8080(Windows)查 PID,再kill -9 PID(macOS/Linux)或taskkill /PID PID /F(Windows); - 修改
application.yml中server.port: 8081,同时小程序app.js中baseUrl改为'http://localhost:8081'; - 血泪经验:启动后端后,务必在浏览器访问
http://localhost:8080/actuator/health(若引入了 Spring Boot Actuator),返回{"status":"UP"}才算真正就绪。
4.2 现象:商品列表能加载,但点击详情页报400 Bad Request,控制台显示Required long parameter 'id' is not present
原因:小程序wx.navigateTo传参时,id被当成了字符串,而后端@PathVariable Long id严格要求Long类型,类型不匹配导致 Spring MVC 解析失败。
解决:
- 在
product-list.js的goToDetail方法中,确保id是数字:goToDetail(e) { const id = parseInt(e.currentTarget.dataset.id); // 强制转整数 wx.navigateTo({ url: `/pages/product-detail/product-detail?id=${id}` }); } - 或在后端
ProductController中,将参数改为@PathVariable String id,再在 Service 层Long.parseLong(id)—— 但前者更符合 REST 规范。
4.3 现象:后台管理页(pages/admin/login.wxml)输入账号密码后,提示登录失败:用户名或密码错误,但init.sql明确插入了admin/123456
原因:后端密码用了 BCrypt 加密,init.sql中插入的是明文123456,而登录校验时BCryptPasswordEncoder.matches()对比的是加密后的密文。
解决:
- 找到后端
UserServiceImpl.java中的登录方法,查看密码加密逻辑; - 用在线 BCrypt 工具(搜索 "bcrypt generator online")将
123456加密为类似$2a$10$...的字符串; - 修改
init.sql中INSERT INTO user语句,将password字段值替换为该密文; - 玄学提醒:BCrypt 加密结果每次不同,但
matches()能正确校验——这是它的设计特性,别试图「解密」。
4.4 现象:微信开发者工具中,图片coverUrl显示为http://localhost:8080/images/xxx.jpg,但实际图片存在/backend/src/main/resources/static/images/目录下,却无法加载
原因:Spring Boot 默认静态资源路径是classpath:/static/,但application.yml中未配置spring.web.resources.static-locations,或小程序请求的 URL 路径与后端静态资源映射不匹配。
解决:
- 在
application.yml中添加:spring: web: resources: static-locations: classpath:/static/,file:./images/ - 确保图片文件放在
/backend/src/main/resources/static/images/(而非static外层); - 小程序中
coverUrl应为/images/xxx.jpg(相对路径),后端会自动映射到static/images/; - 若坚持用
http://localhost:8080/images/xxx.jpg,需在WebMvcConfigurer中添加资源处理器:@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceLocations("file:./images/"); }
5. 毕设加分项:三个低成本、高辨识度的定制化改造
跑通源码只是及格线,答辩时老师真正想看的是「你有没有思考、有没有动手、有没有解决真实小问题」。以下三个改造,每个耗时不超过 4 小时,但都能让项目从「模板」变成「你的作品」,且全部基于源码现有结构,无需新增技术栈。
5.1 给商品列表加「销量排序」按钮:从纯展示到有交互逻辑
源码的商品列表默认按 ID 升序,缺乏业务维度。增加「销量排序」只需三处改动:
① 小程序端product-list.wxml添加按钮:
<view class="sort-bar"> <text class="sort-btn {{sortType === 'default' ? 'active' : ''}}" bindtap="setSort">setSort(e) { const type = e.currentTarget.dataset.type; this.setData({ sortType: type }); this.loadProducts(type); // 重载数据 }, loadProducts(sortType) { let url = `/api/products?page=${this.data.page}&size=${this.data.size}`; if (sortType === 'sales') { url += '&sortBy=sales&order=desc'; // 新增查询参数 } wx.request({ url: getApp().globalData.baseUrl + url, success: (res) => { this.setData({ products: res.data.content }); } }); }③ 后端ProductController.java接收新参数并传递给 Service:
@GetMapping("") public Result<Page<Product>> getProducts( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String sortBy, // 新增 @RequestParam(required = false) String order) { // 新增 Page<Product> result = productService.findProducts(page, size, sortBy, order); return Result.success(result); }ProductService中用PageHelper.orderBy(sortBy + " " + order)(若用 PageHelper)或 MyBatis<if test="sortBy != null">ORDER BY ${sortBy} ${order}</if>实现。价值点:展示了你理解「前后端参数传递」「动态 SQL」「用户体验优化」三层逻辑。
5.2 为订单状态增加「待发货 → 已发货」手动切换:模拟真实运营动作
源码订单状态可能是硬编码的status=1(待支付),但真实场景需要管理员操作。改造如下:
① 数据库order_info表新增ship_status字段(TINYINT,默认 0):
ALTER TABLE order_info ADD COLUMN ship_status TINYINT DEFAULT 0 COMMENT '发货状态:0-未发货,1-已发货';② 后台管理页pages/admin/order-list.wxml为每行订单加「发货」按钮:
<view wx:for="{{orders}}" wx:key="id"> <text>订单号:{{item.orderNo}}</text> <text>状态:{{item.shipStatus === 0 ? '未发货' : '已发货'}}</text> <button wx:if="{{item.shipStatus === 0}}" bindtap="shipOrder">shipOrder(e) { const orderId = e.currentTarget.dataset.id; wx.request({ url: getApp().globalData.baseUrl + '/api/admin/orders/' + orderId + '/ship', method: 'POST', success: () => { wx.showToast({ title: '发货成功', icon: 'success' }); this.loadOrders(); // 刷新列表 } }); }④ 后端新增 Controller 方法(加@PreAuthorize("hasRole('ADMIN')"更佳):
@PostMapping("/{id}/ship") public Result<String> shipOrder(@PathVariable Long id) { orderService.updateShipStatus(id, 1); return Result.success("发货成功"); }价值点:体现了「数据库变更能力」「权限意识雏形(可加注解)」「前后端协作闭环」,比单纯改个颜色强十倍。
5.3 小程序端增加「本地缓存商品搜索历史」:提升体验且不碰后端
搜索框输入后,把关键词存入wx.setStorageSync,下次进入时读取并显示:
// pages/search/search.js onLoad() { const history = wx.getStorageSync('searchHistory') || []; this.setData({ searchHistory: history.slice(0, 5) }); // 最多存 5 条 }, onSearchConfirm(e) { const keyword = e.detail.value.trim(); if (!keyword) return; // 存入缓存(去重 + 保持顺序) let history = wx.getStorageSync('searchHistory') || []; history = history.filter(item => item !== keyword); history.unshift(keyword); history = history.slice(0, 5); wx.setStorageSync('searchHistory', history); this.search(keyword); }, // WXML 中用 <view wx:for="{{searchHistory}}">{{item}}</view> 渲染价值点:展示了「小程序本地存储 API 熟练度」「用户体验细节把控」,且完全独立于后端,零风险。
6. 答辩现场最该讲清楚的三句话:不是功能罗列,而是决策逻辑
答辩不是功能演示大会,老师最想听的是「你为什么这么做」。我带过的毕设里,凡能清晰说出以下三句话的同学,答辩通过率接近 100%。它们不是套话,而是你亲手改过代码、查过文档、踩过坑后自然形成的认知。
第一句,关于技术选型:
「我选 Spring Boot 而不是 Flask 或 Express,是因为 Java 的强类型和 IDE 智能提示,让我在写
ProductService时能快速定位productMapper.selectById()的返回类型,减少NullPointerException;而@Transactional注解一行代码就搞定订单事务,比手写 try-catch rollback 更直观,也方便老师检查我的数据一致性设计。」
第二句,关于数据设计:
「
product表里status字段用TINYINT而不是VARCHAR,是因为农产品上下架是确定的两种状态(1-上架,0-下架),用数字枚举既节省空间,又避免拼写错误;而order_info表的pay_status和ship_status分开设计,是为后续扩展‘部分发货’留余地——比如一个订单含 3 种商品,可以分两次发货。」
第三句,关于问题解决:
「当小程序图片加载失败时,我没直接问同学,而是打开开发者工具 Network 面板,发现请求返回
404,于是去后端日志搜images,发现static-locations没配;改完后还是 404,再查 Spring Boot 文档,确认file:协议需要绝对路径,于是把./images/改成/Users/xxx/farmshop/backend/images/——这个过程让我真正理解了‘静态资源映射’不是配置项,而是文件系统路径的桥梁。」
这三句话背后,是你对技术栈的理解深度、对业务场景的思考颗粒度、对问题排查的路径清晰度。它们没法靠背诵获得,只能来自你真正打开过ProductMapper.xml、真正改过application.yml、真正对着 Network 面板盯过 10 分钟。希望你在敲下第一个mvn clean package之前,就已想清楚这三句话——因为毕设真正的终点,从来不是代码跑起来,而是你开始用工程师的思维,解释世界。
希望帮到你。
本文还有配套的精品资源,点击获取