ARTICLE DETAIL

资讯详情

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

JSP+MySQL农产品商城源码实战:从环境搭建到下单事务的完整拆解

JSP+MySQL农产品商城源码实战:从环境搭建到下单事务的完整拆解 简介本资源为基于Java的农产品网上销售系统设计与实现文档面向计算机相关专业学生、课程设计或毕业设计开发者以及希望了解电商系统开发流程的初学者。文档围绕农产品线上销售场景从需求分析、系统设计到功能实现进行了完整阐述涵盖用户浏览购物、商品管理、订单处理等核心模块并采用JSP开发语言与MySQL数据库保证系统稳定性采用模块化设计便于后期扩展维护。资源包共1个docx文件大小约1.27MB内容包含摘要、英文摘要、目录及绪论等章节结构清晰便于按章节查阅与参考。目前已有72人学习下载适合需要撰写开题报告、系统设计文档或参考电商项目实现思路的读者可帮助快速理解农产品销售系统的整体架构与开发要点具有较高的参考与借鉴价值。1. 从一份 JSP 农产品商城源码说起它到底能跑出什么打开这份《基于java的农产品网上销售系统设计与实现.docx》第一眼看到的是完整的毕业论文结构绪论、技术简介、需求分析、系统设计、系统实现、测试一路到参考文献。但真正让一线开发者愿意花时间拆它的原因不是论文写得多规范而是它背后对应着一套可落地的 JSPMySQL 农产品商城实现方案。农产品网上销售系统这个选题在高校毕设里出现频率极高但大多数文档只讲“做了什么”不讲“怎么跑起来、哪里会翻车”。这份材料难得地把用户模块和管理员模块的功能边界、数据库实体、活动图都列了出来等于给了你一张施工图。它适合三类人正在做 JavaWeb 课程设计、需要一套能改能交的系统想用 JSP 技术栈练手完整电商流程的初学者以及需要一份“需求分析到数据库设计”完整参照的开发者。系统本身不复杂用户端覆盖注册登录、商品浏览、购物车、订单、退货、留言管理端覆盖会员、类别、商品、订单、退货审核。麻雀虽小但电商该有的闭环它都有。接下来我不复述论文而是把它当成一个真实项目来拆环境怎么搭、数据库怎么建、核心模块怎么写、哪些地方最容易卡住。2. 环境与数据库落地JSPMySQL 跑通的第一步2.1 技术栈选型与运行环境确认这套系统用的是 JSP 作为页面技术、MySQL 存数据、B/S 三层结构开发工具是 MyEclipse服务器是 Tomcat。这套组合在今天看来偏传统但它的好处是资料多、门槛低、学校机房环境普遍能跑。选型上没必要纠结“为什么不用 Spring Boot”因为这份资源的价值就在于它是一套纯 JSPServlet 的经典实现你能清楚看到请求怎么从浏览器到 Servlet 再到 DAO 最后落库中间没有框架帮你藏黑匣子。运行环境按文档给的最低配置来就行Windows XP 或 Win7 均可CPU 酷睿 i3 以上、内存 2G 以上、硬盘 320GB。实际我建议直接上 Win10/Win11Tomcat 用 8.5 或 9.0 版本JDK 用 1.8MySQL 用 5.7。这里有个血泪经验JDK 版本别乱升JSP 老项目在 JDK 11 以上容易遇到javax.servlet包找不到的问题因为高版本 JDK 移除了部分 Java EE 模块。MySQL 5.7 和 8.0 的驱动类名不同5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.Driver连接串还要加时区和 SSL 参数这个后面避坑章节会细说。环境变量配置是新手第一道坎。JAVA_HOME指向 JDK 安装目录CATALINA_HOME指向 Tomcat 目录Path里加上%JAVA_HOME%\bin和%CATALINA_HOME%\bin。配完在命令行敲java -version和startup.bat能起来就说明基础环境没问题。2.2 数据库表设计与建表脚本文档第 4 章给了数据库概念设计和实体但没给完整建表语句。按农产品商城的业务闭环核心表至少要有用户表、商品表、商品类别表、购物车表、订单表、订单明细表、退货表、留言表。我一般会先画实体关系再落 SQL下面这套建表脚本是按文档描述的实体补全的字段类型和约束都按实际业务来定。-- 创建数据库字符集用 utf8mb4 兼容中文和特殊符号 CREATE DATABASE agri_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE agri_shop; -- 用户表区分普通用户和管理员role 字段控制权限 CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(64) NOT NULL COMMENT 建议存 MD5 或 BCrypt 哈希, real_name VARCHAR(50), phone VARCHAR(20), address VARCHAR(200), role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品类别表 CREATE TABLE t_category ( cate_id INT PRIMARY KEY AUTO_INCREMENT, cate_name VARCHAR(50) NOT NULL, cate_desc VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品表price 用 DECIMAL 避免浮点误差stock 控制库存 CREATE TABLE t_product ( pro_id INT PRIMARY KEY AUTO_INCREMENT, pro_name VARCHAR(100) NOT NULL, cate_id INT NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, pro_img VARCHAR(200) COMMENT 图片相对路径, pro_desc TEXT, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, FOREIGN KEY (cate_id) REFERENCES t_category(cate_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 购物车表同一用户同一商品只存一条数量累加 CREATE TABLE t_cart ( cart_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, pro_id INT NOT NULL, quantity INT DEFAULT 1, UNIQUE KEY uk_user_pro (user_id, pro_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单主表order_no 用时间戳随机数生成避免自增 ID 暴露业务量 CREATE TABLE t_order ( order_id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, order_status TINYINT DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成 4已取消, receiver VARCHAR(50), receiver_phone VARCHAR(20), receiver_addr VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单明细表下单时把商品快照写进来防止商品改价影响历史订单 CREATE TABLE t_order_item ( item_id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, pro_id INT NOT NULL, pro_name VARCHAR(100) COMMENT 下单时的商品名快照, price DECIMAL(10,2) COMMENT 下单时的单价快照, quantity INT NOT NULL, FOREIGN KEY (order_id) REFERENCES t_order(order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 退货表 CREATE TABLE t_return ( return_id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, user_id INT NOT NULL, reason VARCHAR(500), audit_status TINYINT DEFAULT 0 COMMENT 0待审核 1通过 2驳回, apply_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 留言表 CREATE TABLE t_message ( msg_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, content VARCHAR(1000), reply VARCHAR(1000), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段脚本有几个参数值得说明。DECIMAL(10,2)用于金额比FLOAT可靠电商系统里金额算错是致命的。t_order_item里冗余存了pro_name和price这是订单快照的常见做法因为商品可能改价或下架历史订单必须保留成交时的信息。t_cart上建了user_idpro_id的唯一索引配合INSERT ... ON DUPLICATE KEY UPDATE quantityquantity1就能实现“重复加购只累加数量”。字符集统一用utf8mb4农产品名称里偶尔会有生僻字utf8存不下。建完表后在项目里配置数据库连接。JSP 项目通常用db.properties或直接在工具类里写死我建议抽成配置文件# db.properties jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/agri_shop?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password你的密码useUnicodetruecharacterEncodingutf8解决中文乱码useSSLfalse在本地开发时关掉 SSL 警告。如果是 MySQL 8.0驱动类改成com.mysql.cj.jdbc.DriverURL 末尾加serverTimezoneAsia/Shanghai否则会报时区错误。3. 核心模块实现从登录到下单的代码拆解3.1 登录鉴权与权限拦截登录模块是整个系统的入口文档里的活动图写得很清楚判断账号是否存在、验证码是否正确、识别用户角色。实现上分三层JSP 页面收集表单、Servlet 处理请求、DAO 查库比对。下面是一个典型的登录 Servlet 写法。// LoginServlet.java WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); String code req.getParameter(code); // 1. 校验验证码session 里存的是生成时的值 String sessionCode (String) req.getSession().getAttribute(checkCode); if (code null || !code.equalsIgnoreCase(sessionCode)) { req.setAttribute(msg, 验证码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } // 2. 密码做 MD5 后再比对数据库不存明文 String md5Pwd MD5Util.encode(password); UserDao dao new UserDao(); User user dao.findByUsernameAndPwd(username, md5Pwd); if (user null) { req.setAttribute(msg, 账号或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } // 3. 登录成功写 session按角色跳不同首页 req.getSession().setAttribute(loginUser, user); if (user.getRole() 1) { resp.sendRedirect(req.getContextPath() /admin/index.jsp); } else { resp.sendRedirect(req.getContextPath() /index.jsp); } } }逻辑说明验证码校验放在最前面防止暴力破解密码用 MD5 哈希后比对数据库里存的是哈希值不是明文登录成功后把User对象放进 session后续页面通过session.getAttribute(loginUser)判断是否登录。参数上req.setCharacterEncoding(UTF-8)必须在取参数之前调用否则中文用户名会乱码。权限拦截不能只靠页面隐藏菜单。常见做法是写一个LoginFilter拦截/admin/*路径检查 session 里有没有管理员身份没有就踢回登录页。// LoginFilter.java WebFilter(/admin/*) public class LoginFilter 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(loginUser); // 未登录或不是管理员一律拦截 if (user null || user.getRole() ! 1) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }这个过滤器是权限控制的关键很多毕设系统只在前端用if判断菜单显示后端接口裸奔直接改 URL 就能进管理页这是典型的安全漏洞。3.2 购物车与订单事务处理购物车和下单是电商系统里最容易出 bug 的地方核心难点在库存扣减和事务一致性。加购逻辑相对简单用唯一索引配合ON DUPLICATE KEY UPDATE就能搞定。// CartDao.java 添加购物车 public void addToCart(int userId, int proId, int quantity) { String sql INSERT INTO t_cart(user_id, pro_id, quantity) VALUES(?,?,?) ON DUPLICATE KEY UPDATE quantity quantity ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, userId); ps.setInt(2, proId); ps.setInt(3, quantity); ps.setInt(4, quantity); ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } }下单才是重头戏。用户点“提交订单”时要做三件事生成订单主记录、写入订单明细、扣减商品库存。这三步必须在一个事务里任何一步失败都要回滚否则会出现“订单生成了但库存没扣”或者“库存扣了但订单没生成”的脏数据。// OrderService.java 下单核心逻辑 public String createOrder(int userId, ListCartItem items, String receiver, String phone, String addr) throws SQLException { Connection conn null; PreparedStatement psOrder null; PreparedStatement psItem null; PreparedStatement psStock null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 计算总金额并生成订单号 BigDecimal total BigDecimal.ZERO; for (CartItem item : items) { total total.add(item.getPrice().multiply(new BigDecimal(item.getQuantity()))); } String orderNo System.currentTimeMillis() (int)(Math.random()*90001000); // 2. 插入订单主表 String sqlOrder INSERT INTO t_order(order_no,user_id,total_amount,receiver,receiver_phone,receiver_addr) VALUES(?,?,?,?,?,?); psOrder conn.prepareStatement(sqlOrder, Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, orderNo); psOrder.setInt(2, userId); psOrder.setBigDecimal(3, total); psOrder.setString(4, receiver); psOrder.setString(5, phone); psOrder.setString(6, addr); psOrder.executeUpdate(); ResultSet rs psOrder.getGeneratedKeys(); int orderId 0; if (rs.next()) orderId rs.getInt(1); // 3. 循环插入明细并扣库存库存不足直接抛异常回滚 String sqlItem INSERT INTO t_order_item(order_id,pro_id,pro_name,price,quantity) VALUES(?,?,?,?,?); String sqlStock UPDATE t_product SET stock stock - ? WHERE pro_id ? AND stock ?; psItem conn.prepareStatement(sqlItem); psStock conn.prepareStatement(sqlStock); for (CartItem item : items) { psItem.setInt(1, orderId); psItem.setInt(2, item.getProId()); psItem.setString(3, item.getProName()); psItem.setBigDecimal(4, item.getPrice()); psItem.setInt(5, item.getQuantity()); psItem.addBatch(); psStock.setInt(1, item.getQuantity()); psStock.setInt(2, item.getProId()); psStock.setInt(3, item.getQuantity()); if (psStock.executeUpdate() 0) { throw new SQLException(商品[ item.getProName() ]库存不足); } } psItem.executeBatch(); conn.commit(); // 全部成功才提交 return orderNo; } catch (SQLException e) { if (conn ! null) conn.rollback(); // 任何异常回滚 throw e; } finally { if (conn ! null) conn.close(); } }这段代码的关键点conn.setAutoCommit(false)关闭自动提交开启事务扣库存的 SQL 带了AND stock ?条件利用数据库行锁保证并发下不会超卖如果返回影响行数为 0 说明库存不够直接抛异常触发回滚Statement.RETURN_GENERATED_KEYS用来拿刚插入订单的自增主键供明细表关联。参数上订单号用时间戳加随机数保证唯一的同时不暴露业务量。3.3 商品管理与分页查询管理端的商品管理是增删改查的典型场景用户端的商品列表则必须做分页否则商品一多页面直接卡死。分页的核心是LIMIT offset, size配合一条COUNT(*)查总数。// ProductDao.java 分页查询 public PageBeanProduct findByPage(int pageNum, int pageSize, Integer cateId) { PageBeanProduct page new PageBean(); StringBuilder where new StringBuilder( WHERE status 1 ); ListObject params new ArrayList(); if (cateId ! null) { where.append( AND cate_id ? ); params.add(cateId); } // 先查总数 String countSql SELECT COUNT(*) FROM t_product where; // 再查当前页数据按上架时间倒序 String dataSql SELECT * FROM t_product where ORDER BY pro_id DESC LIMIT ?,?; try (Connection conn DBUtil.getConnection()) { PreparedStatement psCount conn.prepareStatement(countSql); for (int i 0; i params.size(); i) psCount.setObject(i1, params.get(i)); ResultSet rs psCount.executeQuery(); int total rs.next() ? rs.getInt(1) : 0; PreparedStatement psData conn.prepareStatement(dataSql); int idx 1; for (Object p : params) psData.setObject(idx, p); psData.setInt(idx, (pageNum - 1) * pageSize); // offset psData.setInt(idx, pageSize); ResultSet rsData psData.executeQuery(); ListProduct list new ArrayList(); while (rsData.next()) { Product p new Product(); p.setProId(rsData.getInt(pro_id)); p.setProName(rsData.getString(pro_name)); p.setPrice(rsData.getBigDecimal(price)); p.setStock(rsData.getInt(stock)); p.setProImg(rsData.getString(pro_img)); list.add(p); } page.setList(list); page.setTotalCount(total); page.setPageNum(pageNum); page.setPageSize(pageSize); } catch (SQLException e) { e.printStackTrace(); } return page; }参数说明offset (pageNum - 1) * pageSize这是分页公式pageNum 从 1 开始。ORDER BY pro_id DESC让新上架的商品排在前面。where条件用 StringBuilder 动态拼接配合参数列表防止 SQL 注入比直接字符串拼接安全得多。商品图片上传用commons-fileupload组件处理存到项目外的独立目录数据库只存相对路径这样重新部署 war 包时图片不会丢。4. 避坑与排查这套 JSP 商城最容易翻车的地方4.1 中文乱码三处源头现象商品名、用户名、留言内容在页面上显示成问号或乱码。原因通常有三个源头JSP 页面编码、请求编码、数据库编码。解决要三处一起改。JSP 页面顶部加% page contentTypetext/html;charsetUTF-8 languagejava %Servlet 里取参数前调req.setCharacterEncoding(UTF-8)数据库连接串加characterEncodingutf8建库建表用utf8mb4。如果是 GET 请求传中文还要改 Tomcat 的server.xml在 Connector 标签加URIEncodingUTF-8。三处缺一处都可能乱码这是新手最常卡的地方。4.2 数据库连接报错与驱动版本现象启动后访问页面报ClassNotFoundException: com.mysql.jdbc.Driver或No suitable driver found。原因是驱动 jar 没放进WEB-INF/lib目录或者 MySQL 版本和驱动类名不匹配。MySQL 5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.Driver。另外 MySQL 8.0 必须加serverTimezoneAsia/Shanghai否则报时区错误。驱动 jar 一定要放WEB-INF/lib下放项目根目录或者 Tomcat 的 lib 下都可能因为类加载顺序出问题。4.3 库存超卖与事务失效现象并发下单时库存扣成负数或者订单生成了但库存没减。原因是没开事务或者扣库存的 SQL 没加库存判断条件。解决下单逻辑必须用conn.setAutoCommit(false)开启事务扣库存 SQL 写成UPDATE t_product SET stock stock - ? WHERE pro_id ? AND stock ?靠数据库行锁和条件判断双重保险。还要注意如果 DAO 里每次操作都从连接池拿新连接事务会失效必须保证下单过程中的所有操作共用同一个 Connection 对象。4.4 图片上传路径与部署丢失现象本地测试图片能显示重新部署 war 包后图片全没了。原因是图片存到了项目编译输出目录里重新部署被覆盖。解决图片存到项目外的独立目录比如D:/agri_upload/数据库存相对路径页面上用img src${pageContext.request.contextPath}/upload/${product.proImg}访问再配一个 Servlet 或 Tomcat 虚拟路径映射到真实目录。这样重新部署不影响已上传的图片。4.5 session 失效与登录状态丢失现象用户登录后操作几步就跳回登录页。原因是 session 默认 30 分钟超时或者 Tomcat 重启导致 session 丢失。解决在web.xml里配置session-configsession-timeout60/session-timeout/session-config延长超时时间。如果是 Tomcat 重启丢 session开发阶段可以接受生产环境要考虑 session 持久化或改用 token 方案。另外注意浏览器禁用 cookie 时 session 也会失效需要 URL 重写兜底。5. 二次开发与验证让这套系统真正能用起来拿到这套源码后别急着改业务先做一轮完整验证。我的习惯是建库、导数据、配连接、启动 Tomcat、走一遍“注册→登录→浏览商品→加购→下单→后台发货→确认收货→申请退货→后台审核”的全流程。这条链路能跑通说明系统骨架是完整的。跑不通的地方就是你要重点排查的点。验证通过后二次开发可以从三个方向入手。第一是安全加固把密码哈希从 MD5 升级到 BCrypt加登录失败次数限制给所有表单加 CSRF token。第二是功能扩展比如接入支付宝沙箱做真实支付回调或者加一个农产品溯源信息字段扫码能看到产地和检测报告这个在农产品场景里很实用。第三是性能优化商品列表加 Redis 缓存热门商品不每次都查库分页查询在cate_id和status上建联合索引。这里给一个 BCrypt 替换 MD5 的示例依赖jbcrypt库// 注册时加密 String hashed BCrypt.hashpw(password, BCrypt.gensalt(12)); // 登录时校验 boolean match BCrypt.checkpw(inputPassword, storedHash);gensalt(12)里的 12 是计算强度值越大越安全但越慢一般 10 到 12 够用。BCrypt 每次生成的盐不同所以同一个密码每次哈希结果都不一样比 MD5 安全得多。还有一个容易被忽略的点这套系统的订单状态流转是硬编码的数字0 到 4 分别代表不同状态。二次开发时建议抽成枚举类避免代码里到处是魔法数字改起来容易漏。public enum OrderStatus { UNPAID(0, 待付款), PAID(1, 已付款), SHIPPED(2, 已发货), FINISHED(3, 已完成), CANCELED(4, 已取消); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } }从那以后我每次接手这类 JSP 老项目都强制先跑一遍全链路再动一行代码。因为这类系统的坑往往不在业务逻辑本身而在环境、编码、事务这些基础设施上先把地基验一遍后面改起来才踏实。希望帮到你。本文还有配套的精品资源点击获取
返回列表