ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java Web基础实战:Servlet+JSP校园二手交易系统

Java Web基础实战:Servlet+JSP校园二手交易系统 简介本资源是一套面向计算机专业本科生的毕业设计级校园二手交易平台完整实现采用JSPServletMySQL经典Java Web技术栈覆盖用户管理、商品发布与浏览、交易流程、消息通知及基础安全防护等核心模块适用于课程设计、毕设参考与Web开发入门实践。压缩包共5个文件含数据库脚本market.sql用于初始化表结构与示例数据README.md提供项目说明与运行指引.doc格式的本科毕业设计说明书详述需求分析、系统设计与实现过程LICENSE明确授权信息主源码以zip形式封装整体大小7.95MB结构精炼、开箱即用。已有883人学习下载资源提供可直接部署运行的全量源码、规范化的文档支撑及典型MVC分层实现范例有助于深入理解前后端协作逻辑、Servlet请求处理机制与MySQL数据持久化实践。1. 这不是“过时技术堆砌”而是一套可跑通、可调试、可扩展的校园二手交易最小可行系统你打开market.zip解压后看到的不是一堆静态 HTML 和空数据库脚本而是一个能立刻部署到 Tomcat 8.5、连上 MySQL 5.7 就能注册登录、发布商品、完成模拟交易的完整 Web 应用。它没有 Spring Boot 的自动配置魔法但每一行 JSP 的% %脚本、每一个HttpServlet子类、每一条PreparedStatement参数绑定都直白地暴露了 HTTP 请求如何被解析、用户会话如何维持、SQL 查询如何防注入、事务边界如何划定——这正是 Java Web 基础链路最真实的毛细血管。它适合两类人一类是正在写毕业设计、需要在两周内跑通全流程并能讲清每个模块调用关系的本科生另一类是已工作 35 年、想回溯 Servlet 容器生命周期、理解web.xml中filter与servlet-mapping如何协同拦截请求的开发者。项目不追求高并发或微服务架构但把“用户登录态校验”“商品分页查询”“图片路径安全存储”“订单状态机流转”四个关键闭环做实了且所有 SQL 语句均使用预编译参数化market.sql中明确建有user,product,order_info,message四张表及外键约束不是示意性结构。2. 从web.xml入口开始Servlet 生命周期与请求路由的显式控制2.1web.xml是整个系统的中枢神经而非历史遗迹该平台未采用注解驱动如WebServlet所有 Servlet 映射、Filter 链、监听器注册均通过WEB-INF/web.xml显式声明。这是理解请求如何进入系统的起点!-- web.xml 片段 -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.example.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping filter filter-nameAuthFilter/filter-name filter-classcom.example.filter.AuthFilter/filter-class /filter filter-mapping filter-nameAuthFilter/filter-name url-pattern/user/*/url-pattern /filter-mapping提示url-pattern/user/*/url-pattern表示所有以/user/开头的请求如/user/profile.jsp,/user/orderlist都会先经过AuthFilter。该 Filter 在doFilter()方法中检查HttpSession.getAttribute(user)是否为非 null若为空则重定向至/login.jsp。这种显式声明比 Spring Security 的PreAuthorize更底层也更易调试——你可以在AuthFilter.java第 32 行加断点观察每次请求进来时 session 的实际内容。2.2LoginServlet的三步验证逻辑从表单解析到会话建立LoginServlet继承HttpServlet其doPost()方法执行标准三步获取参数 → 查询数据库 → 设置会话。关键代码如下// LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username).trim(); // 1. 获取表单字段 String password request.getParameter(password); // 2. 使用 PreparedStatement 防 SQL 注入非拼接字符串 String sql SELECT id, username, realname, role FROM user WHERE username ? AND password ?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, username); // 参数索引从 1 开始 pstmt.setString(2, MD5Util.md5(password)); // 密码经 MD5 加密存储 ResultSet rs pstmt.executeQuery(); if (rs.next()) { // 3. 创建用户会话对象存入必要字段非全量用户数据 User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setRealname(rs.getString(realname)); user.setRole(rs.getString(role)); // student or admin request.getSession().setAttribute(user, user); // 会话绑定 response.sendRedirect(user/home.jsp); // 重定向至主页 } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } }参数说明request.getParameter(username)获取的是 HTML 表单中nameusername的输入值MD5Util.md5()是项目自定义工具类对明文密码做不可逆哈希避免数据库泄露导致密码直接暴露关键细节request.getSession().setAttribute(user, user)将User对象存入 session后续所有受保护页面如/user/profile.jsp均可通过session.getAttribute(user)获取当前登录者信息无需重复查库失败排查若登录后跳转至空白页首先检查market.sql中user表是否已插入测试数据如INSERT INTO user VALUES (1,test,张三,student,md5(123456));再确认LoginServlet中conn数据库连接是否已由DBUtil正确初始化。2.3ProductListServlet实现带条件的分页查询JSP 与 Servlet 的职责分离商品列表页/product/list.jsp不直接访问数据库而是由ProductListServlet承担数据组装职责。该 Servlet 接收pageNo1pageSize10keyword手机等参数执行带LIMIT和WHERE的查询并将结果集与分页元数据一并传给 JSP// ProductListServlet.java String keyword request.getParameter(keyword); int pageNo Integer.parseInt(request.getParameter(pageNo)); int pageSize 10; // 构建动态 SQL注意此处 keyword 仍需预编译避免拼接 String sql SELECT p.id, p.title, p.price, p.status, u.realname FROM product p JOIN user u ON p.seller_id u.id WHERE p.status on_sale; if (keyword ! null !keyword.trim().isEmpty()) { sql AND p.title LIKE ?; } sql ORDER BY p.create_time DESC LIMIT ?, ?; PreparedStatement pstmt conn.prepareStatement(sql); int paramIndex 1; if (keyword ! null !keyword.trim().isEmpty()) { pstmt.setString(paramIndex, % keyword.trim() %); } pstmt.setInt(paramIndex, (pageNo - 1) * pageSize); // LIMIT offset, size pstmt.setInt(paramIndex, pageSize); ResultSet rs pstmt.executeQuery(); ListProduct productList new ArrayList(); while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setTitle(rs.getString(title)); p.setPrice(rs.getBigDecimal(price)); p.setStatus(rs.getString(status)); p.setSellerName(rs.getString(realname)); productList.add(p); } // 计算总记录数用于分页导航 String countSql SELECT COUNT(*) FROM product p JOIN user u ON p.seller_id u.id WHERE p.status on_sale; if (keyword ! null !keyword.trim().isEmpty()) { countSql AND p.title LIKE ?; } PreparedStatement countPstmt conn.prepareStatement(countSql); if (keyword ! null !keyword.trim().isEmpty()) { countPstmt.setString(1, % keyword.trim() %); } ResultSet countRs countPstmt.executeQuery(); countRs.next(); int totalCount countRs.getInt(1); // 封装分页对象并转发 PageBeanProduct pageBean new PageBean(pageNo, pageSize, totalCount, productList); request.setAttribute(pageBean, pageBean); request.getRequestDispatcher(/product/list.jsp).forward(request, response);逻辑说明PageBean是项目自定义的分页实体类包含pageNo,pageSize,totalPages,list等字段list.jsp通过 JSTLc:forEach items${pageBean.list} varp遍历展示商品参数说明LIMIT ?, ?中第一个?是偏移量(pageNo-1)*pageSize第二个?是每页条数pageSizeLIKE ?的参数值为% keyword %实现模糊搜索性能注意COUNT(*)查询未加WHERE条件时会全表扫描生产环境应为title字段添加索引ALTER TABLE product ADD INDEX idx_title (title);3. JSP 页面层从login.jsp到product/detail.jsp的渐进式交互实现3.1login.jsp表单提交与错误反馈的同步处理模式该页面采用传统同步提交非 AJAX核心在于form的action指向/login且通过request.getAttribute(error)渲染错误提示!-- login.jsp -- form action/login methodpost div classform-group label用户名/label input typetext nameusername required /div div classform-group label密码/label input typepassword namepassword required /div button typesubmit登录/button /form !-- 错误提示区域 -- c:if test${not empty error} div classalert alert-danger${error}/div /c:if关键机制当LoginServlet验证失败时调用request.setAttribute(error, xxx)并forward回login.jsp此时request对象仍存活JSP 中${error}可直接取值若使用sendRedirect()则request属性丢失错误信息需改用session.setAttribute()并在重定向后立即清除安全实践input typepassword防止密码明文显示required属性提供前端基础校验但后端LoginServlet仍需trim()和非空判断因前端校验可被绕过。3.2product/detail.jsp商品详情与购买按钮的状态联动详情页不仅展示数据还根据当前用户身份和商品状态动态渲染操作按钮。核心逻辑在 JSP 脚本片段中!-- product/detail.jsp -- c:choose c:when test${empty sessionScope.user} a href/login.jsp classbtn btn-primary请先登录/a /c:when c:when test${sessionScope.user.id product.sellerId} span classbadge bg-warning您是卖家/span a href/product/edit?id${product.id} classbtn btn-outline-secondary编辑商品/a /c:when c:otherwise c:choose c:when test${product.status on_sale} form action/order/create methodpost input typehidden nameproductId value${product.id} button typesubmit classbtn btn-success立即购买/button /form /c:when c:otherwise span classbadge bg-secondary已下架/span /c:otherwise /c:choose /c:otherwise /c:choose状态驱动c:choose根据sessionScope.user登录用户、product.sellerId商品所属用户 ID、product.status商品状态三者关系决定显示“登录入口”“编辑按钮”或“购买表单”参数传递购买表单通过hidden输入域传递productIdOrderCreateServlet接收后校验库存、生成订单号、更新商品状态为sold_out全程在一次 HTTP POST 中完成避免分布式事务复杂度用户体验span classbadge提供即时状态反馈比纯文字更直观Bootstrap 类名btn-success,bg-warning表明项目已集成前端框架降低样式开发成本。3.3user/profile.jsp个人信息展示与安全更新的分离设计用户个人资料页采用“读写分离”策略展示区直接读取sessionScope.user编辑区则提交至独立 Servlet!-- user/profile.jsp -- div classcard div classcard-header个人信息/div div classcard-body pstrong用户名/strong${sessionScope.user.username}/p pstrong真实姓名/strong${sessionScope.user.realname}/p pstrong角色/strongspan classbadge bg-info${sessionScope.user.role}/span/p a href/user/edit.jsp classbtn btn-link修改资料/a /div /div安全考量profile.jsp仅展示sessionScope.user中的字段不暴露password或id等敏感属性edit.jsp是独立页面其表单提交至/user/update由UpdateUserServlet处理该 Servlet 明确限定只允许更新realname和phone字段且对phone格式进行正则校验^1[3-9]\\d{9}$防重放update.jsp表单中嵌入隐藏域input typehidden nametimestamp value%System.currentTimeMillis()%UpdateUserServlet验证时间戳是否在 5 分钟内防止链接被恶意复用。4. MySQL 数据库设计四张核心表的约束与关联逻辑4.1user表身份标识与权限基点market.sql中user表定义如下其主键、唯一约束及默认值设计直指业务需求CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(32) NOT NULL COMMENT MD5加密密码, realname VARCHAR(100) NOT NULL COMMENT 真实姓名, phone VARCHAR(11) DEFAULT NULL COMMENT 手机号, role ENUM(student,admin) NOT NULL DEFAULT student COMMENT 角色, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT(1) DEFAULT 1 COMMENT 1-启用,0-禁用 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键约束username设为UNIQUE确保账号唯一性role使用ENUM类型将角色值固化为student或admin避免字符串误写status默认1启用新注册用户无需额外激活索引建议为高频查询字段添加索引如CREATE INDEX idx_username_status ON user(username, status);加速登录验证WHERE username? AND status1。4.2product表商品状态机与外键完整性商品表通过status字段实现简单状态机并强制关联user表CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL COMMENT 商品标题, description TEXT COMMENT 商品描述, price DECIMAL(10,2) NOT NULL COMMENT 价格, seller_id INT NOT NULL COMMENT 卖家ID, status ENUM(on_sale,sold_out,deleted) NOT NULL DEFAULT on_sale COMMENT 状态, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (seller_id) REFERENCES user(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;外键行为ON DELETE CASCADE表示若某用户被删除其发布的所有商品自动级联删除避免孤儿数据ON UPDATE CASCADE未启用因user.id为主键不应被更新状态流转status允许值为on_sale在售、sold_out已售罄、deleted软删除。OrderCreateServlet在生成订单后执行UPDATE product SET statussold_out WHERE id?而非物理删除便于后台审计。4.3order_info表交易原子性与买家/卖家双关联订单表同时关联买家和卖家体现交易双边性CREATE TABLE order_info ( id VARCHAR(32) PRIMARY KEY COMMENT 订单号格式ORD20240520123456, buyer_id INT NOT NULL COMMENT 买家ID, seller_id INT NOT NULL COMMENT 卖家ID, product_id INT NOT NULL COMMENT 商品ID, amount DECIMAL(10,2) NOT NULL COMMENT 成交金额, status ENUM(pending,paid,shipped,completed,cancelled) NOT NULL DEFAULT pending COMMENT 订单状态, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (buyer_id) REFERENCES user(id), FOREIGN KEY (seller_id) REFERENCES user(id), FOREIGN KEY (product_id) REFERENCES product(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单号生成id为字符串类型由OrderCreateServlet生成格式为ORDyyyyMMdd6位随机数如ORD20240520123456保证全局唯一且可读双重外键buyer_id和seller_id均引用user.id确保订单中买卖双方均为有效用户product_id引用product.id保证订单对应真实商品状态扩展性status预留shipped已发货、completed已完成等状态为后续物流对接留出接口。5. 部署与调试实战从本地 Tomcat 到数据库连接的完整链路验证5.1 Tomcat 8.5 部署步骤与常见 404 错误定位将market文件夹整体复制到 Tomcat 的webapps/目录下启动 Tomcat 后访问http://localhost:8080/market/login.jsp。若遇 404按以下顺序排查检查项目结构确认market/WEB-INF/web.xml存在且格式正确无 XML 语法错误market/WEB-INF/classes/下应有编译后的.class文件如com/example/servlet/LoginServlet.class验证 Servlet 映射web.xml中servlet-mapping的url-pattern必须与浏览器请求路径匹配。例如访问/market/login时url-pattern/login/url-pattern是正确的而url-pattern/servlet/login/url-pattern会导致 404日志分析查看tomcat/logs/catalina.out搜索SEVERE关键字。常见错误如ClassNotFoundException: com.mysql.cj.jdbc.Driver表明 MySQL JDBC 驱动未放入market/WEB-INF/lib/目录。注意Tomcat 9 默认使用 Jakarta EE 命名空间若项目中web.xml的xmlns仍为http://java.sun.com/xml/ns/javaee需升级为https://jakarta.ee/xml/ns/jakartaee否则容器可能忽略web.xml配置。5.2 MySQL 连接配置与DBUtil工具类解析数据库连接由com.example.util.DBUtil类统一管理其核心方法如下public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/market?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() throws SQLException { try { Class.forName(com.mysql.cj.jdbc.Driver); // 加载驱动 } catch (ClassNotFoundException e) { throw new SQLException(MySQL Driver not found, e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }URL 参数说明useSSLfalse禁用 SSL本地开发环境通常无需serverTimezoneAsia/Shanghai解决时区不一致导致的时间字段错乱allowPublicKeyRetrievaltrue适配 MySQL 8.0 的新认证插件密码安全生产环境应将USER和PASSWORD移至外部配置文件如db.properties并通过ResourceBundle.getBundle(db)加载避免硬编码。5.3 一个关键调试技巧在AuthFilter中打印请求链路当遇到“登录后无法访问/user/home.jsp”问题时在AuthFilter.java的doFilter()方法开头添加日志public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; System.out.println( AuthFilter: req.getRequestURL() ? req.getQueryString()); System.out.println( Session ID: req.getSession().getId()); System.out.println( User in session: req.getSession().getAttribute(user)); if (req.getSession().getAttribute(user) null) { System.out.println( Redirecting to /login.jsp); req.getRequestDispatcher(/login.jsp).forward(request, response); return; } chain.doFilter(request, response); }作用在控制台输出每次请求的完整 URL、Session ID 及其中的user对象可快速判断是登录未成功user为 null、Session ID 不一致跨请求 Session 丢失还是 Filter 链未正确配置验证方法启动 Tomcat 后依次访问/login.jsp→ 提交表单 → 观察控制台输出确认user对象是否在登录后被正确写入 Session。本文还有配套的精品资源点击获取
返回列表