news 2026/10/2 4:23:23

农产品预售平台SpringBoot+Vue毕设项目完整源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
农产品预售平台SpringBoot+Vue毕设项目完整源码解析

每年到了毕设季,我的留言区就会被同一类问题刷屏:有没有一套SpringBoot+Vue的完整项目,能直接跑起来、有数据库脚本、接口说明还写得清楚的那种。说实话,网上能搜到的Java Web毕设源码不少,但真正能让你在一周内看懂、跑通、还能在答辩时讲明白的项目包,并不算多。我这次整理的这套农产品预售平台,就是按这个标准来的——SpringBoot后端、Vue前端、SQL初始化脚本、接口文档四件套齐全,业务上也选了"预售"这个比普通增删改查有深度一点的场景。

这套项目适合谁用?两类人:一类是正在准备开题、想要一个完整工程作为基础再改改的毕业生,另一类是刚学完SpringBoot和Vue、想找一个真实业务练手的技术新手。预售这个场景的好处在于,它既有商品、订单、支付这类电商共性功能,又带着"定金、尾款、批次、超时取消"这些特殊业务规则,难度比学生管理系统高一个档次,但又没复杂到企业级电商那种让人望而却步的程度,刚好卡在毕设最理想的位置。

1. 农产品预售平台这个选题,为什么适合做Java Web毕设

1.1 预售业务自带复杂度,恰好填满"工作量"这一栏

很多毕设项目的问题不是"不会做",而是"太简单"。比如纯商品展示加留言,后端就是几张表的增删改查,前端是几个列表页,写到中期你自己都觉得答辩时没什么可讲的。农产品预售不一样,它的核心链路是:农户发布某批农产品(比如果园的晚熟桃、养殖场的大闸蟹),设定本批次的预售时间、定金比例和预计发货日期,用户在预售期内先付一笔定金锁定名额,等临近发货期再支付尾款,平台方安排发货,最后确认收货。

这条链路里藏着好几个有含金量的设计点:

  • 商品的库存不是简单扣减,而是按"批次"维度做预售限额,超卖要拦得住。
  • 订单状态不是只有"已下单/已完成",而是要经历待付定金、已付定金、待付尾款、待发货、已发货、已完成,中间还掺着用户主动取消、超时未付尾款的自动取消。
  • 金额计算涉及定金和尾款拆分,涉及单价、数量、比例的组合,浮点数精度问题在这里必须处理。

这些点堆在一起,项目的业务深度就出来了。答辩时老师问"这个项目的难点是什么",你至少能拿出状态机设计、定时任务、库存校验这三样来聊,而不是支支吾吾说"我用了SpringBoot"。

1.2 前后端分离的技术选型,正好覆盖评分表高频项

毕设评分通常看重几个维度:技术栈的现代程度、功能完整性、代码规范性、数据库设计的合理性。这套农产品预售平台在技术栈上刚好踩中大部分评审老师的偏好:

评分方向项目中的落点
后端框架SpringBoot 2.7.x,涉及自动装配、Starter、配置绑定这些机制
持久层MyBatis-Plus,SQL写在XML或注解里,能讲清楚ORM原理
前端框架Vue + Vue Router + Pinia,组件化、路由、状态管理全覆盖
数据库MySQL,8张以上的业务表,含索引设计
安全机制JWT登录鉴权 + 接口白名单 + 密码BCrypt加密
异步/定时Spring定时任务做尾款超时扫描

这套组合的好处是网上资料多、你踩坑时搜得到解法,而且每个点都对应一道经典面试题:SpringBoot自动装配原理、JWT和Session的区别、Vue路由守卫怎么用、MyBatis和MyBatis-Plus的区别。等于做项目的过程中顺便把面试题复习了一遍。

1.3 一个完整的项目包应该包含哪四块内容

