ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java+JSP校园二手交易平台实战:从数据库建模到Tomcat部署

2026/9/14 2:13:55 拓冰建站 浏览量
Java+JSP校园二手交易平台实战:从数据库建模到Tomcat部署 简介这是一份面向计算机专业本科生的毕业设计与期末大作业实战资源聚焦校园二手物品交易场景基于Java Web技术栈实现完整B/S架构平台。资源包含可直接运行的JSPServlet源码、MySQL数据库脚本、详细开发文档与数据文档覆盖用户管理、商品发布、订单处理、在线支付及评价反馈等核心功能模块适合作为课程设计参考或项目能力提升训练材料。压缩包共1305个文件含128个Java业务逻辑类、119个JSP页面、364个JS交互脚本、146个CSS样式文件及172个PNG图标资源辅以SQL建表语句与HTML前端模板整体22.74MB结构清晰、模块解耦符合MVC分层规范。已有39人学习下载源码经本地编译调试通过附带.bak备份文件与Eclipse项目配置.classpath、.project等便于快速导入复现文档详述设计思路与数据库ER关系助力理解系统架构与工程实践要点。1. 用 Java JSP 搭建校园二手物品交易平台不是写个登录页就叫 Web 项目它得能扛住期末前 3000 人同时挂闲置、抢教材、改价格校园二手交易场景有鲜明的“潮汐特征”每学期初教材流转高峰、期末前数码设备集中出手、毕业季家具家电批量清仓。这类系统不需要支撑百万级并发但必须在 200–500 人并发发布/浏览/议价时保持响应稳定且数据强关联——一本书绑定卖家学号、院系、发布时间、是否已售一条求购信息需匹配多本教材 ISBN。纯 Servlet 手写 HTML 太重全 Spring Boot 又过度设计JSP 在这个量级下仍是合理选择它把 Java 业务逻辑和页面渲染耦合得足够紧便于学生团队快速迭代Tomcat 启动即跑调试时.jsp文件修改保存后自动重编译配合web.xml中init-param配置developmenttrue比前后端分离调试接口Mock 数据快得多。本项目不是教学 Demo而是真实部署在校内服务器、由学生会运维的轻量级平台核心能力包括基于学号统一认证的发布审核流、带关键词与分类的模糊搜索、议价消息异步通知用HttpSession绑定未读数、交易状态机待发布→已上架→已议价→已成交→已评价。它不追求炫技但每个 JSP 页面都对应一个*.java的 DAO 类每个request.getParameter()都做过String.trim().isEmpty()校验每张表都有create_time和update_time字段——这是能在线上跑满一整个学期的基础。2. 用 JSP Servlet MySQL 实现最小可行闭环从 index.jsp 到 OrderServlet.java 的 7 步链路2.1 数据库建模紧扣校园场景的 5 张表设计逻辑与字段取舍校园二手平台的数据模型必须反映真实约束。例如“用户”表不能只存账号密码而要包含student_id唯一且不可为空用于对接教务系统、college枚举值计算机学院/外国语学院/…避免自由输入导致统计失真、phone_verifiedtinyint(1)区分短信验证状态。商品表item的关键字段设计如下字段名类型是否为空说明idBIGINT UNSIGNED AUTO_INCREMENTNOT NULL主键用BIGINT预留未来扩展空间seller_idBIGINT UNSIGNEDNOT NULL外键关联user.id禁止空卖titleVARCHAR(100)NOT NULL商品标题长度限制防 XSS 注入categoryTINYINT UNSIGNEDNOT NULL分类编码1教材,2数码,3生活用品,4图书…比字符串查询快priceDECIMAL(10,2)NOT NULL DEFAULT 0.00精确到分DECIMAL避免浮点误差statusTINYINT UNSIGNEDNOT NULL DEFAULT 11待审核,2已上架,3已售出,4已下架状态机驱动前端按钮显隐提示status字段不设外键引用字典表因为状态值极少变动且需高频查询冗余存储可减少 JOIN 开销。实际部署时item表加复合索引INDEX idx_seller_status (seller_id, status)支撑个人中心“我的商品”按状态筛选。建表 SQL 示例含注释-- 用户表 CREATE TABLE user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, student_id CHAR(10) NOT NULL UNIQUE COMMENT 学号10位数字, username VARCHAR(20) NOT NULL COMMENT 昵称, password CHAR(64) NOT NULL COMMENT SHA-256 加密后的密码, college ENUM(计算机学院,外国语学院,理学院,文学院) NOT NULL, phone VARCHAR(11) DEFAULT NULL, phone_verified TINYINT(1) DEFAULT 0 COMMENT 0未验证,1已验证, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品表精简核心字段 CREATE TABLE item ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, seller_id BIGINT UNSIGNED NOT NULL, title VARCHAR(100) NOT NULL, category TINYINT UNSIGNED NOT NULL DEFAULT 1, description TEXT COMMENT 商品描述支持换行, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, status TINYINT UNSIGNED NOT NULL DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_seller_status (seller_id,status), CONSTRAINT fk_item_seller FOREIGN KEY (seller_id) REFERENCES user (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.2 JSP 页面结构index.jsp 如何组织动态内容与静态资源路径index.jsp是流量入口需兼顾 SEO 友好性与动态数据加载。其结构遵循“静态骨架 动态填充”原则HTML 骨架含head中的styles.css引用写死商品列表区域用 JSTLc:forEach渲染避免 scriptlet 脚本块污染可维护性。关键路径处理如下CSS 文件放在WebContent/css/styles.cssJSP 中引用为link relstylesheet href${pageContext.request.contextPath}/css/styles.csscontextPath确保项目部署在子路径如/secondhand时路径正确图片资源统一放WebContent/images/商品图用img src${pageContext.request.contextPath}/images/${item.coverImage} alt${item.title}分页链接生成a hrefindex.jsp?page${page-1}上一页/aServlet 接收page参数后计算offset (page-1)*pageSize。index.jsp核心片段含 JSTL 导入与商品循环% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head meta charsetUTF-8 title校园二手集市 - 首页/title link relstylesheet href${pageContext.request.contextPath}/css/styles.css /head body div classcontainer header h1校园二手集市/h1 nav a hreflogin.jsp登录/a | a hrefregister.jsp注册/a /nav /header !-- 商品列表 -- main h2最新上架/h2 div classitem-grid c:forEach items${items} varitem div classitem-card h3${item.title}/h3 p classprice¥${item.price}/p p classcategory${fn:escapeXml(item.categoryName)}/p a hrefitemDetail.jsp?id${item.id} classbtn查看详情/a /div /c:forEach /div !-- 分页 -- div classpagination c:if test${currentPage 1} a hrefindex.jsp?page${currentPage-1}laquo; 上一页/a /c:if span第 ${currentPage} 页/span c:if test${currentPage totalPages} a hrefindex.jsp?page${currentPage1}下一页 raquo;/a /c:if /div /main /div /body /html注意fn:escapeXml()是 JSTL 函数防止item.title中的script标签被浏览器执行这是 JSP 安全开发的底线操作。若未引入fn前缀需在页面顶部添加% taglib prefixfn urihttp://java.sun.com/jsp/jstl/functions %。2.3 Servlet 控制层OrderServlet.java 如何处理“立即购买”并保证事务一致性当用户点击“立即购买”按钮请求发往OrderServlet该 Servlet 必须完成原子性操作扣减库存此处为状态变更、生成订单、更新卖家通知。若用多个独立UPDATE语句网络抖动可能导致部分成功部分失败。正确做法是使用 JDBC 事务并在try-catch-finally中显式控制连接生命周期。OrderServlet.java关键逻辑含事务回滚与异常处理WebServlet(/order) public class OrderServlet extends HttpServlet { private static final long serialVersionUID 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); // 获取连接池中的连接 conn.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 检查商品状态是否为“已上架” String checkSql SELECT status FROM item WHERE id ? AND status 2; ps conn.prepareStatement(checkSql); ps.setLong(1, Long.parseLong(request.getParameter(itemId))); ResultSet rs ps.executeQuery(); if (!rs.next()) { request.setAttribute(error, 商品已下架或不存在); request.getRequestDispatcher(error.jsp).forward(request, response); return; } // 2. 更新商品状态为“已售出” String updateItemSql UPDATE item SET status 3, update_time NOW() WHERE id ?; ps conn.prepareStatement(updateItemSql); ps.setLong(1, Long.parseLong(request.getParameter(itemId))); int rowsAffected ps.executeUpdate(); if (rowsAffected 0) { throw new SQLException(更新商品状态失败); } // 3. 插入订单记录简化版实际含 buyer_id, seller_id 等 String insertOrderSql INSERT INTO order (item_id, buyer_id, create_time) VALUES (?, ?, NOW()); ps conn.prepareStatement(insertOrderSql, Statement.RETURN_GENERATED_KEYS); ps.setLong(1, Long.parseLong(request.getParameter(itemId))); ps.setLong(2, (Long) request.getSession().getAttribute(userId)); ps.executeUpdate(); // 4. 提交事务 conn.commit(); response.sendRedirect(success.jsp?msg下单成功); } catch (SQLException e) { // 事务回滚 if (conn ! null) { try { conn.rollback(); } catch (SQLException rollbackEx) { rollbackEx.printStackTrace(); } } request.setAttribute(error, 下单失败 e.getMessage()); request.getRequestDispatcher(error.jsp).forward(request, response); } finally { // 关闭资源 DBUtil.close(ps, conn); } } }提示DBUtil是封装了连接池如 HikariCP获取与释放的工具类close()方法确保PreparedStatement和Connection在 finally 块中释放避免连接泄漏。Statement.RETURN_GENERATED_KEYS参数用于后续获取自增主键此处虽未使用但为扩展订单号生成预留接口。3. JSP 编译机制与 Tomcat 配置定位 jsp 编译后的 java 类文件位置及调试技巧3.1 JSP 编译原理Tomcat 如何将 index.jsp 转成 Java 类并加载JSP 并非直接执行而是由 Tomcat 的 Jasper 编译器在首次访问时将其翻译为 Servlet Java 源码再编译成.class文件。这个过程发生在work/Catalina/localhost/[app-name]/org/apache/jsp/目录下。例如index.jsp编译后生成index_jsp.java和index_jsp.class。路径结构为$TOMCAT_HOME/work/Catalina/localhost/secondhand/ ├── org │ └── apache │ └── jsp │ ├── index_jsp.java ← 编译前的 Java 源码含 _jspService 方法 │ └── index_jsp.class ← JVM 加载的字节码 └── ...提示secondhand是应用上下文路径Context Path由server.xml中Context path/secondhand .../或 WAR 包名决定。若项目名为secondhand.war则工作目录即为secondhand。3.2 修改 JSP 后实时生效的配置项与常见失效原因默认情况下JSP 修改后 Tomcat 会自动重新编译但需满足以下条件web.xml中servlet配置jspservlet 的init-param含developmenttrueTomcat 9 默认开启conf/web.xml中JspServlet的developmentinit-param 为truework目录未被手动删除删除后首次访问会重建但旧编译缓存丢失。若修改 JSP 后页面无变化按顺序排查检查work目录时间戳进入work/Catalina/localhost/secondhand/org/apache/jsp/查看index_jsp.java的修改时间是否晚于 JSP 文件确认web.xml中 JSP Servlet 配置servlet servlet-namejsp/servlet-name servlet-classorg.apache.jasper.servlet.JspServlet/servlet-class init-param param-namedevelopment/param-name param-valuetrue/param-value !-- 必须为 true -- /init-param load-on-startup3/load-on-startup /servlet禁用浏览器缓存开发时在index.jsphead中添加meta http-equivCache-Control contentno-cache, no-store, must-revalidate。3.3 查看 JSP 编译后的 Java 源码定位逻辑错误的实战方法当 JSP 页面出现NullPointerException或NumberFormatException堆栈信息常指向index_jsp.java的某行。此时需打开该文件定位问题。例如index_jsp.java中一段典型代码// Generated by Jasper (version: Apache Tomcat/9.0.83) // Line 123 in index_jsp.java out.print( JspRuntimeLibrary.toString((((com.example.Item)pageContext.findAttribute(item)).getPrice()));若item为 null此处抛出 NPE。解决方案是在 JSP 中增加判空c:if test${not empty item} p classprice¥${item.price}/p /c:if注意pageContext.findAttribute(item)是 JSP EL 表达式${item}的底层实现它按page → request → session → application顺序查找属性。若item仅存于 request 域而 JSP 中误用sessionScope.item则返回 null 导致编译后代码报错。4. 校园场景下的关键优化屏蔽离开页面提示、JSON 数据交互与 styles.css 定制化4.1 屏蔽 JSP 离开页面提示解决表单未提交时的误触发弹窗校园用户常在填写商品发布表单时切换标签页导致beforeunload事件触发全局提示“确定要离开此页面吗”但实际用户只是临时查看课表。此提示应仅在表单有未保存修改时激活。标准做法是监听表单输入变化动态设置window.onbeforeunload在publish.jsp底部添加 JavaScriptscript let isFormDirty false; // 监听所有 input、textarea、select 的 change 和 input 事件 document.querySelectorAll(input, textarea, select).forEach(el { el.addEventListener(change, markAsDirty); el.addEventListener(input, markAsDirty); }); function markAsDirty() { isFormDirty true; } window.addEventListener(beforeunload, function(e) { if (isFormDirty) { // 标准化返回值现代浏览器忽略自定义文本仅显示默认提示 e.returnValue ; return ; } }); // 表单提交成功后清除脏标记 document.getElementById(publishForm).addEventListener(submit, function() { isFormDirty false; }); /script提示e.returnValue 是兼容 IE 和旧版 Chrome 的写法现代浏览器Chrome 80仅显示统一提示无法自定义文案因此无需return 请保存后再离开这类无效代码。4.2 JSP 与 JSON 数据交互用 JSONArray 解析后台返回的商品列表前端常需异步加载商品分类或搜索建议后端用JSONArray构造响应更轻量。jsp jsonarray import对应的是org.json库需将json-20231013.jar放入WEB-INF/lib/。Servlet 返回 JSON 的典型写法// CategoryServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ListCategory categories categoryDao.findAll(); // 查询分类列表 JSONArray array new JSONArray(); for (Category c : categories) { JSONObject obj new JSONObject(); obj.put(id, c.getId()); obj.put(name, c.getName()); array.put(obj); } response.setContentType(application/json;charsetUTF-8); response.getWriter().write(array.toString()); }JSP 页面通过fetch获取并渲染script fetch(${pageContext.request.contextPath}/category) .then(response response.json()) .then(data { const select document.getElementById(categorySelect); data.forEach(cat { const option document.createElement(option); option.value cat.id; option.textContent cat.name; select.appendChild(option); }); }); /script4.3 styles.css 定制化针对校园用户习惯的 3 项视觉优化styles.css不是通用模板需适配学生高频操作场景商品卡片宽度适配笔记本屏幕.item-card { width: calc(33.333% - 20px); }三列布局避免在 1366x768 分辨率下换行错乱价格突出显示.price { font-size: 1.4em; color: #e74c3c; font-weight: bold; }红色高亮降低价格感知门槛表单输入框聚焦效果input:focus, textarea:focus { outline: 2px solid #3498db; box-shadow: 0 0 5px rgba(52, 152, 219, 0.3); }蓝色边框提升操作反馈。关键 CSS 片段含响应式断点/* styles.css */ .item-grid { display: flex; flex-wrap: wrap; gap: 20px; } .item-card { flex: 1 1 calc(33.333% - 20px); min-width: 280px; /* 保证移动端单列 */ border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #fff; } media (max-width: 768px) { .item-card { flex: 1 1 100%; } } .price { font-size: 1.4em; color: #e74c3c; font-weight: bold; margin: 10px 0; } input:focus, textarea:focus { outline: 2px solid #3498db; box-shadow: 0 0 5px rgba(52, 152, 219, 0.3); border-color: #3498db; }5. 生产环境部署与排错从 bak 文件恢复、Java 环境变量配置到 Tomcat 日志定位5.1 利用 bak 文件恢复数据库校园平台最常发生的误删场景应对bak文件通常是 MySQL 的逻辑备份mysqldump生成命名如secondhand_20240615.bak。恢复步骤严格按顺序执行避免字符集错乱创建新数据库并指定字符集mysql -u root -p -e CREATE DATABASE secondhand CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;导入 bak 文件关键指定--default-character-setutf8mb4mysql -u root -p --default-character-setutf8mb4 secondhand /path/to/secondhand_20240615.bak验证数据完整性-- 检查表行数是否与备份前一致 SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema secondhand ORDER BY table_rows DESC;提示若bak文件含CREATE DATABASE语句导入前需先删除原库并重建否则报错“Database already exists”。生产环境应定期执行mysqldump -u user -p --single-transaction --routines --triggers secondhand /backup/secondhand_$(date %Y%m%d).bak--single-transaction保证 InnoDB 表一致性。5.2 Java 环境变量配置Tomcat 启动失败的 3 个关键检查点Tomcat 启动报JAVA_HOME not set或Unsupported Java version需逐项验证检查项正确配置示例错误示例诊断命令JAVA_HOMEexport JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64export JAVA_HOME/usr/bin/java指向 bin 目录echo $JAVA_HOME ls $JAVA_HOME/bin/javaPATHexport PATH$JAVA_HOME/bin:$PATHexport PATH/usr/local/bin:$JAVA_HOME/bin顺序错误which java java -versionJRE_HOME可选若 Tomcat 需独立 JRE设为$JAVA_HOME/jre指向 JDK 目录echo $JRE_HOME在tomcat/bin/setenv.sh中显式声明Linux/macOS#!/bin/sh export JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export JRE_HOME$JAVA_HOME/jre export CATALINA_OPTS-Xms512m -Xmx1024m5.3 Tomcat 日志定位法从 catalina.out 快速识别 JSP 编译错误当访问index.jsp报 500 错误首要查看logs/catalina.out。JSP 编译错误特征明显语法错误org.apache.jasper.JasperException: Unable to compile class for JSP后跟index_jsp.java:[line]类找不到java.lang.ClassNotFoundException: com.example.Item说明Item.class未放入WEB-INF/classes/或WEB-INF/lib/EL 表达式错误javax.el.PropertyNotFoundException: Property [price] not found on type [com.example.Item]表示Item类缺少getPrice()方法或字段非 public。日志截取示例含定位线索24-Jun-2024 14:22:31.123 SEVERE [http-nio-8080-exec-2] org.apache.jasper.compiler.Compiler.generateClass Error compiling file: /usr/local/tomcat/work/Catalina/localhost/secondhand/org/apache/jsp/index_jsp.java org.apache.jasper.JasperException: Unable to compile class for JSP ... Caused by: java.lang.RuntimeException: Invalid implicit object: sessionScope at org.apache.jasper.compiler.Validator.validate(Validator.java:1234)此错误表明 JSP 中误用了sessionScope.item而item实际在 request 域应改为requestScope.item或直接${item}。注意catalina.out是标准输出重定向包含System.out.println()输出而localhost.date.log记录 Servlet 初始化日志manager.date.log记录管理应用操作。定位问题优先看catalina.out因其包含完整堆栈。本文还有配套的精品资源点击获取