☰
SpringBoot2+Vue3农产品预售系统实战:定金尾款与后端实现全解析
2026/9/30 3:23:33 网站建设 项目流程

做农产品预售系统这种毕业设计或者个人项目,最怕的不是写代码,而是拿到一个标题就开始闷头敲键盘。以“Java Web 农产品预售平台系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】”为例,这行标题基本把技术栈、业务方向、交付物都给你交代清楚了,但真正动手之前,你得先想明白一件事:这套系统到底在卖什么、预售怎么预、定金和尾款怎么处理。这篇文章我就以这个项目为蓝本,把自己做过的实操过程、踩过的坑、以及源码里最容易出问题的几个位置全部拆开讲一遍。无论你是准备拿它交毕设,还是想自己练手做一套完整的前后端分离项目,这套技术组合是目前 Java Web 方向里相当实用且好落地的一套,照着下面的思路走,基本能避开九成的新手坑。

1. 项目到底在做什么:先想清楚业务再动手

1.1 农产品预售的核心业务闭环

农产品预售和普通电商的即时下单不太一样,它的核心逻辑是“先收款、后发货”。平台上有农户或者商家发布预售农产品,比如阳澄湖大闸蟹、赣南脐橙、明前绿茶,用户看到预售信息后支付定金,等到农产品成熟或者达到发货条件后,再支付尾款,商家才安排发货。

这套业务闭环拆解下来,其实涉及了电商系统里最基础的几个模块,但也有它自己的特点。普通电商的订单是“商品直接购买”,预售系统的订单却是“定金订单 + 尾款订单”两段式,或者至少得在订单表里有个状态字段区分“已付定金、待付尾款、已付尾款、已发货、已完成、已退款”。很多做这个题目的人容易把预售做成普通商品加个预售标签,结算时直接全额付款,这种设计虽然也能跑通,但和“预售”二字的真实业务含义是有偏差的。我在设计这套系统时,采用的是“定金锁定库存、尾款确认发货”的模式,整个流程才算闭环。

不过讲实话,毕设项目里大多数情况不需要真正接支付网关,也不需要真的发货。所以实际开发时,我会把支付这块做成模拟支付,也就是在订单支付接口里直接生成一个已支付状态,把支付流水记录到 pay_log 表里,这样既能演示完整流程,又不需要申请商户号、配置支付证书。这个思路你如果用到自己的项目里,可以省掉大量与支付对接相关的麻烦事。

1.2 角色与权限拆解

这套系统的用户角色我分成四类:普通用户(买家)、农户/商家、平台管理员、系统运维人员。普通用户的核心操作是浏览预售商品、下单付定金、支付尾款、查看物流、申请退款;农户商家需要维护自己的农产品信息、管理预售批次、处理订单;平台管理员负责审核农产品、管理用户、处理纠纷;运维角色在毕设里基本可以不做权限控制,直接合并到管理员里。

权限这块我用的是 Spring Security 还是自定义拦截器?这里要说明一下,标题里没有明确出现 Spring Security,而项目技术栈是 SpringBoot2 + MyBatis-Plus,那么最轻量、最好解释的方案是使用自定义拦截器结合用户角色字段做权限校验,再配合前端路由守卫做菜单级控制。如果你硬要引入 Spring Security,功能是完整了,但学习成本和配置复杂度会陡然上升,对毕设答辩来说反而增加了被追问的风险。我在实际开发中,后端用拦截器校验登录状态,再通过注解或者简单判断校验角色,前端用 Vue Router 的路由守卫控制页面访问,这套组合在小型项目里完全够用,也好讲清楚。

1.3 这个系统的技术选型为什么是这套组合

SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0 这个组合在近年的毕业设计和中小型项目中非常常见,原因很直接:SpringBoot2 虽然已经不算最新,但生态稳定、资料海量、网上遇到问题一搜就能找到解决方案;Vue3 是目前前端框架的主流方向,组合式 API 写起来比 Vue2 的选项式 API 更清爽;MyBatis-Plus 解决了 MyBatis 单表 CRUD 的重复劳动问题,代码量少一大截;MySQL8.0 则在性能、窗口函数、JSON 支持上都比 5.7 有优势。

选这套组合而不是 SpringBoot3 + MyBatis-Plus 的原因也很现实:SpringBoot3 强制要求 JDK17 及以上,而很多学校的实验环境或者学生本机还停留在 JDK8,SpringBoot2 配 JDK8 是最舒服的组合,重编译快、兼容好、网上资料最多。我在实操中一直强调一个原则:毕设和中小型项目不需要追求最前沿,而是要追求“自己完全能掌控、出现任何问题都能解决”,这套组合恰恰符合这个原则。

2. 后端实现:SpringBoot2 + MyBatis-Plus + MySQL8.0 的正确打开方式

2.1 SpringBoot2 里的核心设计

SpringBoot2 的核心价值在于自动配置和 starter 机制,你不用再像以前 SSM 时代那样写一堆 XML 配置。但这不意味着你可以完全不理解底层,至少这几个关键点你必须清楚。

