☰
Java Web农产品销售系统毕设实战:JSP+Servlet+MySQL完整开发指南
2026/10/1 13:36:24 网站建设 项目流程

简介:本资源是一套完整的基于Java Web技术的毕业设计项目——农产品销售管理系统,面向计算机专业本科生及Java Web初学者,解决传统农产品销售信息不对称、管理低效、购销流程不透明等实际问题。系统采用JSP+Servlet+MySQL架构,以MyEclipse为开发环境,涵盖前台用户端(会员注册、商品浏览、购物车、订单管理、留言反馈)与后台管理端(商品维护、订单处理、库存监控、数据统计)两大模块,具备典型MVC分层结构与完整业务闭环。压缩包为ZIP格式,大小21.73MB,包含源码、数据库脚本、部署说明及系统设计文档等核心文件,其中.java、.jsp、.sql文件构成开发主体,支撑从环境搭建到功能验证的全流程实践。已有335人学习下载,读者可直接导入运行,掌握Web应用开发规范、数据库建模方法、前后端交互逻辑及毕业设计答辩所需的技术实现细节与系统演示能力。

1. 毕业设计选这个题真不亏:一个能跑通、能演示、能答辩的WEB农产品销售系统,到底长什么样?

你打开答辩PPT第一页写着“基于WEB的农产品销售管理系统”,台下老师没点头也没皱眉——这太常见了,但恰恰说明它稳。不是那种“用大模型生成菜谱”的玄学项目,而是实打实要连上MySQL、能增删改查订单、用户登录不裸奔、商品列表带图片、后台能导出Excel报表的完整闭环。它不追求算法黑科技,但必须把Java Web老三样(JSP + Servlet + JDBC)扎扎实实串起来,再加一层基础安全意识(比如密码不存明文、登录态校验不靠前端JS硬扛)。适合本科毕设的核心在于:代码量可控(3000~5000行)、部署门槛低(Tomcat + MySQL本地就能跑)、功能边界清晰(用户/管理员双角色、商品/订单/库存三模块),且所有技术点——MyEclipse写代码、MySQL建库、JSP渲染页面、Servlet处理逻辑——全是企业里还活着的“冷门但没死透”的技能。别被“Spring Boot”吓退,这个题的价值不在框架多新,而在你能亲手把HTTP请求怎么进Servlet、ResultSet怎么转成List、表单提交后重定向怎么防重复提交这些链条一节节捋直。答辩时老师问“登录密码怎么存”,你答“BCrypt加密+盐值”,比说“用了Spring Security”更有说服力。


2. 从零搭起骨架:用MyEclipse建Web项目、配Tomcat、连MySQL的最小可行路径

2.1 创建Dynamic Web Project并确认编译输出路径

MyEclipse里新建项目不能选“Java Project”,必须选File → New → Other → Web → Dynamic Web Project。项目名建议用AgricSaleSystem(避免中文和空格)。关键设置有三处:

  • Target runtime:选已配置好的Apache Tomcat v8.5(或v9.0,避开v10+因Servlet 5.0对JSP支持变弱);
  • Configuration:选“Default Configuration for Apache Tomcat v8.5”;
  • Content directory:保持默认WebContent,这是JSP、CSS、JS存放根目录。

提示:如果创建后Project Explorer里看不到WebContent/WEB-INF/web.xml,右键项目 →Properties → Project Facets,勾选Dynamic Web Module并设版本为3.1(兼容JSP 2.3),MyEclipse会自动生成web.xml骨架。

2.2 配置Tomcat服务器并验证首页可访问

Window → Preferences → MyEclipse → Servers → Tomcat → Tomcat 8.x,填入Tomcat解压路径(如D:\apache-tomcat-8.5.93),勾选Enable Tomcat。启动前务必检查:

  • Tomcatbin/startup.bat能双击运行且无端口冲突(默认8080,若被占用,在conf/server.xml中改<Connector port="8080"为8081);
  • MyEclipse中右键项目 →Run As → Run on Server,选择刚配好的Tomcat。

成功标志:浏览器打开http://localhost:8080/AgricSaleSystem/显示空白页(因还没放index.jsp),控制台输出INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] ms。此时项目已挂载到Tomcat,后续所有JSP/Servlet都通过此路径访问。

2.3 在MySQL中建库建表并测试JDBC连接

用Navicat或命令行建库:

CREATE DATABASE agric_sale DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE agric_sale;

建核心表(精简版,答辩够用):

-- 用户表(含角色区分) CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 存BCrypt加密后字符串 role ENUM('user','admin') DEFAULT 'user', phone VARCHAR(15), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 商品表 CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, image_path VARCHAR(200), -- 存相对路径,如 "images/apple.jpg" description TEXT ); -- 订单表 CREATE TABLE `order` ( id VARCHAR(32) PRIMARY KEY, -- UUID生成,避免数字ID暴露销量 user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status ENUM('pending','paid','shipped','completed') DEFAULT 'pending', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) );

