Spring Boot+Vue前后端分离农产品电商网站毕业设计源码解析与实战

发布时间:2026/10/5 12:05:59
Spring Boot+Vue前后端分离农产品电商网站毕业设计源码解析与实战 简介本资源为基于Java的农产品网站设计与开发完整毕业设计方案面向计算机相关专业学生及Java Web初学者解决农产品在线展示、选购与后台管理等需求。项目采用JDK 1.8与MySQL 5.7及以上版本支持eclipse或idea开发涵盖基础信息管理、农产品展示、网上购物及用户管理等核心模块形成完整电商业务闭环。压缩包共300个文件约25.35MB主要包含97个Java源码、23个JSP页面、29个JavaScript文件、33个CSS样式及数据库SQL脚本等附带说明文档doc/docx和演示录像便于理解系统架构与运行流程。内容预览显示前端基于bootstrap与layer等组件界面开发较为规范适合课程设计、毕业设计参照或二次开发学习。目前已有28人学习下载对于需要完整参考项目并快速搭建农产品交易平台的读者具有较高的实用价值。1. 农产品网站毕业设计源码到手先别急着解压跑起来很多同学拿到这份农产品网站的设计与开发源码压缩包第一反应都是解压、找 README、启动后端然后看着控制台报错发呆。我见过太多人卡在这一步项目能跑起来却不知道怎么讲清楚或者写论文的时候发现功能描述和代码对不上。其实这类 java 毕业设计的核心不在页面做得多花哨而在它把用户、商品、订单这条电商主线完整串了起来再配上前后端分离的工程结构、说明文档和 LW 论文正好对应毕业设计的评分维度。下面我按这套源码常见的结构和实现套路从技术选型、数据库设计、接口联调一路讲到答辩前要排掉的坑中间给的都是可以直接抄作业的代码和参数。2. 先理清方案再动手农产品网站的功能边界与技术选型拿到源码先别急着跑先花半小时把功能边界和技术路线捋清楚。这半小时决定了你后面答辩时是“照着念代码”还是“讲设计思路”。2.1 把毕业设计的评分点映射成功能模块毕业设计评分通常看四个维度需求分析是否完整、系统设计是否合理、功能实现是否可用、文档与答辩是否自洽。这套农产品网站源码能撑起一张完整的评分矩阵是因为它有两个端、三条业务线。前台给访客和普通用户用注册登录、农产品分类浏览、商品列表与详情、加入购物车、下单、个人中心查看订单。后台给管理员用商品管理、分类管理、订单管理、用户管理。为了演示效果好一般还带一个公告或轮播图管理。评分维度对应模块在代码里的落点需求分析用户角色划分、购物流程README / LW 论文中的用例图系统设计数据库表关系、前后端分离结构SQL 脚本、后端 entity 与 mapper功能实现登录鉴权、商品 CRUD、下单事务Controller Service 代码文档答辩运行说明、接口说明、表结构说明说明文档 doc / LW这套映射不是写论文时才编的而是从一开始就应该照着去读源码。如果你的包里没有分类管理也不用慌农产品网站最小可用集是“商品展示 下单 后台订单处理”其他模块属于加分项。2.2 前后端分离项目实战的技术选型Spring Boot MyBatis-Plus Vue“完整前后端”这几个字在 java 毕业设计里最常见的技术组合是后端 Spring Boot 2.x MyBatis-Plus MySQL前端 Vue 2 或 Vue 3 Element UI Axios。如果你见过若依框架前后端分离的项目这套源码的骨架其实就是它的简化版把权限、代码生成这些重东西去掉只留业务主干。选 MyBatis-Plus 而不是原生 MyBatis理由很简单单表 CRUD 不用手写 SQLBaseMapper直接给selectPage、insert、updateById能让后端代码量砍掉三分之一。对于答辩项目少写 SQL 意味着少出 bug也意味着你能把精力放在事务、鉴权这些真正会被提问的点上。Spring Boot 2.x 的版本我建议选 2.3.x 或 2.7.x 这类比较成熟的线别追 3.x 新版本。原因是 MyBatis-Plus 的 starter 对 Spring Boot 3 的兼容需要额外配mybatis-plus-spring-boot3-starter很多源码包里的依赖还是按 2.x 写的你换了版本会踩一堆 ClassNotFound 的坑。JDK 用 1.8 或 11MySQL 用 5.7 或 8.0 都行但连接参数有区别后面第 5 章我会把 8.0 的时区坑单独列出来。这套选型不是唯一答案却是毕业设计场景下风险最低、最容易在三天内跑通的组合。2.3 解压 zip 后先找到这四样东西数据库脚本、配置、接口地址与说明文档一套规范的源码 zip 包解压后应该能数出四类东西后端工程、前端工程、数据库脚本、说明文档。如果命名比较规范你会在根目录看到sql文件夹、doc文件夹和两个工程目录。但实际下载到的包经常有二级嵌套比如外面套一层和项目名同名的文件夹所以在 IDE 里导入之前先手动翻一下目录层级。unzip 农产品网站.zip -d agricultural-demo cd agricultural-demo find . -maxdepth 3 -type f | head -50这条命令把 zip 解压到agricultural-demo目录然后列出三层以内的文件。你重点找四样东西.sql结尾的数据库初始化脚本、application.yml或application.properties配置文件、前端src/api或src/utils/request.js接口封装文件、README或LW文档。文件类型常见位置打开后看什么数据库脚本sql/或根目录建库语句、表名、初始管理员账号后端配置src/main/resources/数据源地址、端口、文件上传路径前端接口src/api/baseURL、Token 头字段名说明文档doc/或根目录运行步骤、默认账号密码这里有个血泪经验不要用压缩软件直接双击预览.sql文件更不要用记事本打开带中文注释的脚本编码不一致会把注释变成乱码看起来像脚本坏了。用 IDE 或 VS Code 以 UTF-8 编码打开确认建库语句的字符集是utf8mb4。这一步确认清楚后面能少折腾两个小时。3. 数据库与后端实现把农产品的增删改查讲出设计感后端是答辩时被问得最细的部分也是很多人只说得出“CRUD”三个字的地方。这一章把三张核心表、登录鉴权、下单事务这三块讲透讲完你至少能扛住三个连环追问。3.1 三张核心表的设计用户、农产品、订单与订单明细农产品网站要撑起电商闭环最少需要四张表用户表user、农产品表goods、订单表orders、订单明细表order_item。分类可以直接在goods表里放一个category_id字段也可以单独拆一张category表。毕设场景拆出来更好论文里能多画一张 E-R 图。CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(255) NOT NULL COMMENT BCrypt密文, nickname VARCHAR(50) DEFAULT 买家 COMMENT 显示名称, phone VARCHAR(20) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 1普通用户 2管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL COMMENT 分类ID, name VARCHAR(100) NOT NULL COMMENT 农产品名称, price DECIMAL(10,2) NOT NULL COMMENT 单价单位元, stock INT NOT NULL DEFAULT 0 COMMENT 库存, image VARCHAR(255) DEFAULT NULL COMMENT 图片URL, description TEXT COMMENT 详情, sales INT DEFAULT 0 COMMENT 销量, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT农产品表; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号防重复提交, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, address VARCHAR(255) NOT NULL COMMENT 收货地址快照, status TINYINT DEFAULT 0 COMMENT 0待发货 1已发货 2已完成 3已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, goods_id INT NOT NULL, goods_name VARCHAR(100) NOT NULL COMMENT 商品名称快照, price DECIMAL(10,2) NOT NULL COMMENT 下单时单价快照, quantity INT NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;字段设计里有三个容易被追问的点。第一金额用DECIMAL(10,2)不能用FLOAT浮点算总价会出现0.1 0.2 0.30000000000000004这类问题这在 java 面试题里也是常客。第二order_item冗余了goods_name和price理由是商品信息会变订单明细必须保留下单那一刻的快照不然以后卖 5 块钱的苹果涨到 6 块历史订单全部跟着错。第三orders.order_no加了唯一索引这是后端拦截重复提交的关键后面 3.3 会细讲。3.2 注册登录与 JWT 拦截器一条能应付答辩的权限链路农产品网站的权限模型很简单游客能看商品但不能下单普通用户可以下单、看自己的订单管理员能进后台。常见的实现是 JWT 拦截器Token 里直接带用户 ID 和角色。public class JwtUtil { private static final String SECRET agriculture-demo-secret; private static final long EXPIRE 24 * 60 * 60 * 1000L; public static String createToken(Integer userId, Integer role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }这里SECRET是签发密钥写在代码里是方便演示真实项目要放到配置文件中。EXPIRE是有效期 24 小时答辩时可以主动说这个值应该按业务调比如管理员后台可以 2 小时失效。Token 里只放userId和role不放大对象这是 JWT 的通用约定。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { if (OPTIONS.equalsIgnoreCase(req.getMethod())) { return true; } String token req.getHeader(Authorization); if (token null || token.isEmpty()) { resp.sendError(401, 未登录); return false; } try { Claims claims JwtUtil.parseToken(token); req.setAttribute(userId, claims.get(userId)); req.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { resp.sendError(401, 登录已过期); return false; } } }拦截器里放行OPTIONS请求是前后端分离项目里最容易漏的一步。浏览器跨域时会先发一个OPTIONS预检请求它不是真正的业务请求如果拦截器直接判未登录前端就会一直报 CORS 或 401。注册拦截器时要把登录注册和商品查询的接口排除掉。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/user/login, /api/user/register, /api/goods/list, /api/goods/detail); } }密码存储建议用 BCryptSpring Security 的BCryptPasswordEncoder可以直接用不引入整个 Security 全家桶。加密后的密码形如$2a$10$...长度 60 位所以user.password字段定义成VARCHAR(255)而不是VARCHAR(20)这也是很多源码包字段长度不够导致登录报错的隐藏原因。3.3 商品下单接口事务、库存扣减与按钮重复提交校验下单是整站业务逻辑最重的一环也是答辩时老师最可能要求“手写思路”的地方。常见做法是前端把购物车商品列表和收货地址提交给后端后端在一个事务里完成校验库存、扣库存、生成订单、生成明细四步。只要任一步失败全部回滚。Transactional public Long createOrder(OrderCreateDTO dto, Integer userId) { ListOrderItemDTO items dto.getItems(); if (items null || items.isEmpty()) { throw new RuntimeException(订单商品不能为空); } Order order new Order(); order.setOrderNo(AG System.currentTimeMillis()); order.setUserId(userId); order.setAddress(dto.getAddress()); order.setStatus(0); BigDecimal total BigDecimal.ZERO; ListOrderItem orderItems new ArrayList(); for (OrderItemDTO item : items) { Goods goods goodsMapper.selectById(item.getGoodsId()); if (goods null || goods.getStock() item.getQuantity()) { throw new RuntimeException(商品库存不足: goods.getName()); } // 扣减库存 goods.setStock(goods.getStock() - item.getQuantity()); goods.setSales(goods.getSales() item.getQuantity()); goodsMapper.updateById(goods); BigDecimal amount goods.getPrice().multiply( BigDecimal.valueOf(item.getQuantity())); total total.add(amount); OrderItem orderItem new OrderItem(); orderItem.setGoodsId(goods.getId()); orderItem.setGoodsName(goods.getName()); orderItem.setPrice(goods.getPrice()); orderItem.setQuantity(item.getQuantity()); orderItems.add(orderItem); } order.setTotalAmount(total); ordersMapper.insert(order); for (OrderItem item : orderItems) { item.setOrderId(order.getId()); orderItemMapper.insert(item); } return order.getId(); }Transactional是 Spring 声明式事务的入口默认遇到RuntimeException就回滚但要注意它只对通过 Spring 代理调用的方法生效。你在同一个类里写普通方法调createOrder事务会失效这是个经典坑答辩时老师问到就说“要通过 Controller 调 Service 的公开方法”。库存扣减这里还有个并发问题同一时间两个人买同一件商品先读库存再更新的写法可能超卖。毕设层面用Transactional加数据库行锁就够了进阶方案是UPDATE goods SET stock stock - 1 WHERE id ? AND stock 0把库存判断下沉到 SQL这个思路说出来就是加分项。至于前后端对于按钮重复提交校验方法前端在订单提交按钮上做 loading 置灰Element UI 里就是:loadingsubmitting加disabled后端靠orders.order_no唯一索引兜底同一个订单号第二次 insert 会抛 DuplicateKeyException直接返回“请勿重复提交”。这一层配合老师问“怎么防止用户连点下单”时你就能回答出“前端限流 后端幂等约束”两层防线。3.4 说明文档与 LW写论文时最容易被翻车的三处内容说明文档和 LW 论文不是给老师看的摆设它是你答辩时的提词器。但很多源码里的文档和实际代码对不上这是最致命的。常见的有三处不一致。第一E-R 图画的字段和 SQL 脚本对不上。比如文档里画了goods表有origin产地字段但 SQL 脚本里根本没有。答辩老师如果拿着文档比对一眼就看穿。解决方法是打开 SQL 脚本照着真实表结构改 E-R 图。第二功能描述超出代码范围。文档写“支持支付宝支付”实际项目里只有货到付款这种功能夸大是答辩中翻车的高发区宁可少写一个功能也不要写没有代码支撑的功能。第三运行环境版本写错。文档写 JDK 8实际代码用了 JDK 11 的var语法启动就会报错所以文档里的版本号要和pom.xml、前端package.json逐一核对。改这三处不需要会多少架构但需要耐心。我的建议是把 LW 论文里的系统功能设计章节对着真实代码目录过一遍每个章节提到的功能都找到对应的 Controller 路径找不到就删掉描述。这套动作做完论文的“保真度”会大幅提升。4. 前端页面与接口联调把农产品网站做成能演示的效果后端接口通了前端要能接上才算完整。这一章讲三个前端必改的点路由权限、Axios 封装、图片上传。改完这三个地方整个网站的演示就能走通了。4.1 路由守卫游客、普通用户、管理员三个角色怎么分流前端路由要解决的问题是用户没登录就想进后台或者普通用户直接敲管理页面的 URL。Vue Router 的beforeEach全局前置守卫是标准做法。router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) return } if (to.meta.roles !to.meta.roles.includes(role)) { next(/) return } next() })路由配置里通过meta声明页面权限比如后台管理页写meta: { requiresAuth: true, roles: [2] }商品详情页不需要鉴权就不写requiresAuth。注意这里角色存的是字符串2因为localStorage只能存字符串后端返回的role数字要转一下再存否则includes永远不相等。登录成功后要把token和role同时写入localStorage并且跳转到redirect参数指定的页面这样用户被拦截后重新登录能回到原来的页面演示起来体验顺畅很多。4.2 Axios 封装统一处理 Token 与错误码接口联调最大的重复劳动是每个请求都手动带 Token、每个响应都手动判断错误。用 axios 实例集中处理是所有前后端分离项目实战里的通用做法。import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) service.interceptors.response.use( res { if (res.data.code 200) { return res.data } if (res.data.code 401) { localStorage.removeItem(token) localStorage.removeItem(role) window.location.href /login return Promise.reject(new Error(登录已过期)) } return Promise.reject(new Error(res.data.msg || 请求失败)) }, error { if (error.response error.response.status 401) { window.location.href /login } return Promise.reject(error) } ) export default service请求拦截器统一往 header 里塞 Token这样每个接口都不用单独写。响应拦截器把 HTTP 状态和业务码分开处理HTTP 200 不代表业务成功还要看code是不是 200。这里baseURL: /api是约定前端所有请求都走/api前缀后端容器也统一监听这个前缀后面在转发配置里把/api指到后端端口。需要注意后端返回的code字段名要和前端约定一致。有的源码包后端返回的是status或success前端封装是按code写的联调时就会一直进错误分支。拿到源码后第一步先搜后端统一返回对象的字段名再对着改前端拦截器这两处对不上页面会一片报错。4.3 图片上传与商品表单先传文件再提交数据后台新增农产品时表单里一般有图片上传。最容易踩的坑是用户在点击“保存商品”的同时把图片也一起提交后端接口接收的是 JSON根本没法处理文件流。正确顺序是先单独调上传接口拿图片 URL再把 URL 放进表单一起提交。el-upload :http-requesthandleUpload :show-file-listfalse acceptimage/* el-button typeprimary上传图片/el-button /el-uploadasync function handleUpload(option) { const formData new FormData() formData.append(file, option.file) const res await service.post(/file/upload, formData, { headers: { Content-Type: multipart/form-data } }) productForm.image res.data.url }后端接收这个文件常见做法是MultipartFile参数加一个/file/upload的 Controller 方法把文件写到本地磁盘目录然后返回访问路径。这里有个隐藏配置文件保存路径和静态资源映射要配好否则图片上传成功但浏览器访问不到。后端代码一般是配置一个虚拟路径映射Configuration public class FileConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceLocations(file:D:/agricultural-demo/uploads/); } }这段配置把访问/uploads/xxx.jpg的请求映射到磁盘上的D:/agricultural-demo/uploads/目录。实际部署时要改成服务器上的绝对路径不能写相对路径这是 java 后端开发里一个很容易被忽视的细节。如果你前端上传后图片裂开优先检查这一行addResourceLocations的路径和权限。5. 避坑和常见问题排查启动失败、依赖冲突、中文乱码把一套陌生源码跑起来碰到的坑翻来覆去就那么几个。这一章我按出现频率列五条每一条都按“现象 → 原因 → 解决”写你照着排就行。5.1 后端启动报端口被占用或 Tomcat 启动失败现象Spring Boot启动到一半报Port 8080 was already in use或者静默失败控制台看不到熟悉的 Tomcat 启日志。原因本机 8080 端口被其他进程占用常见的是之前启动的后端没关干净或者装了其他开发工具占用了端口。也有一种情况是源码包里的server.port配置被改过你和其他人的配置冲突了。解决先确认端口配置再看占用进程。Windows 上执行netstat -ano | findstr 8080macOS/Linux 上执行lsof -i tcp:8080找到 PID 后结束进程。如果是你自己项目占用的用kill -9 PID或任务管理器结束即可。要是端口被系统服务占用不想动直接改application.yml里的server.port为 8081但前端的baseURL和转发配置也要跟着改否则接口还是 404。5.2 数据库连接失败Unknown database 或 Access denied现象后端启动刷出一大片红字核心报错是java.sql.SQLException: Unknown database agricultural或者Access denied for user rootlocalhost。原因第一种是只解压了源码、没执行 SQL 脚本数据库根本不存在。第二种是application.yml里的用户名密码和你本机 MySQL 不一致。解决先用命令行建库并导入脚本注意要指定字符集。mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS agricultural DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p agricultural sql/agricultural.sql导入完成后打开application.yml检查spring.datasource.url、username、password三项。MySQL 8.0 的用户认证方式默认是caching_sha2_password如果驱动版本太老会报Public Key Retrieval is not allowed在 JDBC URL 后面加allowPublicKeyRetrievaltrue能解决。同时 MySQL 8.0 的 URL 要带时区参数不然会报Server returns invalid timezonespring: datasource: url: jdbc:mysql://localhost:3306/agricultural?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver5.3 前端页面能打开但接口全部 404 或跨域报错现象npm run serve成功浏览器能打开首页但一调登录接口就报404 Not Found或控制台出现CORS、blocked by CORS policy字样。原因404 一般是前端请求的接口路径和后端 Controller 路径对不上比如前端调/api/user/login后端映射的是/user/login少了一层前缀。CORS 报错则是因为前端端口比如 8081和后端端口8080不同浏览器拦截了跨域响应。解决先解决路径问题统一约定。最简单的方式是让后端 Controller 的RequestMapping都有/api前缀前端 axios 的baseURL也设为/api再由前端开发服务器的转发配置把/api送到后端端口。如果源码用的不是这种结构就直接把 axios 的baseURL改成完整后端地址http://localhost:8080本地演示够用。跨域的根治办法是后端加全局 CORS 配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8081) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) .maxAge(3600); } }maxAge(3600)是让浏览器一小时内不用重复发预检请求能减少不少跨域报错。这里的allowedOrigins写死前端地址演示时够用如果想省事开发阶段可以直接allowedOriginPatterns(*)但答辩别这么干老师问安全策略时说不上来。5.4 数据库或页面中文全部变成问号、乱码现象商品名称、用户昵称在数据库里显示为???或者页面展示乱码。原因三层字符集至少有一层不对。数据库表不是utf8mb4连接 URL 没带characterEncodingutf8前端页面文件保存编码不是 UTF-8。解决从上到下排查。第一步建库时指定字符集utf8mb4第二步 JDBC URL 带上useUnicodetruecharacterEncodingutf8第三步确认后端代码里没有手动把字符串转码。如果你发现数据已经变成???那说明字节已经写坏了直接删掉重新导入脚本别再往脏数据上叠配置。网页乱码则要看 HTML 头部有没有meta charsetUTF-8以及.vue文件在编辑器里是不是以 UTF-8 保存。这里有个隐藏知识点utf8mb4和utf8的区别是前者支持 emoji 和生僻字农产品名里有“”这种字的话普通utf8存不进去。建议所有表统一用utf8mb4不要混用。5.5 Maven 依赖下载缓慢、jar 包冲突或启动 NoSuchMethodError现象Maven 构建一直卡在Downloading...或者启动时抛NoSuchMethodError、ClassNotFoundException最常见的是java.lang.NoClassDefFoundError: org/mybatis/logging/LoggerFactory。原因依赖下载慢一般是默认中央仓库连不上或网速慢需要切换镜像源。jar 包冲突则是版本不匹配典型场景是 Spring Boot 版本和 MyBatis-Plus starter 版本不兼容老项目用mybatis-plus-boot-starter 3.5.x配 Spring Boot 3.x 就会出现类加载不到。解决在 Maven 的settings.xml里配置阿里云镜像mirror idaliyunmaven/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror配置完重新mvn clean package。如果是版本冲突在项目根目录跑mvn dependency:tree查看依赖树找到冲突的包后用exclusion排除旧版本。我给你的排查顺序是先确认 Spring Boot 版本再确认 MyBatis-Plus 版本最后确认 JDK 版本三个对齐了这类报错能消掉一大半。6. 答辩演示前的一小时把核心流程演练成讲解脚本功能全跑通之后最后这一小时别去调样式也别研究新功能把演示流程固定下来练成肌肉记忆。我建议按这个顺序走一遍注册新账号 → 登录 → 浏览农产品列表 → 点开详情 → 加入购物车 → 提交订单 → 切换管理员账号 → 后台看到新订单并发货 → 切回用户账号看到订单状态变化。每一步的讲解点都可以提前备好注册登录时讲 JWT 令牌和 BCrypt 加密浏览列表时讲 MyBatis-Plus 分页参数current和size提交订单时讲Transactional事务里库存扣减的四个步骤看到订单号时顺便说一句“订单号加了唯一索引防止重复提交”这就是 3.3 章那套说辞。老师大概率会问一个问题“你这个项目有什么亮点”别回答“用了前后端分离”这种大白话把话题引到具体实现上可以说下单接口的事务边界也可以说前端按钮 loading 与后端唯一索引两层防重复提交设计。这两个点都是你能指着代码讲出细节的比“用了 Spring Boot”有说服力得多。演示时翻车概率最高的操作是临时切换管理员账号时 Token 没清干净。我踩过一次这个坑演示前用管理员账号操作后台后来切回普通账号时localStorage里还留着管理员 token路由守卫判断角色不对直接跳回首页我当时还以为是页面坏了。从那以后我养成了一个习惯演示前先把浏览器开发者工具打开Application 面板里确认三个值——token、role、当前路由的meta三个一致再点下一步。这个检查十秒钟能做完但能让你的答辩流程顺畅很多希望帮到你。本文还有配套的精品资源点击获取