ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot+Vue在线农产品销售系统毕设实战指南

Spring Boot+Vue在线农产品销售系统毕设实战指南 最近有不少准备开始做毕业设计的同学来问我在线农产品销售系统这个题目到底能不能选作为去年刚用这套系统完成毕设、最后连源码带文档一起整理妥当的人我的回答是能做而且很稳。先说明一下我说的不是让你去网上随便扒一个源码直接交差而是把这个题目当成一个完整项目去理解、去跑通、去改造。我当时从选题到前后端联调结束前后大概花了三周最后附带源码整理成一整套可以运行的项目今天就把整个过程包括技术选型、数据库设计、核心功能实现、部署调试和答辩避坑一次性拆开讲给你听。这套系统适合谁适合正在发愁选题的计算机相关专业本科生也适合打算把课程设计升级成毕设的人。它的业务边界很清晰农户或管理员发布农产品消费者浏览、加入购物车、下单管理后台处理商品和订单。没有太多花哨的算法但涉及到一个典型电商系统该有的完整流程。比起“图书管理系统”“学生管理系统”这类老掉牙的题目它更容易在答辩时讲出业务深度页面展示也更丰满比起“电商秒杀”“分布式商城”这种容易把自己绕晕的题目它又足够克制周期和难度都能控制住。1. 选题与整体设计这个项目到底该怎么搭1.1 为什么农产品销售系统适合当毕设毕设题目最重要的一点是评委能听懂你自己能讲透。农产品销售系统的核心场景大家都很熟悉买菜、买水果、下单收货这些日常逻辑几乎没有解释成本。你要向老师说明白的主要是系统怎么通过技术实现这些业务而不是花大把时间解释这个行业是干嘛的。另一个原因是数据模型很典型。电商系统该有的实体它都有用户、商品、分类、购物车、订单、订单项、收货地址。这些实体之间的关联关系天然就是一对多、多对多非常适合展示数据库设计基本功。而且农产品自带一些特色字段比如产地、单位、新鲜度、是否预售等和普通通用商城区分开来会显得贴合主题。从开发量来看这个题目也容易控制。核心功能少说也有六个以上用户注册登录、商品浏览搜索、商品详情、购物车、订单提交、后台管理再加上图片上传和简单的数据统计工作量足够一篇本科毕设。同时又不需要接触消息队列、分布式事务这类高难组件三到六周做完是正常的。1.2 技术栈选型我用的Spring Boot加Vue为什么技术选型是第二个必须想清楚的问题。我当时选的是Spring Boot MySQL MyBatis-Plus Vue Element UI前端构建工具用的Vite后端接口鉴权用JWT。这套组合现在仍然是毕设项目里的主流方案原因很简单。后端用Spring Boot省掉了一大堆XML配置。比如以前写SSH或者SSM光配置文件就要好几个数据源、事务、扫描包、拦截器都要手动声明。Spring Boot的自动配置把这些默认行为全部处理好你只需要在application.yml里写上数据库连接项目就能跑起来。对一个三周要完成毕设的人来说这等于把底层的重复劳动砍掉了大半。MyBatis-Plus在MyBatis基础上提供了通用的增删改查方法。比如实体类User可以直接调用userMapper.selectById(id)、userMapper.selectList(wrapper)不用自己写SQL。复杂的多表查询再写XML这样既有开发速度又能保留手写SQL的空间。和JPA比起来MyBatis-Plus对数据库控力度更高很多同学也更熟悉排查问题时心里有底。前端用Vue是因为组件化开发方便Element UI直接把表格、表单、对话框、分页做好了。页面在视觉上不会显得太“课程设计”老师打开系统看到的是一个舒服的后台界面第一印象就会好很多。如果用纯JSP加Bootstrap也不是不行但前后端代码混在一起后面加功能、改样式都会很痛苦。我给这套组合的基本定位是不要为了炫技上微服务、上高并发组件。毕设评分的重点不是你用了多冷门的框架而是你的工程结构清不清楚、业务逻辑有没有漏洞、自己写的代码能不能讲明白。1.3 功能模块拆分从登录到订单哪些是必需品我不建议一开始就堆功能。先把业务闭环打通再考虑锦上添花。我的模块划分如下。从使用角色角度系统分成三类管理员、商家农户、普通用户。管理员负责商品审核、分类管理和订单查看商家负责自己商品的上下架和库存维护普通用户负责浏览、加购、下单、支付模拟、查看订单。从功能模块角度最核心的是四个闭环用户中心注册、登录、地址管理、个人信息。商品中心商品分类、商品列表、商品详情、搜索、上下架。交易中心购物车、下单、减库存、模拟支付、订单状态更新。管理后台商品管理、订单管理、用户管理、统计仪表盘。这句话我在写文档时也用过就是“先打通用户从看到菜到买到菜的全流程”。其它比如首页轮播图、公告栏、评论留言都属于添加功能有余力再做。毕设最怕的就是功能表写了十几个结果核心流程还有bug那答辩时很难圆场。2. 数据库设计与源码目录解析2.1 核心数据表一张表一张表讲清楚数据库是整个项目的地基表结构如果设计得不好后面写代码到处都不痛快。我实际用了七张核心表外加一张省去不用的支付表。这里列出我最关键的几张表的字段设计不是全部字段但足够表达核心思路表名关键字段用途说明userid, username, password, nickname, phone, role, avatar, create_time保存用户和商家信息role区分管理员、商家、普通用户categoryid, name, parent_id, sort商品分类可以用parent_id做两级分类也可一级先用productid, category_id, name, cover_image, detail, price, stock, sales, status, farmer_id, origin, unit商品表farmer_id关联商家status控制上架/下架cartid, user_id, product_id, quantity, checked购物车checked用于下单时选中标记ordersid, order_no, user_id, total_price, status, receiver_name, receiver_phone, receiver_address, create_time订单主表保存收货人信息快照见2.2order_itemid, order_id, product_id, product_name, product_image, price, quantity, subtotal订单明细表保存下单瞬间商品信息快照addressid, user_id, consignee, phone, province, city, district, detail收货地址表用户可维护多个地址下单时选一个有些同学会把商品图片表单独拆出来这在项目里可以合并成一个cover_image加一个详情图字段因为不做多图轮播的话从简即可。category表里虽然写了parent_id但如果你的商品分类不超过两页直接用一级分类也能跑通。表设计不是越多越好够用并且能自圆其说最重要。还有一个细节密码字段不要存明文。我用了Spring Security自带的BCryptPasswordEncoder加密或者你也可以用MD5加盐。答辩时老师只要看到密码是密文存储安全这一块印象分就上来了。2.2 表关系与字段取舍为什么订单里要存地址快照很多人会把orders表设计成直接存user_id然后下单后要查收货地址时再去address表里取。这个设计在初学者眼里没毛病但实际业务中会有问题如果用户改了地址你再去查地址表拿到的就是新地址可历史订单里用户当时买的东西是送到旧地址的这就不符合真实电商场景。订单表里的receiver_name、receiver_phone、receiver_address这几个字段我明确是下单时从address表复制过来的快照这样以后无论用户在个人中心怎么改地址都不会影响已经生成的订单。同样order_item表里的product_name和product_image也是快照因为商品名称和图片可能在后续被商家改掉但历史订单明细应该保持下单那一刻的样子。这个设计在答辩时是一个非常好的加分点。老师问“你的订单为什么这么冗余”的时候你可以从数据一致性和历史数据可追溯两个角度回答远比说“为了满足同学需求”有力得多。2.3 源码目录结构后端分层和前端路由怎么拆拿到源码或者自己写源码目录结构要让人一眼看明白。后端我用的是经典分层controller、service、mapper、entity或者说domain、config、common、utils。前端用Vue Router组织页面views下面建了home、product、cart、order、user、admin等文件夹。后端需要注意的分层原则是Controller只负责接收参数和返回结果不写业务逻辑业务逻辑放到Service层数据库操作在Mapper层。比如下单这个动作Controller里就是接收购物车选中列表和地址信息调用OrderService.createOrder()Service里完成减库存、生成订单、生成订单明细、清空购物车整个过程加上事务。前端目录里我单独建了一个api文件夹把axios请求统一封装。每个页面的请求都从api模块导入方法而不是直接在组件里写axios.post。这样后期改接口地址只改一处代码可维护性强很多。源码拿到后你先看这个目录基本就能判断这个项目写得好不好如果所有代码堆在一个文件里那后续扩展和维护都是噩梦。3. 核心功能实现登录、商品浏览、下单全链路3.1 JWT登录鉴权从依赖到拦截器登录模块我推荐用JWT。它和Session最大的区别是状态不保存在服务端服务端只需要在用户登录时生成一个带签名的token字符串发回去客户端之后每次请求都在请求头里携带这个token服务端校验签名和过期时间就行。实际用到的依赖如果是Spring Boot 2.x可以引入io.jsonwebtoken:jjwt-apiio.jsonwebtoken:jjwt-implio.jsonwebtoken:jjwt-jackson生成token的代码思路是登录成功后通过Jwts.builder()设置subject为userId设置过期时间再用密钥签名。注意这里三个细节第一密钥不要放在代码里写死或者至少不要放在别人一眼能看到的地方。可以在application.yml里配置一个字符串。虽然毕设不需要特别严格但别把安全写得太儿戏。第二拦截器校验token时要允许登录接口、注册接口、商品列表接口这些不需要登录就能访问的请求放行。我在WebMvcConfigurer里注册拦截器通过addPathPatterns和excludePathPatterns配置放行路径。这个操作很简单但很多新手会忘记放行Swagger或者前端静态资源导致页面打不开。第三token过期时间我设成了24小时。毕设演示时如果token过期又要重新登录很影响展示节奏所以稍微设长一点也合理。拦截器代码的核心逻辑如下public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { throw new BusinessException(未登录); } Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } }注意实际项目里不能直接把异常抛给前端就完事应该写一个全局异常处理器给前端返回统一格式的JSON状态码、消息、数据。我写了一个GlobalExceptionHandler用RestControllerAdvice拦截异常这样前端才能统一处理错误提示。3.2 商品分页搜索MyBatis-Plus的QueryWrapper商品列表要支持分类筛选、关键词搜索和分页。MyBatis-Plus提供了一套非常方便的条件构造器QueryWrapper。比如QueryWrapperProduct wrapper new QueryWrapper(); if (categoryId ! null) { wrapper.eq(category_id, categoryId); } if (StringUtils.hasText(keyword)) { wrapper.and(w - w.like(name, keyword) .or().like(origin, keyword) .or().like(detail, keyword)); } wrapper.eq(status, 1); wrapper.orderByDesc(sales); PageProduct page productMapper.selectPage(new Page(pageNum, pageSize), wrapper);这里重点说一下like查询。不要在代码里手动拼接字符串百分号容易产生SQL注入风险。QueryWrapper的like方法会自动做参数绑定相对安全。另一个容易忽略的是status状态过滤只显示上架商品。如果忘了这个条件后台下架的商品还会出现在前台这是一个非常低级的逻辑bug。分页插件要单独配置。我在config目录里加了MybatisPlusConfig类加入分页拦截器Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }如果不加这个配置Page对象的分页不会生效查询结果会直接返回全部数据到了前端页面却发现数据一直重复排查起来很头疼。3.3 下单事务减库存、生成订单、清空购物车一个都不能少下单是整个系统最核心、最容易出错的地方。我把逻辑梳理成五步根据前端提交的购物车商品列表查出所有商品最新价格和库存。计算总金额写入订单主表。写入每个商品的订单明细。循环扣减每个商品的库存并累加销量。删除购物车中已下单的商品。这五步必须在一个事务里完成任何一步失败都要回滚。否则会出现订单生成了、商品库存没扣下次下单库存不对的问题。我直接在Service层方法上加Transactional注解。还有个容易忽略的点判断库存不足不是简单的if (stock quantity)。因为并发情况下两个用户同时下单都读到库存是10都扣减到9但实际库存可能已经变成负数。毕设环境可能不会遇到这么大的并发但答辩老师一定会问。我当时的处理方案是扣库存的SQL使用原子更新比如UPDATE product SET stock stock - #{quantity}, sales sales #{quantity} WHERE id #{productId} AND stock #{quantity}如果影响行数等于0说明库存不足或者商品不存在立即抛异常回滚。这种方式叫乐观锁的思路虽然没加version字段但通过“带条件的原子更新”避免了超卖。这个方案足够应对毕设层面的并发要求而且代码量很小。3.4 购物车批量下单的实现细节购物车批量下单有个小坑前端传过来的是一个被勾选的商品列表里面可能包含productId、quantity也可能同时包含后端不该信任的价格字段。我明确要求前端不传价格后端下单时根据productId重新查库以数据库里的价格为准。这样就算有人绕过前端伪造请求也没法把单价改成0.01元。下订单时还有一个要考虑的点是生成订单号和计算总金额。订单号我用了时间戳加随机数格式类似202506011234560001。这样看起来真实而且方便排序。计算金额统一用BigDecimal不要用double不然会出现0.10.2精度问题答辩时老师很容易问到你价格计算方式。4. 前端页面与交互从“能跑”到“能看”4.1 页面结构首页、商品列表、商品详情、购物车、订单、后台前端这一块的核心是让业务闭环可视化。我当时做了这些页面首页顶部导航、轮播图、推荐商品入口、农产品分类入口。商品列表页左侧分类右侧商品卡片支持搜索和分页。商品详情页大图、价格、库存、加入购物车和立即购买按钮。购物车页表格展示商品修改数量勾选批量结算。订单确认页选择收货地址展示金额提交订单。订单列表页按状态展示订单支持模拟支付、取消订单。用户中心个人信息、地址管理。管理后台商品管理、订单管理、分类管理、数据统计。页面数量不需要多到吓人关键是每个页面都有真实交互。比如首页不是纯静态轮播而是从后端接口读取公告商品卡片点击后能跳到详情页购物车取消勾选后再提交订单不会带入未勾选商品。这些细节比多套一个空壳页面重要得多。Element UI的表格和表单能显著提高样式完成度。下单确认页我用了一个el-steps组件展示“提交订单-支付-发货-完成”的步骤条页面立刻专业不少。页面布局我统一使用了栅格系统首页用el-row和el-col分隔。你打开源码后主要看views和components目录能很快定位到这些页面。4.2 接口对接与跨域axios封装和前端代理前后端分离后最常踩的就是跨域问题。前端运行在http://localhost:5173后端运行在http://localhost:8080浏览器的同源策略会拦截请求。我在前端api目录下封装了一个request.js创建axios实例时设置baseURL为/api然后在Vite配置文件里加一个开发代理把/api开头且未匹配到前端的请求转发到http://localhost:8080并去掉路径里的/api前缀。server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } }这样开发阶段不需要后端开启CORS避免跨域和路径重写互相纠缠。当然后端也加了一个CorsFilter作为兜底但生产部署时我推荐统一用反向代理比如Nginx。axios拦截器的核心作用是在每个请求发出前自动把token加到请求头里service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; });同时在响应拦截器里统一处理401状态码比如跳回登录页并提示“登录已过期”。这个封装非常重要不然每个页面都要自己判断登录状态。4.3 图片上传本地目录与静态资源映射图片上传是又一个容易让项目“看起来有问题”的环节。很多人把图片上传上去之后前端拿到的路径是类似D:/upload/1.jpg然后直接拼到img标签的src里页面当然显示不出来。正确做法是后端保存图片文件到一个目录比如项目的upload/文件夹数据库里只存相对路径然后通过配置把upload目录映射成一个URL前缀访问。我的配置是在application.yml里加file: upload-dir: D:/agri-upload/然后写一个WebMvcConfigurer把外部目录映射到/resources/upload/**Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadDir /); }这样前端图片地址就是 http://localhost:8080/upload/abc.jpg。上传接口用MultipartFile接收文件生成文件名时用UUID加原文件后缀避免不同用户上传同名文件互相覆盖。这个点十有八九在你答辩演示时会用到一定要提前测好。5. 本项目源码的运行与调试从导入到启动5.1 环境准备版本别随便拿最新的如果你想复现这套源码最好按照我列的环境来不然会出现一些莫名其妙的报错。JDK1.8或者11我用的1.8。Maven3.6以上。MySQL5.7或者8.0。Node16以上。前端包管理器npm或者pnpm。最好不要直接用JDK 17加Spring Boot 2.3的组合虽然大概率能跑但有一些老项目的依赖可能没有适配。MyBatis-Plus版本和Spring Boot版本也要对应。如果你拿到的源码里pom.xml已经写好了版本尽量保持一致。导入后端时我用的是IDEA选择Import Maven Project等待依赖下载完成。前端用WebStorm或VSCode都行在根目录执行npm install如果网络慢可以临时切换镜像源。5.2 初始化数据库和修改配置源码压缩包里一般会提供一个db.sql或者init.sql。在MySQL命令行或者Navicat里执行这个脚本数据库会创建好表和初始数据。初始数据里最好带一个管理员账号比如admin/admin123方便演示时直接登录后台。然后打开后端application.yml按实际环境修改数据库连接spring: datasource: url: jdbc:mysql://localhost:3306/agri_sales?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的密码端口配置默认8080如果被占用改成8081同时前端代理也要改成8081。这个不一致是新手最常见的启动问题之一。前端项目启动前检查.env.development文件中的VITE_API_BASE_URL如果已经配置过开发代理甚至可以不单独配置baseURL。我习惯把baseURL留空全部走代理因为这样部署到服务器环境时改动最小。5.3 常见启动报错与排查清单我把实际运行中遇到的几个典型报错整理成表照着排查会快很多报错现象可能原因解决方法启动后端报“Access denied for user”数据库用户名密码不对修改application.yml报“Unknown database agri_sales”没有执行SQL脚本先建库再导表启动后接口返回“Failed to obtain JDBC Connection”MySQL未启动或连接被拒检查MySQL服务和端口3306前端npm install报错依赖版本冲突或网络问题清缓存重装换镜像源页面请求返回404前端代理路径写错检查proxy里的target和rewrite商品图片显示不出图片映射路径或数据库路径不对检查upload目录映射和数据库路径还有一个问题很隐蔽MySQL 8的驱动类名是com.mysql.cj.jdbc.DriverMySQL 5.7是com.mysql.jdbc.Driver。Spring Boot 2.x会根据连接串自动判断但如果你手动指定driver-class-name新旧版本不能混用否则会报错。这种情况你用navicat连数据库正常代码里就是连不上十有八九是驱动类写错了。6. 毕设答辩与二次开发源码不是拿去交差是拿去加分的6.1 答辩常问问题和回答思路答辩时老师不会一个字一个代码去审查但一定会问你几个核心问题。我建议你提前准备“你这个项目用了什么技术架构”——回答Spring Boot Vue前后端分离后端三层架构数据库用MySQL鉴权用JWTORM用MyBatis-Plus。“订单表为什么要保存收货地址快照”——参考2.2强调历史数据一致性。“库存不足或者并发下单怎么办”——讲Transactional回滚和带条件的原子更新SQL。“如何区分管理员、商家和用户”——用user表的role字段后端拦截器校验接口权限。“图片是怎么存储的上传到服务器还是数据库”——文件存磁盘数据库存相对路径再通过资源映射访问。用BLOB存数据库会让数据库膨胀不好维护。“这个项目的难点在哪里”——推荐讲订单处理和图片上传统筹以及购物车批量下单时价格可信性问题。这些问题都不会超出我在正文里写的范围。你只要对每一个业务逻辑有自己的理解回答问题就不会卡壳。6.2 二次开发建议给项目加一两个有区分度的功能如果你的时间有余量我特别建议在源码基础上做一点“有区分度”的改造。这不是让你把别人的东西包装得更好而是通过自己动手理解整个系统的脉络。我推荐以下几个方向难度都不大在商品详情页增加累计销量排行SQL用order by sales desc后端加一个接口即可。在管理后台加一个简单的销售统计用柱状图展示最近七天的订单数。这里只需要简单分组查询然后交给前端ECharts画图。给订单增加“发货”状态管理员在后台填写物流单号用户端可以看到物流信息。给评论模块增加一个简单评分不用做多级评论一张表就能完成。每加一个功能你都要能说清楚它的表结构、接口流程和页面交互。这些功能代码量不大但在答辩时会让老师觉得你有独立思考能力比一个“完美复刻”的项目更有价值。6.3 个人实操经验源码拿到手先做三件事最后分享一点我最想提醒你的经验。不管这套源码从哪来拿到手之后不要急着直接跑、直接交先做三件事第一通读后端的所有Controller了解有哪些接口。每看到一个路由就记录它是给哪个页面用的。这样就算老师现场随机点一个页面功能你也能立刻说出对应的接口路径和大致代码位置。第二删掉一两个“冗余功能”再重新实现一遍。比如把首页轮播图改成从数据库读取把原先写死在页面的数据改成接口动态返回。这种二次改造规模很小但对理解前后端数据流动非常有帮助。第三把代码里的注释改成自己的语言。不是改得一塌糊涂而是把关键方法上方的注释重新整理一遍加上自己理解的业务说明。这样如果老师让你现场打开某个文件讲解你能讲得比照着注释念自然得多。我在做这套在线农产品销售系统时最深的感受是它的难度不在于代码量而在于能不能把一个看似简单的业务流程用工程化思维拆解成数据库、接口、页面三层并且在每一层都处理好细节。商品图片能否显示、下单后库存是否同步、权限是否生效这些细节决定答辩现场是顺利演示还是手忙脚乱。如果你正准备做这个题目我的建议是先从数据库表结构开始看再打开商品列表接口跟前端页面做一次联调把完整购物流走通一遍。这个过程走通之后整个系统对你来说就不存在理解盲区了。到时候不管是答辩、写报告还是后续找工作把项目写进简历里你都能说得头头是道。
返回列表