JDBC测试:在WebContent/WEB-INF/lib/下放入mysql-connector-java-8.0.33.jar(必须8.0+,否则连MySQL 8.0+报Public Key Retrieval is not allowed错误)。写一个测试Servlet:

// src/com/example/test/DbTestServlet.java @WebServlet("/dbtest") public class DbTestServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String url = "jdbc:mysql://localhost:3306/agric_sale?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"; String user = "root"; String pwd = "your_password"; // 实际用配置文件读取 try (Connection conn = DriverManager.getConnection(url, user, pwd)) { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM user"); rs.next(); resp.getWriter().println("用户总数:" + rs.getInt(1)); } catch (Exception e) { e.printStackTrace(); resp.getWriter().println("数据库连接失败:" + e.getMessage()); } } }

访问http://localhost:8080/AgricSaleSystem/dbtest若显示“用户总数:0”,说明JDBC通了——这是整个系统数据层的地基,塌了后面全白干。


3. 核心业务落地:用JSP+Servlet实现用户登录、商品展示、下单三连环

3.1 登录模块:表单提交→Servlet校验→Session存用户→跳转主页

login.jsp(放在WebContent/下):

<%@ page contentType="text/html;charset=UTF-8" %> <!DOCTYPE html> <html> <head><title>登录</title></head> <body> <form action="LoginServlet" method="post"> <input type="text" name="username" placeholder="用户名" required><br> <input type="password" name="password" placeholder="密码" required><br> <button type="submit">登录</button> </form> <p><a href="register.jsp">注册新用户</a></p> </body> </html>

LoginServlet.java(src/com/example/servlet/):

@WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); // 1. 从数据库查用户(实际应封装到DAO层) User user = UserDao.findByUsername(username); // 假设UserDao已实现 if (user != null && BCrypt.checkpw(password, user.getPassword())) { // 2. 密码正确:存入Session,重定向到主页 req.getSession().setAttribute("currentUser", user); resp.sendRedirect("index.jsp"); // 注意:重定向避免F5重复提交 } else { // 3. 失败:回传错误信息到login.jsp req.setAttribute("error", "用户名或密码错误"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } }

index.jsp(登录后首页):

<%@ page contentType="text/html;charset=UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <% // 强制校验登录态,未登录跳回登录页 if (session.getAttribute("currentUser") == null) { response.sendRedirect("login.jsp"); return; } %> <html> <head><title>首页</title></head> <body> <h2>欢迎,${currentUser.username}!</h2> <p><a href="LogoutServlet">退出登录</a></p> <h3>最新农产品</h3> <c:forEach items="${productList}" var="p"> <div> <img src="${p.imagePath}" width="100" alt="${p.name}"> <p>${p.name} - ¥${p.price} <a href="addCart.jsp?id=${p.id}">加入购物车</a></p> </div> </c:forEach> </body> </html>

逻辑说明:JSP负责展示,Servlet负责处理业务逻辑和跳转。req.getSession().setAttribute()是会话管理核心,response.sendRedirect()确保URL刷新,避免用户F5导致重复登录。JSTL标签<c:forEach>替代脚本片段,更安全易读。

3.2 商品展示:Servlet查库→JSP用JSTL循环渲染

ProductListServlet.java:

@WebServlet("/ProductListServlet") public class ProductListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<Product> products = ProductService.getAllProducts(); // 封装好的业务方法 req.setAttribute("productList", products); req.getRequestDispatcher("index.jsp").forward(req, resp); } }

关键点:ProductService.getAllProducts()内部调用ProductDao.findAll(),后者用JDBC执行SELECT * FROM product WHERE stock > 0。注意SQL注入防护——这里用预编译语句(PreparedStatement),而非拼接字符串。

3.3 下单流程:购物车暂存→订单生成→库存扣减原子性保障

addCart.jsp(简化版,实际应有数量选择):

<% // 从URL参数获取商品ID,存入Session购物车(Map<Integer, Integer> cart) Integer productId = Integer.parseInt(request.getParameter("id")); Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } cart.put(productId, cart.getOrDefault(productId, 0) + 1); response.sendRedirect("cart.jsp"); %>

OrderSubmitServlet.java(核心事务):