很多同学在网上下的所谓"完整源码",打开以后只有后端一个文件夹,数据库脚本没有,前端还缺依赖,跑起来全是报错。一个能真正用于毕设的项目包,我建议至少要有四块:

  • 前后端完整源码:后端Maven工程可以直接编译,前端npm依赖完整,不是残缺版。
  • SQL脚本:包括建库、建表、初始化数据,最好拆成init.sql和data.sql,方便分步执行。
  • 接口文档:列出所有接口的地址、请求方式、入参、出参,前端对接和答辩画架构图都靠它。
  • 运行说明:JDK/Node/MySQL版本要求、启动步骤、默认账号,这一步能帮你少走两天弯路。

这套农产品预售平台就是按这四件套来整理的。后面我会逐个拆开讲:后端订单状态机怎么落地、前端页面体系怎么搭、SQL表结构为什么这样设计、接口文档怎么用,最后再把跑通源码的步骤和几个高频坑过一遍。

2. 后端SpringBoot部分:模块划分与预售订单状态机的落地

2.1 Maven工程结构与技术选型

后端是一个标准的SpringBoot Maven工程,包结构按controller / service / mapper / entity / config / common / utils划分。这种分包方式不算花哨,但在毕设里是加分项,因为评审老师一眼就能看出你清楚分层职责。

核心依赖选型我多说两句:

  • spring-boot-starter-web:提供MVC和内嵌Tomcat,这是整个后端的基础。
  • mybatis-plus-boot-starter:比原生MyBatis省事,单表CRUD不需要写XML,复杂多表查询再补注解或XML。
  • mysql-connector-java:数据库驱动,注意版本要跟MySQL对应。
  • jjwt:生成和校验JWT,做登录鉴权用。
  • lombok:省掉getter/setter模板代码,让实体类清爽很多。
  • hutool:工具类库,字符串、日期、加密这些常用操作不用自己造轮子。

pom.xml里不少同学会踩版本坑,比如SpringBoot版本太高导致某些依赖冲突。毕设场景我建议用SpringBoot 2.7.x搭配JDK8,这套组合最稳,网上的教程也基本都按这个版本来写。如果你用JDK17甚至JDK21,很多旧依赖会报"包不存在"之类的问题,排查起来很痛苦。

2.2 预售订单状态机:从定金到尾款的流转设计

订单模块是整个预售平台的业务核心。我把它设计成一张订单表加一个状态字段,用整数表示订单状态,同时配合系统的数据字典说明状态含义。订单状态流转大概是这样的:

待付定金 → 已付定金(等待支付尾款) → 已付尾款(商家待发货) → 已发货 → 已完成

中间有两个分叉:支付定金后用户主动取消,进入已取消;尾款超时未支付,系统自动取消。这里要注意,状态机是"单向推进"的,不允许订单从"已完成"跳回"已付定金",这是业务逻辑校验要做死的事情。

在代码实现上,我用service层封装了状态变更的入口方法,所有状态切换必须经过统一方法,而不是在controller里直接改状态字段。这样做的好处是,后续如果要加"退款中""退款完成"这些状态,只需要在状态机里补分支,不影响调用方。

订单创建的核心逻辑是库存校验。用户提交定金订单时,要锁定预售批次的部分库存:

public PresaleOrder createPresaleOrder(Long batchId, Integer buyCount, Long addressId) { PresaleBatch batch = presaleBatchMapper.selectById(batchId); if (batch == null || batch.getStatus() != 1) { throw new BizException("该批次不在预售中"); } if (batch.getRemainStock() < buyCount) { throw new BizException("该批次剩余库存不足"); } // 计算定金:单价 * 数量 * 定金比例,保留两位小数 BigDecimal deposit = batch.getPrice() .multiply(BigDecimal.valueOf(buyCount)) .multiply(batch.getDepositRatio()) .setScale(2, RoundingMode.HALF_UP); // 扣减库存 presaleBatchMapper.cutStock(batchId, buyCount); // 创建订单,状态为待付定金 // ... }

这里有个关键细节:金额一律用BigDecimal,不能用double,否则定金、尾款的精度问题会在支付环节给你上一课。扣库存用update语句里的原子操作,比如UPDATE presale_batch SET remain_stock = remain_stock - #{count} WHERE id = #{id} AND remain_stock >= #{count},从数据库层面避免并发超卖。

2.3 尾款超时自动取消:用Spring定时任务实现

预售场景有个很现实的规则:用户付了定金,但发货前没付尾款,这个名额不能无限期占着。我在系统里加了一个定时任务,每分钟扫描一次所有"已付定金且已超过尾款支付截止时间"的订单,自动置为已取消,并把占用的库存释放回批次。

用Spring的@Scheduled注解就能实现,不需要引入额外的任务调度框架:

@Component public class TailPayTimeoutJob { @Scheduled(cron = "0 0/1 * * * ?") public void scanTimeoutOrders() { List<PresaleOrder> timeoutOrders = orderMapper.selectDepositPaidButTimeoutOrders(new Date()); for (PresaleOrder order : timeoutOrders) { // 关闭订单,释放库存 orderService.cancelOrderByTimeout(order.getId()); } } }

注意在启动类上要加@EnableScheduling。这个定时任务写出来之后,你的项目里就有"异步任务"这个技术点了,答辩的时候可以顺带提一嘴分布式环境下可以用xxl-job替代单体定时任务,显得你考虑过生产级方案。

2.4 自动装配与配置绑定:答辩高频问题的准备素材

SpringBoot的项目如果只是写几个@RestController,答辩时老师随便问一句"SpringBoot为什么能自动配置"就可能卡住。建议把原理吃透一点:@SpringBootApplication里包含@EnableAutoConfiguration,它会去读META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,把里面的自动配置类按条件装配进容器,@ConditionalOnMissingBean、@ConditionalOnProperty这些条件注解决定某个配置在什么条件下生效。

在application.yml里,我们把数据源、JWT密钥、文件上传路径这些配置统一管理。比如:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/agri_presale?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 jwt: secret: your-secret-key-at-least-32-characters expire-minutes: 120

有一个非常隐蔽的坑:JWT的HS256算法要求密钥长度不能太短,如果你随便写个几位数的字符串,运行时会在生成token时抛异常,报错信息还不太直观。建议密钥至少32个字符,这个我会在后面第6节再强调一遍。

3. 前端Vue部分:路由、状态管理与Axios拦截器的实际用法

3.1 用户端与管理端:两套页面的路由设计

前端我按"用户端"和"管理端"拆成两块。用户端面向普通消费者,核心页面有首页、商品列表、商品详情、预售批次详情、购物车、订单列表、支付页、个人中心;管理端面向运营人员,核心页面有商品管理、预售批次管理、订单管理、用户管理、数据统计。两边路由分开,通过路由守卫做权限控制。

Vue Router的路由配置本身不复杂,复杂的是"哪些页面需要登录才能进""管理端怎么拦住普通用户"。我在router的meta里挂角色信息,再在全局前置守卫里判断:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }) return } if (to.meta.role) { const user = JSON.parse(localStorage.getItem('userInfo') || '{}') if (user.role !== to.meta.role) { next('/403') return } } next() })

登录后把token和用户信息落到localStorage,刷新页面时再从本地恢复。这里顺便说一句,如果你用的是Vue3的组合式API,可以用Pinia管理用户状态,比localStorage到处读要规范得多,也更好向老师解释"状态集中管理"。

3.2 Axios拦截器:统一处理token和错误码

前端所有请求我都走axios,并在封装的request模块里加了拦截器。请求拦截器负责在每次请求头里带上token,响应拦截器负责统一处理后端返回的状态码:

service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data // 后端统一返回体 { code, message, data } if (res.code !== 200) { if (res.code === 401) { localStorage.clear() location.href = '/login' } ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { ElMessage.error('网络异常,请稍后重试') return Promise.reject(error) } )

后端返回体设计成{ code, message, data }这种统一格式后,前端每个页面就不用再自己判断status是200还是500,逻辑全集中在拦截器里。这也是接口文档里最核心的约定,前后端必须在这个结构上达成一致,否则联调阶段会非常痛苦。

3.3 组件化实践:预售倒计时和商品卡片

前端页面里比较有含金量的组件有两个:一个是预售倒计时,一个是商品卡片。

预售倒计时需要处理"批次预售还剩多久结束"以及"距离尾款支付截止还有多久",我把它封装成一个独立的CountDown组件,用setInterval每秒刷新剩余时间,并在组件卸载时清掉定时器,防止内存泄漏:

onMounted(() => { timer = setInterval(() => { const remain = endTime - Date.now() if (remain <= 0) { clearInterval(timer) emit('timeout') } state.remainText = formatRemain(remain) }, 1000) }) onUnmounted(() => clearInterval(timer))

商品卡片则用Vue插槽(slot)做成可插拔的结构——默认样式展示图片、名称、价格,管理端需要显示上下架按钮时,通过具名插槽塞进去。这种做法正好是Vue面试题里常考的插槽应用,也避免了同一份代码复制两遍的尴尬。

3.4 环境搭建:vue安装依赖、版本选择与镜像源

前端环境最常出问题的就是npm install那一步。node-sass这类老依赖在Node高版本下经常编译失败,所以我在前端选型时直接绕开它:Vue脚手架用Vite,UI库用Element Plus,这样对Node版本的要求宽松很多。如果你拿到的项目用的是旧版vue-cli加node-sass,先确认Node版本是否符合依赖要求,不行就装nvm切换版本,或者把镜像源切到国内源再试。

# 使用国内镜像源安装依赖,能省下大量等待时间 npm config set registry https://registry.npmmirror.com npm install npm run dev

启动之后默认端口一般是5173(Vite)或8080(vue-cli),本地开发时前后端端口不同,所以前端vite.config.js里通常要配一层代理或者后端放开跨域。这个会在第5节的接口联调部分一起讲。

4. SQL脚本里的表结构:预售场景的建表思路与字段细节

4.1 核心表清单与职责划分

整套系统我建了9张表,不算多,但每张表都有明确的业务职责:

表名职责
sys_user用户表,区分普通用户和管理员,存手机号、密码(BCrypt加密)、角色
product农产品表,存商品名称、简介、主图、分类、上下架状态
presale_batch预售批次表,存批次名称、总库存、剩余库存、定金比例、预售时间、发货时间
product_image商品轮播图表,一对多挂在商品下
presale_order预售订单表,存订单状态、定金金额、尾款金额、地址快照
payment_record支付流水表,记录定金支付、尾款支付各一次
order_address地址表,用户收货地址
cart购物车表
category商品分类表

这里有个设计细节值得展开:商品和预售批次是分开的两张表。原因很简单——同一款农产品可以发起多轮预售,不同轮次的定价、定金比例、发货时间都不一样,如果把批次字段堆在商品表里,产品一换季你就得改表结构。拆开后,商品是静态信息,批次是动态信息,一个商品关联多个预售批次,属于非常自然的1:N关系。

4.2 订单表为什么要做"快照"

订单表里我没有直接存"商品ID + 数量",而是把商品名称、主图、单价、批次名称这些下单时的信息冗余了一份进去。这种做法在电商系统里叫"订单快照"。

假设用户下单时商品卖60块,三天后商家把价格改成80块,如果订单只存productId,用户打开订单列表看到的就会是新的80块,这显然不合理。把价格、名称、图片在下单那一刻固化进订单表,之后商品怎么改价都不影响历史订单展示。这个点虽然小,但在答辩时属于"考虑到了实际业务问题"的加分细节。

