农产品商城小程序毕设源码搭建与调试指南
简介本资源是一套完整的农产品商城微信小程序毕业设计项目面向计算机专业本科生、Java与小程序初学者及课程设计实践者提供从需求分析到部署上线的全流程参考方案。压缩包含1241个文件主体为131个Java后端业务逻辑文件、226个JS前端交互脚本、148个Vue组件、161个SVG图标资源及49个WXML/WXSS页面样式文件辅以1个SQL建表脚本和完整数据库文件整体大小19.59MB。已有90人学习下载体现其在教学实践场景中的实用价值。读者可直接导入Eclipse或IDEA运行Java后端JDK1.8Tomcat7配合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它包含三个明确分层/backendJava Spring Boot 项目、/miniprogram微信开发者工具可直接打开的小程序工程、/dbSQL 脚本。搭建不是「复制粘贴就完事」而是要验证每一层是否真正就绪。我一般会按「数据库 → 后端 → 小程序」顺序推进因为只要数据库没通后端必报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.ymlspring: datasource: url: jdbc:mysql://localhost:3306/farmshop?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue 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中useSSLfalse是为绕过本地 MySQL SSL 认证学生机基本不用开 SSLserverTimezoneAsia/Shanghai防止时间字段存成 UTC 导致订单时间错乱ddl-auto: validate表示启动时校验实体类与表结构是否一致不一致则报错——这是防止你改了 Java 实体但忘了同步 SQL 的后悔药。启动FarmShopApplication.java看到控制台输出Tomcat started on port(s): 8080后立刻用浏览器或 curl 测试接口curl http://localhost:8080/api/products?page1size10应返回 JSON 格式商品列表含id,name,price,stock字段。如果报50090% 是数据库连接失败如果报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商品项绑定了bindtapgoToDetailview classproduct-item wx:for{{products}} wx:keyid bindtapgoToDetail>goToDetail(e) { const productId e.currentTarget.dataset.id; wx.navigateTo({ url: /pages/product-detail/product-detail?id${productId} }); }跳转后product-detail.js的onLoad生命周期钩子捕获id参数并调用封装好的 APIonLoad(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 ResultProduct getProductById(PathVariable Long id) { Product product productService.findById(id); return Result.success(product); } }PathVariable自动解析 URL 中的idResult是自定义的统一响应包装类含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) { // status1 表示上架 throw new BusinessException(商品不存在或已下架); } return product; } }最终落到 MyBatis 的ProductMapper.xmlselect idselectById resultTypecom.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 ResultOrder submitOrder(RequestBody OrderSubmitDTO dto) { // 1. 校验购物车商品库存 ListCart 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设为POSTdata传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 :8080macOS/Linux或netstat -ano | findstr :8080Windows查 PID再kill -9 PIDmacOS/Linux或taskkill /PID PID /FWindows修改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 classsort-bar text classsort-btn {{sortType default ? active : }} bindtapsetSort>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 sortBysalesorderdesc; // 新增查询参数 } wx.request({ url: getApp().globalData.baseUrl url, success: (res) { this.setData({ products: res.data.content }); } }); }③ 后端ProductController.java接收新参数并传递给 ServiceGetMapping() public ResultPageProduct getProducts( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) String sortBy, // 新增 RequestParam(required false) String order) { // 新增 PageProduct result productService.findProducts(page, size, sortBy, order); return Result.success(result); }ProductService中用PageHelper.orderBy(sortBy order)若用 PageHelper或 MyBatisif testsortBy ! nullORDER BY ${sortBy} ${order}/if实现。价值点展示了你理解「前后端参数传递」「动态 SQL」「用户体验优化」三层逻辑。5.2 为订单状态增加「待发货 → 已发货」手动切换模拟真实运营动作源码订单状态可能是硬编码的status1待支付但真实场景需要管理员操作。改造如下① 数据库order_info表新增ship_status字段TINYINT默认 0ALTER TABLE order_info ADD COLUMN ship_status TINYINT DEFAULT 0 COMMENT 发货状态0-未发货1-已发货;② 后台管理页pages/admin/order-list.wxml为每行订单加「发货」按钮view wx:for{{orders}} wx:keyid text订单号{{item.orderNo}}/text text状态{{item.shipStatus 0 ? 未发货 : 已发货}}/text button wx:if{{item.shipStatus 0}} bindtapshipOrder>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 ResultString 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之前就已想清楚这三句话——因为毕设真正的终点从来不是代码跑起来而是你开始用工程师的思维解释世界。希望帮到你。本文还有配套的精品资源点击获取