@WebServlet("/OrderSubmitServlet") public class OrderSubmitServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { User user = (User) req.getSession().getAttribute("currentUser"); Map<Integer, Integer> cart = (Map<Integer, Integer>) req.getSession().getAttribute("cart"); if (cart == null || cart.isEmpty()) { resp.sendRedirect("cart.jsp?msg=购物车为空"); return; } Connection conn = null; try { conn = JdbcUtil.getConnection(); // 自定义工具类获取连接 conn.setAutoCommit(false); // 开启事务 // 1. 生成订单号(UUID) String orderId = UUID.randomUUID().toString().replace("-", ""); // 2. 插入订单主表 String sql1 = "INSERT INTO `order`(id, user_id, total_amount, status) VALUES(?, ?, ?, ?)"; PreparedStatement ps1 = conn.prepareStatement(sql1); ps1.setString(1, orderId); ps1.setInt(2, user.getId()); ps1.setBigDecimal(3, calculateTotal(cart)); // 计算总价 ps1.setString(4, "pending"); ps1.executeUpdate(); // 3. 扣减库存(关键:WHERE stock >= ? 防超卖) for (Map.Entry<Integer, Integer> entry : cart.entrySet()) { String sql2 = "UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?"; PreparedStatement ps2 = conn.prepareStatement(sql2); ps2.setInt(1, entry.getValue()); ps2.setInt(2, entry.getKey()); ps2.setInt(3, entry.getValue()); // 确保库存足够 int rows = ps2.executeUpdate(); if (rows == 0) { throw new RuntimeException("商品ID " + entry.getKey() + " 库存不足"); } } conn.commit(); req.getSession().removeAttribute("cart"); // 清空购物车 resp.sendRedirect("orderSuccess.jsp?orderId=" + orderId); } catch (Exception e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) {} } req.setAttribute("error", "下单失败:" + e.getMessage()); req.getRequestDispatcher("cart.jsp").forward(req, resp); } } }

参数说明:conn.setAutoCommit(false)是事务起点;UPDATE ... WHERE stock >= ?是库存扣减防超卖的硬核写法,比先查再更新(Read-Modify-Write)更可靠;UUID.randomUUID()生成全局唯一订单号,避免自增ID暴露业务规模。


4. 避坑指南:MyEclipse+JSP+MySQL组合里踩过的5个真实血泪坑

4.1 现象:JSP中文乱码,页面显示“???”

原因:MyEclipse默认编码是GBK,而MySQL 8.0+默认utf8mb4,JSP文件本身没声明UTF-8,Tomcat响应头也未设charset。
解决:

  • MyEclipse:右键项目 →Properties → Resource → Text file encoding→ 选UTF-8;
  • JSP顶部加<%@ page contentType="text/html;charset=UTF-8" %>;
  • web.xml中添加过滤器强制编码(防止POST乱码):
<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

注意:即使不用Spring,也可手写一个简单Filter,但用Spring的现成类最省事。

4.2 现象:MySQL 8.0连接报错Public Key Retrieval is not allowed

原因:MySQL 8.0默认开启RSA密钥交换,JDBC驱动要求显式授权。
解决:在JDBC URL末尾追加参数&allowPublicKeyRetrieval=true&useSSL=false(开发环境可关SSL,生产环境需配证书)。

血泪经验:别信网上“升级驱动就行”的说法,8.0.33驱动仍需此参数,否则连不上就是连不上。

4.3 现象:MyEclipse里修改JSP后刷新页面无变化

原因:Tomcat的JSP热加载机制失效,或浏览器缓存了旧版本。
解决:

  • MyEclipse:Window → Preferences → MyEclipse → Servers → Tomcat → Tomcat 8.x → 勾选Automatically publish when resources change;
  • 强制刷新:Ctrl+F5(Windows)或 Cmd+Shift+R(Mac);
  • 终极方案:删掉Tomcat工作目录work/Catalina/localhost/AgricSaleSystem/下所有文件,重启Tomcat。

4.4 现象:登录成功后跳转index.jsp,但${currentUser.username}显示为空

原因:req.getSession().setAttribute("currentUser", user)存的是对象,但JSP EL表达式${currentUser.username}要求User类有public的getter方法(getUsername()),且字段名与EL中一致。
解决:

  • 检查User类:private String username;必须有public String getUsername() { return username; };
  • 确保<%@ page isELIgnored="false" %>(默认true,但MyEclipse新建JSP通常已设为false);
  • 查看Tomcat日志,确认Session是否真的存进去了(System.out.println(session.getAttribute("currentUser")))。

4.5 现象:订单提交时库存扣减失败,但数据库里订单已插入

原因:事务未正确回滚,conn.rollback()被异常吞掉,或conn对象在catch块外已关闭。
解决:

  • 把Connection conn = null声明放在try块外,确保catch里能访问;
  • rollback()前加判空:if (conn != null && !conn.isClosed()) { conn.rollback(); };
  • 关键:conn.setAutoCommit(false)必须在try块内第一条执行,否则事务不生效。

5. 答辩加分项:让系统看起来更专业、更像“真实项目”的4个实操技巧

5.1 给后台加简易权限拦截——不用框架也能防越权

很多同学只做“登录后能访问”,但管理员才能删商品、用户只能看自己订单。纯Servlet时代,用Filter是最轻量方案。写AdminFilter.java:

@WebFilter("/admin/*") // 拦截所有/admin/下的请求 public class AdminFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; User user = (User) request.getSession().getAttribute("currentUser"); if (user == null || !"admin".equals(user.getRole())) { response.sendRedirect(request.getContextPath() + "/login.jsp?msg=需要管理员权限"); return; } chain.doFilter(req, resp); } }

然后把商品管理页面(如admin/productManage.jsp)放到/WebContent/admin/目录下。这样,普通用户直接访问/admin/xxx会被Filter拦住跳转——比在每个Servlet里写if判断更干净,也向老师展示了“分层拦截”意识。

5.2 用CSS+Bootstrap 3快速美化页面,避免答辩时被说“丑”

别花时间手写CSS。下载Bootstrap 3.4.1(兼容IE8,适合毕设),解压后把css/bootstrap.min.css和js/bootstrap.min.js放到WebContent/css/和WebContent/js/。在JSP头部引入:

<link rel="stylesheet" href="css/bootstrap.min.css"> <script src="js/jquery-3.6.0.min.js"></script> <script src="js/bootstrap.min.js"></script>

然后把商品列表改成卡片式布局:

<c:forEach items="${productList}" var="p"> <div class="col-md-4"> <div class="card"> <img src="${p.imagePath}" class="card-img-top" alt="${p.name}"> <div class="card-body"> <h5 class="card-title">${p.name}</h5> <p class="card-text">¥${p.price} | 库存:${p.stock}</p> <a href="addCart.jsp?id=${p.id}" class="btn btn-primary">加入购物车</a> </div> </div> </div> </c:forEach>

效果:三列响应式网格,鼠标悬停有阴影,按钮带圆角——5分钟提升视觉专业度,老师不会因为你用Bootstrap扣分,反而觉得你懂工程效率。

5.3 导出订单Excel:用Apache POI生成带表头的.xls文件

答辩时老师常问“数据能导出吗?”,写个Servlet搞定:

@WebServlet("/ExportOrderServlet") public class ExportOrderServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<Order> orders = OrderService.getUserOrders(((User)req.getSession().getAttribute("currentUser")).getId()); HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("订单列表"); // 表头 String[] headers = {"订单号", "用户", "总金额", "状态", "下单时间"}; HSSFRow headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 for (int i = 0; i < orders.size(); i++) { HSSFRow row = sheet.createRow(i + 1); row.createCell(0).setCellValue(orders.get(i).getId()); row.createCell(1).setCellValue(orders.get(i).getUser().getUsername()); row.createCell(2).setCellValue(orders.get(i).getTotalAmount().doubleValue()); row.createCell(3).setCellValue(orders.get(i).getStatus()); row.createCell(4).setCellValue(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(orders.get(i).getCreateTime())); } resp.setContentType("application/vnd.ms-excel"); resp.setHeader("Content-Disposition", "attachment; filename=orders_" + System.currentTimeMillis() + ".xls"); workbook.write(resp.getOutputStream()); } }

注意:POI 3.17对应Excel 2003格式(.xls),兼容性最好;若用POI 5.x生成.xlsx,部分老版Office打不开。依赖加poi-3.17.jar即可。

5.4 部署包瘦身:打包WAR时剔除不必要的文件

MyEclipse右键项目 →Export → WAR file,但默认会把.project、.settings等IDE文件打进WAR,Tomcat启动报错。手动清理:

  • WAR包解压后,删掉WEB-INF/classes/.settings/、WEB-INF/classes/.project;
  • WEB-INF/lib/只留必需jar:mysql-connector-java-8.0.33.jar、poi-3.17.jar、bcprov-jdk15on-1.68.jar(BCrypt加密用);
  • 最终WAR包控制在8~12MB,上传到学校服务器不卡顿。

我当年答辩前夜还在调allowPublicKeyRetrieval=true这个参数,凌晨三点看到登录页弹出“欢迎,张三!”那一刻,比收到offer还踏实。毕设不是炫技,是把一条技术链路从键盘敲到屏幕亮起,每一个NullPointerException、每一次ClassNotFoundException,都是你亲手焊上去的电路板焊点。现在你手里有MyEclipse、MySQL、Tomcat、JSP这四块砖,缺的只是把它们垒成墙的耐心——而这篇笔记,就是那张你本该早点拿到的施工图。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询