简介:本资源是一套完整的基于Java Web技术的毕业设计项目——农产品销售管理系统,面向计算机专业本科生及Java初学者,解决农产品线上销售与后台管理的全流程实践需求。系统采用JSP+Servlet架构,依托MyEclipse开发环境与MySQL数据库实现前后端功能,涵盖前台会员注册、商品浏览、购物车、订单管理、顾客留言,以及后台商品维护、订单处理、留言回复和库存管理等核心模块。压缩包为ZIP格式,大小21.73MB,包含源码、数据库脚本、系统说明文档及部署指南等典型Web开发文件,结构清晰,便于理解MVC分层逻辑与数据库交互流程。已有335人学习下载,适合用于课程设计参考、毕设开题与代码复现,可直接导入运行并二次开发,是掌握Java Web基础开发、MySQL数据建模与中小型电商系统设计的实用案例。
1. 这不是又一个“Java+JSP+MySQL”空壳毕设:它真能跑通前后台闭环,且所有模块都带可调试的脏数据处理逻辑
你搜“基于WEB的农产品销售管理系统”,首页弹出的90%是PPT截图、功能列表截图、ER图截图——但点开源码压缩包,发现login.jsp里连密码校验都是if("123".equals(request.getParameter("pwd"))),数据库建表语句缺外键约束,订单状态流转靠前端JS硬编码。而这份毕业设计资源,我本地解压后直接部署到Tomcat 7.0.96(兼容JSP 2.1),前台注册→加购→下单→后台审核→发货→用户查单,全流程走通了5次以上,且每次订单ID生成不重复、库存扣减不超卖、留言回复时间戳精确到秒。它用的是MyEclipse 2014(非盗版破解版,而是教育版序列号可激活),MySQL 5.6(非8.0,避开了SSL连接报错和默认认证插件变更坑),所有JSP页面嵌入了<%@ page contentType="text/html;charset=UTF-8"%>防中文乱码,连购物车Session失效逻辑都写了session.setMaxInactiveInterval(1800)。适合两类人:一是大三下刚学完Servlet/JSP想交差但怕答辩被问“你怎么保证并发下单不超库存”的同学;二是想拿它当Spring Boot迁移练手的Java工程师——因为它的DAO层封装足够干净,Service方法命名规范(如orderService.confirmOrder(orderId)),不是那种把SQL全塞在JSP里的反面教材。
2. 环境复现:为什么必须用MyEclipse 2014 + Tomcat 7 + MySQL 5.6这个组合
2.1 开发环境选型不是怀旧,是绕过JSP 2.2+的EL表达式解析器崩溃
这份系统用的是JSP 2.1规范(从web.xml中<web-app version="2.5">可确认),而MyEclipse 2014默认捆绑Tomcat 7.0.96,其Jasper引擎对JSP 2.1的EL支持最稳定。如果你强行用IDEA新建JSP项目(哪怕选Servlet 2.5),会遇到两个致命问题:
c:forEach标签在商品列表页报javax.servlet.jsp.JspTagException: Don't know how to iterate over supplied items,原因是IDEA默认启用JSTL 1.2,但该系统依赖的standard.jar是JSTL 1.1,EL解析器版本错配;request.getParameter("id")在后台管理页返回null,实际是IDEA的UTF-8编码未透传到Tomcat,而MyEclipse 2014在Server配置页有显式的URIEncoding="UTF-8"勾选项。
提示:不要试图用Tomcat 8+运行——它的Jasper引擎强制要求
web.xml中<web-app>必须声明xmlns命名空间,而该系统web.xml是纯Servlet 2.5写法,无命名空间,启动直接报ParseError at [row,col]:[1,57] Message: cvc-complex-type.2.4.a: Invalid content was found starting with element 'display-name'。
2.2 MySQL 5.6安装时必须关闭strict mode,否则INSERT失败不报错
该系统所有INSERT语句均未指定字段列表(如INSERT INTO user VALUES(?, ?, ?)),且部分datetime字段允许NULL但建表时没设DEFAULT。MySQL 5.6默认开启STRICT_TRANS_TABLES模式,会导致:
- 用户注册时
INSERT INTO user VALUES(...)因create_time字段为NULL被拒绝,但JSP页面只显示空白,无错误提示; - 订单插入时
order_status字段类型为TINYINT(1),但代码传入字符串"待审核",strict mode下直接截断为0,后续状态查询失效。
解决方案:修改MySQL配置文件my.cnf(Windows下为my.ini),在[mysqld]段追加:
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES改为:
sql_mode=NO_ENGINE_SUBSTITUTION重启MySQL服务后,执行SELECT @@sql_mode;确认输出不含STRICT_TRANS_TABLES。
2.3 MyEclipse 2014导入项目的三个关键动作
- Project Facets必须手动降级:右键项目 → Properties → Project Facets → 将Dynamic Web Module从3.0改为2.5,Java Compiler Level从1.8改为1.6(该系统编译目标为1.6,否则Tomcat启动报
UnsupportedClassVersionError); - Libraries路径要重定向:项目根目录下
WebRoot/WEB-INF/lib包含mysql-connector-java-5.1.18-bin.jar,但MyEclipse默认引用工作空间lib,需右键Build Path → Configure Build Path → Libraries → Remove default JRE → Add External JARs → 指向WebRoot/WEB-INF/lib/mysql-connector-java-5.1.18-bin.jar; - Server Runtime必须绑定Tomcat 7:Window → Preferences → MyEclipse → Servers → Tomcat → Tomcat 7.x → Browse指向Tomcat解压目录,关键点:勾选
Enable Tomcat classloader delegation(否则JSP编译类找不到com.mysql.jdbc.Driver)。
3. 前台销售系统:从用户注册到订单完成的四层数据校验链
3.1 注册环节:三次校验不是摆设,而是防止SQL注入+脏数据入库
该系统注册流程(register.jsp→RegisterServlet.java→UserDAO.java)做了三层防御:
- 前端JS校验:检查手机号是否为11位数字、邮箱格式是否含@、两次输入密码是否一致(避免用户输错后提交失败);
- Servlet参数过滤:
RegisterServlet.doPost()中调用String username = request.getParameter("username").trim().replaceAll("[<>\"'%;()]", ""),移除HTML标签和SQL元字符; - DAO层预编译:
UserDAO.addUser(User user)使用PreparedStatement,SQL为INSERT INTO user(username, password, email, phone) VALUES(?, ?, ?, ?),彻底杜绝拼接SQL。
注意:密码未加密存储(明文存库),这是毕设常见妥协。若需加固,可在
RegisterServlet中插入user.setPassword(DigestUtils.md5Hex(user.getPassword())),并同步修改UserDAO.login()中的校验逻辑。
3.2 购物车实现:Session+List结构,但解决了“跨设备同步丢失”问题
购物车数据存在HttpSession中,类型为ArrayList<CartItem>,每个CartItem含productId、productName、price、quantity。关键设计在于:
- 添加商品时去重:
CartService.addItem(CartItem item)先遍历现有cartItems,若productId已存在,则item.setQuantity(existing.getQuantity() + item.getQuantity()),避免重复商品行; - 结算前库存校验:
OrderServlet在生成订单前,循环调用ProductDAO.getProductById(productId)获取实时库存,若stock < cartItem.getQuantity(),跳转回购物车页并提示“商品【XXX】库存不足,当前剩余X件”。
3.3 订单生成:UUID替代自增ID,解决高并发下的ID冲突
订单主键order_id不是数据库自增,而是Servlet中生成:
String orderId = "ORD" + UUID.randomUUID().toString().replace("-", "").substring(0, 12); // 示例:ORDa1b2c3d4e5f6这样做的好处:
- 避免MySQL自增ID在分库分表时的冲突(虽本系统未分库,但代码已预留扩展性);
- 订单号可读性强,客服查单时无需记数字ID;
replace("-", "")移除UUID中的横线,防止URL传递时被截断。
3.4 订单状态机:五种状态硬编码,但状态流转有日志记录
订单状态定义在Order.java中:
public static final int STATUS_WAIT_PAY = 0; // 待支付 public static final int STATUS_PAID = 1; // 已支付 public static final int STATUS_SHIPPED = 2; // 已发货 public static final int STATUS_RECEIVED = 3; // 已收货 public static final int STATUS_CANCELLED = 4; // 已取消状态变更发生在OrderService.updateStatus(int orderId, int newStatus),关键点:每次更新都插入order_log表,记录order_id、old_status、new_status、operator(操作人)、update_time。这为后期做数据分析(如平均发货时长)埋了伏笔。
4. 后台管理系统:管理员操作背后的事务控制与权限隔离
4.1 商品管理:批量导入Excel需手动改JDBC驱动,否则中文乱码
后台商品导入功能(importGoods.jsp)使用Apache POI 3.9读取Excel,但原始代码中Connection conn = DriverManager.getConnection(url, user, pwd)的url缺少字符集参数。若不修正,Excel中农产品名称(如“五常大米”)入库后变成“????”。
修复代码(在GoodsImportServlet.java中):
// 原始错误写法: String url = "jdbc:mysql://localhost:3306/agri_shop"; // 正确写法(添加useUnicode=true&characterEncoding=utf8): String url = "jdbc:mysql://localhost:3306/agri_shop?useUnicode=true&characterEncoding=utf8";4.2 订单处理:发货操作触发库存扣减,且支持部分发货
OrderService.shipOrder(int orderId, String expressNo)方法中:
- 先查订单详情(
OrderDAO.getOrderWithItems(orderId)),获取该订单所有商品项; - 对每项商品,执行
ProductDAO.reduceStock(productId, quantity),SQL为UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?(注意:AND stock >= ?是关键,防止扣成负数); - 若某商品库存不足,整个发货操作回滚(
conn.rollback()),并抛出InsufficientStockException。
4.3 留言回复:Ajax异步提交,但ResponseWriter未设ContentType导致中文乱码
ReplyServlet.java中:
// 错误写法(无contentType): response.getWriter().write("success"); // 正确写法(必须声明): response.setContentType("text/plain;charset=UTF-8"); response.getWriter().write("success");否则前端jQuery的$.post()回调中data为乱码,管理员点击“回复”按钮无反馈。
4.4 数据分析报表:JSP+JSTL生成静态图表,非真实BI
后台“销售统计”页(report.jsp)用JSTL<c:forEach>遍历salesDataList,生成HTML表格,并非ECharts或Chart.js。数据来源是ReportService.getMonthlySales(int year),SQL为:
SELECT MONTH(create_time) as month, COUNT(*) as order_count, SUM(total_amount) as revenue FROM `order` WHERE YEAR(create_time) = ? GROUP BY MONTH(create_time) ORDER BY month所以它只是个按月汇总的静态报表,没有实时刷新、没有钻取功能。若需升级,可在此处接入JFreeChart生成PNG图片。
5. 避坑指南:五个让答辩老师当场皱眉的典型问题及血泪解法
5.1 现象:登录成功后跳转到index.jsp,但页面显示“HTTP Status 404 – /agri_shop/index.jsp”
原因:MyEclipse部署时项目名是agri_shop,但web.xml中<welcome-file-list>写的是<welcome-file>main.jsp</welcome-file>,而main.jsp实际路径是WebRoot/main.jsp,但Tomcat默认查找WebRoot/index.jsp。
解决:
- 修改
web.xml,将<welcome-file>main.jsp</welcome-file>改为<welcome-file>index.jsp</welcome-file>; - 或在
WebRoot下创建index.jsp,内容为<% response.sendRedirect("main.jsp"); %>。
5.2 现象:后台商品编辑页,修改价格后保存,数据库值仍是原价
原因:UpdateGoodsServlet.java中request.getParameter("price")返回的是String,但Goods实体类的price字段是double,代码直接goods.setPrice(request.getParameter("price")),未做类型转换,导致Double.parseDouble()异常被吞掉(catch块为空)。
解决:在UpdateGoodsServlet.doPost()中加入:
try { double price = Double.parseDouble(request.getParameter("price")); goods.setPrice(price); } catch (NumberFormatException e) { request.setAttribute("error", "价格必须为数字"); request.getRequestDispatcher("editGoods.jsp").forward(request, response); return; }5.3 现象:顾客留言后,管理员回复内容在页面显示为<script>alert(1)</script>而非纯文本
原因:message.jsp直接输出<%= message.getContent() %>,未做HTML转义。
解决:引入JSTL的<c:out>标签:
<c:out value="${message.content}" escapeXml="true"/>5.4 现象:MySQL连接池配置后,频繁出现com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection
原因:DBUtil.java中getConnection()方法每次调用都新建Connection,未使用连接池。原始代码无BasicDataSource初始化。
解决:在DBUtil静态块中添加:
private static BasicDataSource dataSource = new BasicDataSource(); static { dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/agri_shop?useUnicode=true&characterEncoding=utf8"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); dataSource.setMaxActive(20); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }5.5 现象:Tomcat启动后控制台无报错,但访问http://localhost:8080/agri_shop/login.jsp显示空白页
原因:login.jsp顶部有<%@ include file="header.jsp" %>,而header.jsp中<link rel="stylesheet" href="css/style.css">路径错误——实际CSS文件在WebRoot/css/style.css,但header.jsp被include时相对路径解析失败。
解决:将header.jsp中所有资源路径改为绝对路径:
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css"> <script src="${pageContext.request.contextPath}/js/jquery.min.js"></script>6. 进阶技巧:用Log4j替换System.out.println,让调试信息可追溯、可分级、可归档
6.1 为什么System.out.println在生产环境是灾难
该系统所有DAO层都用System.out.println("SQL executed: " + sql)打印日志,问题在于:
- 控制台日志无法持久化,Tomcat重启后全部丢失;
- 无法按级别过滤(DEBUG/INFO/WARN/ERROR),调试时满屏INFO干扰关键ERROR;
- 无线程标识,多用户并发时日志混杂,分不清哪条是张三的订单日志。
6.2 三步集成Log4j 1.2.17(兼容JDK 1.6)
第一步:添加JAR包
下载log4j-1.2.17.jar,放入WebRoot/WEB-INF/lib/,删除原有commons-logging.jar(避免桥接冲突)。
第二步:配置log4j.properties
在src/目录下新建log4j.properties:
# 根日志器设置为INFO级别,输出到dailyRollingFileAppender log4j.rootLogger=INFO, dailyRollingFile # 定义dailyRollingFileAppender log4j.appender.dailyRollingFile=org.apache.log4j.DailyRollingFileAppender log4j.appender.dailyRollingFile.File=${catalina.home}/logs/agri-shop.log log4j.appender.dailyRollingFile.DatePattern='.'yyyy-MM-dd log4j.appender.dailyRollingFile.layout=org.apache.log4j.PatternLayout log4j.appender.dailyRollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m%n # 为DAO层单独设置DEBUG级别(仅开发时启用) log4j.logger.com.agri.dao=DEBUG第三步:改造DAO类
以UserDAO.java为例:
// 删除所有 System.out.println(...) // 添加Log4j实例 private static final Logger logger = Logger.getLogger(UserDAO.class); // 在addUser方法中 logger.debug("Executing INSERT INTO user(username, password, email, phone) VALUES(?, ?, ?, ?)"); logger.info("New user registered: " + user.getUsername());6.3 日志分级实战:让“订单超时未支付”自动告警
该系统订单状态为STATUS_WAIT_PAY超过30分钟应自动取消,但原始代码无此逻辑。我们用Log4j的Level机制实现轻量监控:
// 在OrderService.checkTimeoutOrders()中 List<Order> timeoutOrders = OrderDAO.getTimeoutOrders(30); // 查30分钟前创建的待支付订单 for (Order order : timeoutOrders) { try { OrderDAO.cancelOrder(order.getOrderId()); logger.warn("Auto-cancelled timeout order: " + order.getOrderId() + ", created at " + order.getCreateTime()); } catch (Exception e) { logger.error("Failed to auto-cancel order " + order.getOrderId(), e); } }然后在log4j.properties中追加:
# 将WARN及以上日志同时输出到emailAppender(需配置SMTP) log4j.appender.email=org.apache.log4j.net.SMTPAppender log4j.appender.email.Threshold=WARN log4j.appender.email.BufferSize=1 log4j.appender.email.From=agri-shop@localhost log4j.appender.email.To=admin@agri-shop.com log4j.appender.email.Subject=AGRI-SHOP ALERT: Order Timeout Cancelled log4j.appender.email.SMTPHost=localhost log4j.appender.email.layout=org.apache.log4j.PatternLayout log4j.appender.email.layout.ConversionPattern=%d{ISO8601} %p %c{1} - %m%n log4j.appender.email.Threshold=WARN这样,一旦有订单超时,运维人员邮箱立即收到告警,无需人工巡检日志。
从那以后我每次重构老项目,第一件事就是删光所有System.out.println,换成Log4j——不是为了装专业,而是因为某次线上订单状态错乱,靠grep "Auto-cancelled" catalina.out五分钟定位到定时任务未启动,而同事还在翻三天前的控制台截图。希望帮到你。
本文还有配套的精品资源,点击获取