第一,启动类的位置必须放在所有需要扫描的包的外层。我见过很多新手把启动类放在 com.example.demo 下,然后把 controller、service、mapper 分别放在 com.example.controller、com.example.service,这样做确实也能跑,但一旦你加了自定义注解、拦截器、配置类,非常容易出现“组件扫描不到”的诡异问题。我的经验是:统一把包名设计成 com.preSale(或者是 com.agriculture.preSale 这类),启动类放在 com.preSale 下,controller、service、mapper、entity 全部放在 com.preSale 的子包里,这样 SpringBoot 的默认组件扫描才能完整覆盖。

第二,统一响应体的设计。写接口时如果每个方法都返回不同的数据结构,前端对接时会疯掉。我在这套系统里定义了一个 Result 类,统一包装 code、message、data 三个字段,所有接口都返回 Result 对象。这个类需要是泛型类,这样查询列表和查询单条记录时都能复用。

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

第三,全局异常处理。如果你不在后端做统一异常捕获,数据库字段超长、空指针、SQL 语法错误这些异常会直接把堆栈信息抛给前端,既难看又暴露系统细节。我习惯加一个 @RestControllerAdvice 注解的类,配合 @ExceptionHandler 方法,把常见的异常统一转换成 Result.error 返回。

2.2 MyBatis-Plus 使用要点

MyBatis-Plus 是 MyBatis 的增强工具,它可以让你在写单表操作时完全不写 SQL,直接调用 BaseMapper 提供的方法,比如 selectById、selectList、insert、updateById、deleteById。如果你用的是它的 LambdaQueryWrapper,连字符串字段名都不用写,编译期就能避免字段拼写错误。

在这套系统里,MyBatis-Plus 最核心的用法其实集中在三个方面:第一个是分页查询,第二个是条件构造器,第三个是自动填充。

分页查询这块是个老坑。MyBatis-Plus 的分页需要显式配置一个分页插件,很多人忘了这一步,结果一调用 selectPage 方法就发现返回的数据是全部数据而不是分页数据。原因就是没有注册 PaginationInnerInterceptor。我在代码里是这么配的:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

条件构造器的价值在于动态查询条件,特别是农产品列表页里经常有“按分类筛选、按价格排序、按预售状态筛选”这类需求,用 LambdaQueryWrapper 可以在 Service 层直接组装查询条件,不用写一堆 XML 映射。

自动填充处理的是创建时间、更新时间这类公共字段。如果每个表都手动 setCreateTime,不仅繁琐,还容易漏。我的做法是在实体类字段上加上 @TableField(fill = FieldFill.INSERT) 和 @TableField(fill = FieldFill.INSERT_UPDATE) 注解,然后写一个 MetaObjectHandler 的实现类,统一处理 insert 和 update 时的字段填充。

2.3 MySQL8.0 不要踩的配置坑

MySQL8.0 和 5.7 最大的差异之一就是认证插件变了。MySQL8.0 默认的认证插件是 caching_sha2_password,而 SpringBoot2 里如果你用的驱动版本太老,就会出现连接报错,提示“Unable to load authentication plugin 'caching_sha2_password'”。解决办法有两个:一个是在 pom.xml 里把 mysql-connector-java 的版本提到 8.0.2x 以上,另一个是在创建数据库用户时指定 mysql_native_password 插件。我两个方案都试过,最省事的是直接使用高版本驱动。

还有一个常见的坑是时区问题。如果你的 JDBC 连接串没加 serverTimezone 参数,会报 from a java.text.SimpleDateFormat 相关的错误或者直接提示 serverTimezone 必须要指定。我用的连接串模板是这样的:

jdbc:mysql://localhost:3306/pre_sale_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

allowPublicKeyRetrieval=true 这个参数很多人不知道,MySQL8.0 使用 caching_sha2_password 认证时,如果连接串里不加这个,某些场景下会抛出 Public Key Retrieval is not allowed 异常。

再有一点,MySQL8.0 默认字符集是 utf8mb4,这个比 utf8 好,能存 emoji 和生僻字,不要画蛇添足去改库的字符集。建库语句我建议直接写:

CREATE DATABASE pre_sale_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

3. 前端实现:Vue3 组合式 API 下的用户界面

3.1 Vue3 相对 Vue2 的核心变化

Vue3 最核心的变化是引入了组合式 API(Composition API),也就是 setup 函数。Vue2 的选项式 API 把数据、方法、生命周期拆分成不同区域,写一个稍微复杂一点的页面,代码会散布在 data、methods、watch、computed 里面,阅读时需要在文件中来回跳。Vue3 的组合式 API 则可以按功能去组织代码,比如把登录相关的状态、计算属性、方法都写在一个功能分区里,维护起来清爽很多。

比如一个简单的商品列表页面,Vue2 里你需要写 data 里声明 goodsList、total、queryParams,在 methods 里定义 getList、handleSearch,在 mounted 里调用 getList。Vue3 的 setup 里可以这样写:

import { ref, reactive, onMounted } from 'vue' import { getGoodsList } from '@/api/goods' export default { setup() { const goodsList = ref([]) const queryParams = reactive({ pageNum: 1, pageSize: 10, categoryId: '', status: '' }) const total = ref(0) const getList = async () => { const res = await getGoodsList(queryParams) goodsList.value = res.data.records total.value = res.data.total } onMounted(() => { getList() }) return { goodsList, queryParams, total, getList } } }

如果你写的是项目源码,可以不使用

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

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

立即咨询