
简介这是一套面向高校计算机专业毕业设计的农产品商城与农资电商Java Web项目源码适合正在准备毕设或课程设计的学生参考学习。项目基于Struts、JSP、JDBC与Spring技术栈开发运行环境为Java 1.8、MySQL 5.7以上及Tomcat 8.5支持IDEA或Eclipse导入。会员端涵盖注册登录、商品浏览、购物车、收藏、下单支付、公告查看、留言及个人信息管理后台包含类别、商品、会员、订单、退货、库存记录与盘点、销售与财务统计、系统公告、留言板及密码修改等模块功能链路较为完整。资源包共2004个文件以js脚本、gif与png图片、html页面、jar依赖、jsp页面、class字节码、css样式及xml配置为主另含少量java源码与sql脚本压缩包约97.29MB。目前已有119人学习可作为毕设选题、功能拆解与代码实现的参考素材。1. 农产品商城系统从课程作业到能跑起来的农资电商 Java 项目很多同学做毕业设计时一看到“农产品商城系统”或“农资电商商城系统”就觉得无非是增删改查结果真动手才发现农资商品有季节性、有区域价、有起订量订单还要区分自提和配送库存扣减稍微不注意就超卖。这个标题对应的正是一套基于 Java 技术栈的 B2C/B2B 混合电商系统核心是让农户或农资经销商能在线浏览、下单、支付、查看物流后台能管商品、管订单、管用户。它适合计算机毕业设计选题也适合刚学完 Spring Boot 想练一个完整业务闭环的 Java 新手。下面我按实际做项目的顺序把选型、建表、核心接口、避坑点一次讲透你照着能复现出一个可演示、可答辩的版本。2. 技术选型与工程骨架为什么用 Spring Boot MyBatis 而不是 Servlet2.1 农资电商场景对技术栈的真实要求农资电商和普通商城最大的区别在于商品模型复杂同一款化肥不同品牌、不同规格、不同适用作物价格和库存都不一样。这就要求后端能灵活处理多条件筛选和动态 SQL。如果还用原生 Servlet JDBC光是拼接查询条件就能写几百行后期改一个字段要动好几个文件。所以常见做法是选 Spring Boot 做基础框架MyBatis 做持久层MySQL 存业务数据Redis 缓存热点商品和会话。前端可以用 Vue 或 Thymeleaf毕业设计里 Thymeleaf 更省事不用单独部署前端工程。选型理由很直接Spring Boot 内嵌 Tomcat打成 jar 就能跑省去配置 web.xml 的麻烦MyBatis 的 XML 映射文件对复杂查询友好农资商品的多条件筛选用if标签就能动态拼 SQLMySQL 免费且资料多答辩时老师问底层也容易答。Java 版本建议用 8 或 11别追最新很多毕业设计用的依赖在 17 上会报模块化错误。Maven 管理依赖IDEA 或 Eclipse 都能开发。2.2 用 Spring Initializr 生成可运行的最小工程第一步不是写代码而是把工程骨架跑通。打开 start.spring.io选 Maven、Java 8、Spring Boot 2.7.x依赖勾选 Spring Web、MyBatis Framework、MySQL Driver、Lombok。生成后解压用 IDEA 打开等待依赖下载完成。然后改application.yml配置数据库连接和 MyBatis 映射路径。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/agri_mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.agrimal.entity configuration: map-underscore-to-camel-case: true这段配置里map-underscore-to-camel-case是关键它让数据库的product_name自动映射到 Java 的productName省去手写 resultMap。serverTimezone必须设成 Asia/Shanghai否则插入时间会差 8 小时答辩演示时订单时间对不上就尴尬了。数据库名agri_mall要先在 MySQL 里建好字符集用 utf8mb4支持 emoji 和生僻字。2.3 分层包结构与统一返回格式工程跑起来后按controller、service、mapper、entity、dto、vo、common建包。common里放统一返回类ResultT包含 code、msg、data 三个字段。所有接口都返回这个格式前端处理起来一致也方便你加全局异常处理。比如商品列表接口返回Result.success(list)库存不足返回Result.error(库存不足)。这一步看着简单但很多同学前期不统一后期改接口格式改到崩溃血泪经验就是一开始就定好。3. 数据库设计农资商品表和订单表的字段怎么定3.1 商品表要预留农资特有字段普通商城商品表可能只有名称、价格、库存、分类。农资商品必须加brand品牌、spec规格如 50kg/袋、crop_type适用作物、region_limit限售区域、min_order_qty起订量。这些字段直接影响下单逻辑起订量没达到不能提交订单限售区域外的收货地址要拦截。建表 SQL 如下CREATE TABLE product ( id bigint NOT NULL AUTO_INCREMENT, name varchar(128) NOT NULL COMMENT 商品名称, brand varchar(64) DEFAULT NULL COMMENT 品牌, spec varchar(64) DEFAULT NULL COMMENT 规格, crop_type varchar(64) DEFAULT NULL COMMENT 适用作物, price decimal(10,2) NOT NULL COMMENT 单价, stock int NOT NULL DEFAULT 0 COMMENT 库存, min_order_qty int DEFAULT 1 COMMENT 起订量, region_limit varchar(255) DEFAULT NULL COMMENT 限售区域逗号分隔, category_id int DEFAULT NULL, status tinyint DEFAULT 1 COMMENT 1上架 0下架, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;price用 decimal 不用 double避免浮点精度问题农资单价动辄几百块差一分钱对不上账。region_limit存省份编码逗号分隔简单够用别一上来就搞区域表关联毕业设计时间有限。status控制上下架后台管理用。3.2 订单表与订单明细表的关联设计订单主表存订单号、用户 id、总金额、收货地址、订单状态、创建时间。订单明细表存订单 id、商品 id、购买数量、成交单价。注意成交单价要冗余存不能只关联商品表查当前价因为商品调价后历史订单金额会变这是电商系统的基本要求。订单状态用枚举0 待付款、1 已付款、2 已发货、3 已完成、4 已取消。CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL, user_id bigint NOT NULL, total_amount decimal(10,2) NOT NULL, address varchar(255) NOT NULL, status tinyint DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL, product_id bigint NOT NULL, product_name varchar(128) NOT NULL, price decimal(10,2) NOT NULL, quantity int NOT NULL, PRIMARY KEY (id), KEY idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;order_no加唯一索引防止重复提交生成相同订单号。product_name也冗余存商品被删除后订单详情还能显示。这些细节答辩时老师一问就能看出你有没有实际考虑过业务。4. 核心业务接口商品筛选、下单和库存扣减怎么写4.1 多条件商品查询的 MyBatis 动态 SQL农资商品筛选条件多用户可能按品牌、作物、价格区间查。用 MyBatis 的where和if动态拼条件避免写多个查询方法。select idselectByCondition resultTypecom.example.agrimal.entity.Product SELECT * FROM product where if testbrand ! null and brand ! AND brand #{brand} /if if testcropType ! null and cropType ! AND crop_type #{cropType} /if if testminPrice ! null AND price gt; #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if AND status 1 /where ORDER BY id DESC LIMIT #{offset}, #{limit} /selectwhere标签会自动去掉第一个多余的 AND不用手写 11。gt;和lt;是 XML 转义直接写和会报错。分页用 LIMIToffset 和 limit 由 Service 层算好传入。如果数据量大可以换 PageHelper 插件但毕业设计数据量小手写分页更可控。4.2 下单接口的库存扣减与事务控制下单是核心也是最容易翻车的地方。流程是校验起订量、校验库存、扣库存、生成订单、生成明细。这五步必须在一个事务里任何一步失败全部回滚。库存扣减用UPDATE product SET stock stock - #{qty} WHERE id #{id} AND stock #{qty}靠数据库行锁保证并发安全返回影响行数为 0 说明库存不足。Service public class OrderService { Autowired private ProductMapper productMapper; Autowired private OrderMapper orderMapper; Transactional(rollbackFor Exception.class) public Result createOrder(Long userId, Long productId, int quantity, String address) { Product product productMapper.selectById(productId); if (product null || product.getStatus() 0) { return Result.error(商品不存在或已下架); } if (quantity product.getMinOrderQty()) { return Result.error(未达到起订量 product.getMinOrderQty()); } int affected productMapper.reduceStock(productId, quantity); if (affected 0) { return Result.error(库存不足); } Orders order new Orders(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setTotalAmount(product.getPrice().multiply(new BigDecimal(quantity))); order.setAddress(address); order.setStatus(0); orderMapper.insert(order); // 省略订单明细插入 return Result.success(order.getOrderNo()); } }Transactional的rollbackFor Exception.class必须加否则遇到非运行时异常不回滚。reduceStock的 SQL 里带stock #{qty}条件这是防超卖的关键比先查再扣可靠得多。订单号生成可以用时间戳加随机数别用 UUID太长且无序影响索引性能。4.3 订单超时未支付自动取消的定时任务用户下单后 30 分钟不付款订单要自动取消并回滚库存。用 Spring 的Scheduled定时任务每分钟扫一次超时订单。Component public class OrderTimeoutTask { Autowired private OrderMapper orderMapper; Autowired private ProductMapper productMapper; Scheduled(cron 0 * * * * ?) public void cancelTimeoutOrders() { ListOrders list orderMapper.selectTimeoutOrders(30); for (Orders order : list) { orderMapper.updateStatus(order.getId(), 4); // 回滚库存根据订单明细逐条加回 } } }cron 0 * * * * ?表示每分钟第 0 秒执行。selectTimeoutOrders查创建时间超过 30 分钟且状态为 0 的订单。回滚库存时要注意如果商品已被删除加回会失败所以商品表最好用逻辑删除别物理删除。这个定时任务在答辩时是个加分项说明你考虑了完整业务闭环。5. 避坑与排查农资商城项目里最容易翻车的 4 个点5.1 库存扣成负数现象并发下单后商品库存显示 -5。原因先查库存再扣减两个线程同时查到库存 10都以为够各自扣 10。解决用UPDATE ... WHERE stock qty原子操作或者用 Redis 分布式锁。毕业设计用前者就够了简单可靠。5.2 订单金额与明细对不上现象订单主表总金额 100明细加起来 120。原因下单时先算总金额再逐条插明细中间商品调价了。解决下单时把商品价格快照到明细里总金额用明细累加别单独算。或者加事务但快照更稳妥。5.3 中文乱码现象商品名称存进数据库变成问号。原因数据库连接没设characterEncodingutf8或者建表时字符集不是 utf8mb4。解决连接串加useUnicodetruecharacterEncodingutf8建库建表统一 utf8mb4。已经乱码的数据改不回来只能重插。5.4 定时任务不执行现象超时订单一直不取消。原因启动类没加EnableScheduling。解决在 Spring Boot 启动类上加这个注解。另外注意 cron 表达式是 6 位不是 Linux 的 5 位别写错。6. 进阶技巧用 Redis 缓存商品详情和验证接口性能商品详情页是读多写少每次查数据库浪费资源。用 Redis 缓存商品对象key 用product:detail:{id}过期时间设 10 分钟。查的时候先查 Redis没有再去数据库然后回写。改商品时删缓存别更新缓存避免并发写不一致。public Product getProductById(Long id) { String key product:detail: id; String json redisTemplate.opsForValue().get(key); if (json ! null) { return JSON.parseObject(json, Product.class); } Product product productMapper.selectById(id); if (product ! null) { redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 10, TimeUnit.MINUTES); } return product; }验证性能可以用 JMeter 或 Apache Bench对商品列表接口压 100 并发看响应时间和错误率。没加缓存前可能 200ms加了之后降到 20ms 以内。答辩时拿这个数据说话比空口说“性能好”有说服力。我一般还会在接口里加个简单的耗时日志用System.currentTimeMillis()前后差方便定位慢查询。做这个项目最大的教训是别一上来就堆功能先把商品、订单、库存三个核心表跑通再逐步加支付、物流、评论。我见过太多同学前期贪多最后连下单都跑不起来。希望帮到你。本文还有配套的精品资源点击获取