4.3 字段类型、时间字段与逻辑删除

建表的时候有几个细节要养成习惯:

  • 金额一律DECIMAL(10,2),禁止float/double。数据库里浮点数的二进制表示会导致金额对不上账,审计的时候很难解释。
  • 状态字段用tinyint,配合COMMENT注释说明每个取值含义,比如1预售中 2已结束 3已取消。SQL脚本里的注释质量,某种程度上代表你的工程素养。
  • 每张表都保留created_time、updated_time,并设置DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP,查询和排查问题都方便。
  • 用户、商品这类数据用逻辑删除(deleted字段)而不是物理DELETE,保留历史数据,也防止外键关联断裂。

预售批次表的建表SQL大概长这样:

CREATE TABLE `presale_batch` ( `id` bigint NOT NULL AUTO_INCREMENT, `product_id` bigint NOT NULL COMMENT '关联商品ID', `batch_name` varchar(100) NOT NULL COMMENT '批次名称,如早熟红富士第一轮', `price` decimal(10,2) NOT NULL COMMENT '本批次单价', `total_stock` int NOT NULL COMMENT '本批次总库存', `remain_stock` int NOT NULL COMMENT '剩余库存', `deposit_ratio` decimal(5,2) NOT NULL DEFAULT '0.30' COMMENT '定金比例,0.30表示30%', `presale_start_time` datetime NOT NULL COMMENT '预售开始时间', `presale_end_time` datetime NOT NULL COMMENT '预售结束时间', `tail_pay_deadline` datetime NOT NULL COMMENT '尾款支付截止时间', `delivery_time` datetime NOT NULL COMMENT '预计发货时间', `status` tinyint NOT NULL DEFAULT '1' COMMENT '1预售中 2已结束 3已取消', `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_product_id` (`product_id`), KEY `idx_status_endtime` (`status`, `presale_end_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='农产品预售批次表';

索引设计上,idx_product_id用来支撑按商品查批次,idx_status_endtime用来支撑定时任务扫描"预售中且已过期"的批次。SQL脚本里把索引建好,说明你考虑过查询路径,而不是随便给主键就完事。

4.4 初始化数据脚本的编排

我会把初始化脚本拆成init.sql(建库建表)和data.sql(演示数据)。演示数据里至少要有:一个管理员账号(admin/123456)、一个普通用户、3个商品、每个商品各2个预售批次(一个预售中、一个已结束)、几笔不同状态的订单。

为什么要准备这些数据?因为答辩演示的时候,你总不能现场注册账号、现场下单、现场等定时任务跑。提前把"已付定金、已付尾款、已发货、已完成"的订单都铺进去,演示时点到哪个页面都有数据可看,老师体验会好很多。导入脚本时注意SET FOREIGN_KEY_CHECKS=0,避免因表之间的逻辑关系导致导入顺序报错。

5. 接口文档怎么用起来:联调效率、答辩素材和二次开发

5.1 统一返回体与分页结构

接口文档不是应付检查的摆设,前后端联调全靠它。项目里所有接口都遵循统一返回结构:

{ "code": 200, "message": "success", "data": {} }

分页查询的data里再包一层:

{ "records": [], "total": 32, "current": 1, "size": 10 }

code的含义在文档里写明:200成功、400参数错误、401未登录或token过期、500服务器异常、501业务异常(比如库存不足)。前端拦截器只需要认这几种code,就能把"登录失效自动跳转""业务失败弹提示"这类通用逻辑做掉。

5.2 核心接口清单与一个完整示例

接口按模块分成几组:认证模块、商品模块、预售批次模块、订单模块、支付模块、管理端模块。核心接口我用表格列一下,方便你对着源码找:

接口方法说明是否需要登录
/api/auth/loginPOST登录,返回token和用户信息否
/api/product/pageGET商品分页查询否
/api/product/{id}GET商品详情,含批次列表否
/api/presale/batch/{id}GET批次详情否
/api/cart/*POST/GET购物车增删查是
/api/presale/orderPOST提交预售订单(付定金)是
/api/presale/tail-payPOST支付尾款是
/api/order/listGET我的订单列表是
/api/order/{id}/cancelPOST取消订单是
/api/admin/batchPOST管理端发布预售批次管理员
/api/admin/order/pageGET管理端订单列表管理员
/api/admin/order/deliverPOST管理端发货管理员

拿"提交预售订单"举例,接口文档里要写清楚请求参数:

POST /api/presale/order Content-Type: application/json Authorization: Bearer {token} { "batchId": 2, "buyCount": 2, "addressId": 5, "remark": "" }

后端响应示例:

{ "code": 200, "message": "success", "data": { "orderId": 20241101001, "orderNo": "PS2024110100001", "productName": "烟台红富士苹果 第一轮预售", "totalAmount": 120.00, "depositAmount": 36.00, "tailAmount": 84.00, "status": 1, "statusName": "待付定金" } }

呼应后端逻辑:定金 = 单价 × 数量 × 定金比例,这里60元 × 2件 × 30% = 36元,尾款就是84元。响应里把金额拆清楚,前端支付页就不用自己再算一遍,避免前后端计算结果不一致。

5.3 接口文档在答辩中的三个用法

接口文档除了开发时对齐,答辩时其实特别好用:

第一,展示工作量。老师问"系统有哪些功能",你不用背页面,直接按接口清单讲一遍,认证、商品、批次、订单、支付、管理,模块化讲下来,逻辑清楚又显得规范。

第二,讲解业务规则。比如"一个批次到底能不能重复下单",接口文档里会写明参数校验规则:同一用户同一批次只能下一笔定金订单,未取消前再次提交会返回501业务异常。这类边界规则是答辩时最有说服力的素材。

第三,支撑二次开发。如果你后续想加优惠券、加满减,接口文档就是扩展的基础。第7节我会给几个现成的扩展方向。

5.4 跨域与鉴权白名单:接口联调最容易翻车的两个点

本地开发时前端跑在5173,后端跑在8080,前端的axios请求必然触发跨域。解决方式有两种:一种是在后端加CORS配置,另一种是前端用Vite代理。毕设项目我建议两种都要懂,后端加CORS是为了打包部署后也能跨域访问:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

JWT拦截器那边则要配置白名单:登录、注册、商品查询、批次查询这些公开接口放行,其余接口校验token。白名单配置在代码里集中维护,答辩时能讲清楚"为什么商品详情不需要登录也能看,而下单必须登录"这个产品逻辑。

6. 跑通这套源码的完整步骤与三个高频坑

6.1 从环境到前后端启动

跑通这套项目,我建议按这个顺序操作:

  1. 安装JDK8、Maven 3.6+、MySQL 5.7+、Node 16+,确认java -version、mvn -v、node -v都能正常输出。
  2. 用Navicat或命令行创建数据库agri_presale,字符集选utf8mb4,依次执行init.sql和data.sql。
  3. 打开后端的application.yml,改成你本地数据库的用户名密码,必要时调整端口。
  4. 在backend目录执行mvn spring-boot:run,看到Tomcat started就说明后端起来了。
  5. 在frontend目录依次执行npm install、npm run dev,浏览器访问前端地址,用admin/123456登录。

后端单独启动时可以用命令行带参数覆盖配置,适合不想改yml的情况:

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8081 --spring.datasource.password=xxxx"

6.2 前端打包进SpringBoot的单体部署方式

有些同学答辩时需要"一个jar搞定演示",不想现场开两个终端。这种场景下的做法是:前端执行npm run build,把生成的dist目录里的静态文件复制到后端src/main/resources/static下,重新打包成jar。启动后访问http://localhost:8080/,就能同时拿到页面和接口。

注意两个细节:一是前端请求地址要改成相对路径/api/...而不是写死http://localhost:8080/api/...,否则打进jar后请求还是指向固定端口,换个环境就得改代码;二是Vue Router如果用的是history模式,刷新页面会出现404,因为SpringBoot的静态资源路由不认前端路由,需要在后端加一个转发规则,把所有非/api的路径转发到index.html。如果是hash模式就没有这个问题,毕设图省事可以直接用hash模式。

6.3 三个高频坑的排查链路

结合我平时帮人看代码的经验,这套项目跑不起来的报错基本集中在三个地方。

第一个是数据库连接失败。报Access denied for user是密码写错;报Unknown database是库没建;报The server time zone value是URL里少了serverTimezone=Asia/Shanghai。这些都是配置层问题,对照application.yml逐项检查就能解决。

第二个是端口被占用。报Port 8080 was already in use时,Windows下执行netstat -ano | findstr 8080找到占用进程的PID,任务管理器结束它,或者干脆改后端端口。前端5173被占用同理。

第三个是JWT拦截器把所有请求都拦了,前端登录都登不进去。排查顺序:先看后端日志里请求有没有进入controller;再看拦截器注册时是否把/api/auth/login放进了排除列表;最后看前端是否把token正确塞进了请求头。最常见的坑是拦截器里excludePathPatterns写错了路径,导致登录接口也被拦,返回401,前端就一直卡在登录页。

排查的时候我习惯先看后端日志,再看浏览器Network面板里的请求状态码,最后看数据库里有没有数据。这个顺序能帮你快速定位70%的问题,比瞎猜高效很多。

6.4 部署演示的小建议

如果老师要求现场演示,或者你要录视频交作业,建议提前准备一台干净环境的机器,把MySQL、JDK都装好,启动步骤写成文档。演示用的数据在第4节已经铺好了,你只要保证启动后能登录、能打开几个不同状态的订单页面,讲解的时候再结合接口文档里那几条业务规则,整个演示就很完整了。

7. 想让答辩更有亮点,三个可以直接落地的扩展方向

7.1 MinIO文件存储:把商品图片和宣传视频从本地目录挪走

原项目的商品图片如果直接放在本地磁盘目录,简单是简单,但答辩时容易被问"生产环境图片放服务器磁盘有什么问题"。标准答案是:不利于水平扩展、备份困难、请求压力全在应用服务器上。更合理的方案是接入MinIO做对象存储。

MinIO的接入思路不复杂:后端引入minio Java SDK,配置endpoint、accessKey、secretKey、bucket,上传接口接收MultipartFile后转存MinIO,返回文件访问URL;前端商品管理页用el-upload对接这个上传接口。这样一个改动就能同时在项目里增加"文件上传""对象存储"两个技术点。

7.2 预售宣传视频的m3u8播放:给商品详情页加视频

农产品预售往往需要展示种植环境、采摘过程,静态图片说服力有限,所以可以给商品详情加一个宣传视频模块。视频处理上比较实用的做法是:先用ffmpeg把mp4转成hls切片(m3u8 + ts文件),上传到MinIO,前端用hls.js播放,不需要额外装插件。

前端播放的核心逻辑是:

import Hls from 'hls.js' if (Hls.isSupported()) { const hls = new Hls() hls.loadSource(videoUrl) // 指向 xxx/index.m3u8 hls.attachMedia(videoRef.value) }

这个方案的卖点是"免安装播放器、按需分片加载、适合移动端弱网环境",讲出来就是活的项目亮点。ffmpeg转切片的具体命令你也可以写进项目README,让老师看到你有落地的工程能力。

7.3 数据可视化大屏:用ECharts把预售数据盘活

管理端如果能有一个数据概览页,整个项目的档次会明显不一样。用ECharts做几个图表:各批次预售金额的柱状图、商品分类销售占比的饼图、近30天订单量和付款金额的折线图、尾款支付率的仪表盘。

后端对应加一个/api/admin/stats/overview接口,返回这几个图表需要的数据聚合结果,SQL里用GROUP BY和DATE_FORMAT就可以完成。前端用ECharts的init方法渲染,整个页面差不多一百行代码就能搞定。视觉上的冲击力,远大于你在答辩PPT里写满技术名词。

这三个扩展点都基于现有项目结构,改动量不大,但每一个都能在答辩问答环节给你多留一个"我确实思考过工程化问题"的印象。

最后说点我个人的体会。这套农产品预售平台我前前后后帮人跑过很多遍,印象最深的一次,是一个同学把数据脚本导进去后登录一直报错,折腾一晚上才发现是JWT密钥太短。所以拿到任何项目,先别急着改业务代码,按"建库 → 导数据 → 起后端 → 起前端"的顺序把环境跑通,再带着业务去读代码,效率会高得多。答辩前的晚上,也建议你亲手从零到一再部署一遍,那比背十页PPT都有用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 4:23:18

浙江高中算法与程序设计活动手册答案与代码练习指南

简介&#xff1a;这份PDF面向浙江省高中信息技术课程中学习算法与程序设计的学生&#xff0c;提供《学生活动手册》的参考答案&#xff0c;帮助学生在实践练习后对照检查、理清解题思路。内容覆盖算法基础、编程语言基本概念以及实践一至实践八的操作提示与相关练习&#xff0c…

作者头像 李华
网站建设 2026/10/2 4:22:59

专科毕业论文AI工具测评:8款软件实测对比与搭配方案

又是一年毕业季&#xff0c;专科的同学也躲不开论文这道坎。说实话&#xff0c;本专科毕业论文的难度虽然比本科和研究生低一截&#xff0c;但流程一点不少&#xff1a;选题、开题、文献综述、初稿、改格式、查重复率、答辩PPT&#xff0c;一个都不能少。我去年帮好几个专科的学…

作者头像 李华
网站建设 2026/10/2 4:22:54

Hive CLI 元数据客户端实例化失败排查与单机跑通

凌晨两点跑一条show databases;&#xff0c;屏幕只回了一行红字&#xff1a;FAILED: HiveException java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.ql.me——注意它连类名都没打完&#xff0c;ql.me后面直接断了。第一次见这个报错的人很容易慌&a…

作者头像 李华
网站建设 2026/10/2 4:22:48

AI检测原理与降AI率实战:4条改写指令从50%降到10%

先声明一下立场&#xff1a;我分享的方法只用于“人把AI生成的内容消化吸收之后&#xff0c;用自己的话重新表达出来”&#xff0c;不是教大家绕过学术诚信要求&#xff0c;更不是制造假论文。咱们今天的场景设定很简单——你手头有一份自己用AI辅助写的初稿&#xff0c;或者一…

作者头像 李华
网站建设 2026/10/2 4:22:21

Linux权限管理详解:从rwx到ACL,彻底搞懂chmod与目录权限

你有没有遇到过这种情况&#xff1a;明明chmod 777都给了&#xff0c;用户还是报“权限不足”&#xff1b;或者删不掉一个文件&#xff0c;ls -l一看自己明明有w权限&#xff0c;却提示Operation not permitted。如果你对这些现象背后的逻辑一知半解&#xff0c;那这篇关于 Lin…

作者头像 李华
网站建设 2026/10/2 4:22:15

基于SpringBoot+Vue的企业车辆管理系统设计与实现全解析

1. 项目概述与价值拆解这个标题看起来平平无奇&#xff0c;但做过毕设的人都知道&#xff0c;“企业车辆管理系统”属于管理信息系统里最典型的综合型题目&#xff0c;覆盖面广&#xff0c;技术点密度适中&#xff0c;既能体现开发能力又不至于失控。SpringBoot Vue MySQL这套…

作者头像 李华