Java+JSP校园二手交易平台实战:从环境搭建到部署上线
2026/9/23 12:46:32 网站建设 项目流程

简介:这份资源是面向计算机相关专业学生与项目实战学习者的校园二手物品交易平台完整设计与实现包,采用Java语言与JSP技术构建,基于MVC设计模式划分模型、视图与控制器三层结构,适合用作毕业设计、期末大作业或课程设计参考。压缩包共1305个文件,约22.74MB,其中128个java源文件与119个jsp页面构成系统主体,364个js、146个css及大量png、gif、jpg等静态资源支撑前端交互与界面展示,另含2个sql脚本、2个doc文档及properties配置等,覆盖用户注册登录、商品发布浏览、交易沟通、订单管理、在线支付与评价反馈等完整业务环节。项目源码经本地编译调试可运行,数据库表关系与业务逻辑设计完整,并附开发文档与数据文档,便于快速理解系统架构与实现思路。目前已有39人学习,适合需要完整赛题方案与实战练习材料的学习者参考使用。

1. 校园二手交易平台用 Java+JSP 做,到底在解决什么问题

每年毕业季,宿舍楼下堆着的旧书、台灯、自行车,和新生群里刷屏的“求购洗衣机”,本质上是一个信息匹配问题。基于 Java 的校园二手物品交易平台,要做的就是把这个匹配过程从微信群搬到浏览器里:卖家发布商品、买家搜索浏览、双方在线沟通、线下交割。它不解决支付和物流,核心价值在于把零散的供需信息结构化,让“谁有闲置、谁要买”变得可检索。

这个选题在 JavaWeb 课程设计和毕设里出现频率极高,原因很实际:技术栈成熟(JSP+Servlet+MySQL)、业务逻辑清晰(增删改查加权限)、工作量可控。适合两类人——一是刚学完 Java 基础、需要完整项目练手的在校生,二是要交课程设计但不想从零造轮子的开发者。JSP 在这里的角色是服务端页面渲染,配合 Model2 思想把 JavaBean、Servlet、JSP 三层分开,避免把所有逻辑塞进一个页面里。下面从环境搭建到核心功能实现,把这条路走通。

2. 环境搭建与 Model2 分层:别把 JSP 写成脚本大杂烩

2.1 JDK、Tomcat、MySQL 的版本搭配与安装顺序

环境配置是第一个拦路虎,版本不匹配导致的报错能吃掉半天时间。我一般按这个顺序来:先装 JDK,再配 Tomcat,最后装 MySQL。JDK 选 8 或 11 都行,但要注意 Tomcat 版本对 JDK 的要求——Tomcat 9 支持 JDK 8+,Tomcat 10 开始把 javax.* 包名改成了 jakarta.*,如果你用的 JSP 教程还是老写法,Tomcat 10 会直接报 ClassNotFoundException。稳妥起见,课程设计用 JDK 8 + Tomcat 8.5/9 + MySQL 5.7/8.0 这套组合,资料最多,踩坑最少。

安装完 JDK 后配置环境变量,JAVA_HOME指向 JDK 根目录,Path里加%JAVA_HOME%\bin。验证命令:

java -version javac -version

两条命令输出的版本号要一致。如果javac提示“不是内部或外部命令”,说明 Path 没配好,或者你装的是 JRE 而不是 JDK。Tomcat 解压后不需要装,配置CATALINA_HOME指向解压目录即可,启动用bin\startup.bat,浏览器访问localhost:8080看到猫页面就算通了。

MySQL 安装时记住 root 密码,字符集选 utf8mb4,排序规则选 utf8mb4_general_ci。建库语句:

CREATE DATABASE campus_trade DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

注意:JDBC 连接 MySQL 8.0 时,驱动类名是com.mysql.cj.jdbc.Driver,URL 要加serverTimezone=Asia/Shanghai,否则会报时区错误。MySQL 5.7 用com.mysql.jdbc.Driver即可。

2.2 Model2 三层结构:Servlet 收请求、JavaBean 传数据、JSP 只负责显示

JSP Model2 的核心就一句话:JSP 页面里不写业务逻辑,只做展示。请求先到 Servlet,Servlet 调用 Service/DAO 处理数据,把结果存进 request 或 session,然后 forward 到 JSP。这样改需求时只动 Servlet 或 DAO,不用在一堆 HTML 里找 Java 代码。

目录结构按这个来分:

src/ com.campus.entity/ // JavaBean:User、Goods、Order com.campus.dao/ // 数据库操作:UserDao、GoodsDao com.campus.servlet/ // 控制器:LoginServlet、GoodsListServlet com.campus.util/ // 工具类:DBUtil WebContent/ WEB-INF/web.xml // Servlet 映射 css/ js/ images/ login.jsp index.jsp goods_detail.jsp

一个典型的 Servlet 写法:

@WebServlet("/goods/list") public class GoodsListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 获取查询参数 String keyword = req.getParameter("keyword"); // 2. 调用 DAO 查询 GoodsDao dao = new GoodsDao(); List<Goods> list = dao.search(keyword); // 3. 结果存入 request req.setAttribute("goodsList", list); // 4. 转发到 JSP 展示 req.getRequestDispatcher("/goods_list.jsp").forward(req, resp); } }

逻辑说明:@WebServlet注解替代了 web.xml 里的<servlet-mapping>,Servlet 3.0 以后都这么写。forward是服务器内部跳转,浏览器地址栏不变,request 里的数据能带到 JSP。如果用sendRedirect,request 数据会丢,得改用 session 或 URL 参数。

参数说明:keyword来自搜索框的 name 属性,DAO 里用PreparedStatementLIKE '%?%'防注入。req.setAttribute的 key 在 JSP 里用 EL 表达式${goodsList}取。

JSP 页面里只写:

<c:forEach items="${goodsList}" var="g"> <div class="item"> <h3>${g.title}</h3> <p>价格:${g.price}</p> <a href="goods/detail?id=${g.id}">查看详情</a> </div> </c:forEach>

用 JSTL 的c:forEach替代 Java 脚本片段,页面干净,前端改样式不用碰 Java 代码。需要引入jstl.jarstandard.jar,或者用 Maven 加依赖。

3. 数据库设计与核心表:用户、商品、订单三张表怎么定字段

3.1 用户表与商品表的字段取舍

表设计直接决定后面代码好不好写。用户表user至少要有:id(主键自增)、username(唯一索引)、password(存 MD5 或 BCrypt 哈希,别存明文)、nicknamephoneschoolcreate_time。校园场景下school字段有用,可以按学校筛选商品,但如果你只做一个学校的,这个字段可以省。

商品表goods是核心:idtitledescriptionpriceimage_urlcategory_idseller_id(外键关联 user.id)、status(0 在售 / 1 已售 / 2 下架)、create_timeimage_url存相对路径,比如/upload/20250101_abc.jpg,文件本身放在 WebContent 下的 upload 目录。status用 tinyint 而不是 varchar,查询效率高,代码里用常量类映射。

分类表category单独建:idnamesort_order。常见分类有教材书籍、电子数码、生活用品、运动户外、代步工具。分类数据量小,可以在启动时加载到 application 作用域,避免每次查库。

3.2 订单表与交易状态流转

订单表orders记录交易意向:idgoods_idbuyer_idseller_idpricestatus(0 待确认 / 1 已确认 / 2 已完成 / 3 已取消)、create_timefinish_time。校园二手交易通常线下付款,所以订单表不涉及支付流水,只记录“谁想买、卖家同不同意、最后成没成”。

状态流转逻辑写在 Service 层:

public boolean confirmOrder(int orderId, int sellerId) { Order order = orderDao.findById(orderId); // 只有卖家本人能确认,且订单处于待确认状态 if (order.getSellerId() != sellerId || order.getStatus() != 0) { return false; } order.setStatus(1); return orderDao.updateStatus(order); }

逻辑说明:先查订单,校验操作人身份和当前状态,再更新。这种“先查后改”在并发下有问题——两个请求同时查到 status=0,都去更新,会重复确认。解决办法是用UPDATE orders SET status=1 WHERE id=? AND status=0,根据返回的影响行数判断是否成功。

参数说明:sellerId从 session 里取当前登录用户,不能从请求参数取,否则别人可以伪造。status的数值含义在常量类OrderStatus里定义,避免代码里到处写魔法数字。

建表 SQL 示例:

CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, description TEXT, price DECIMAL(10,2) NOT NULL, image_url VARCHAR(255), category_id INT, seller_id INT NOT NULL, status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_seller (seller_id), INDEX idx_category (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意:外键约束在课程设计里可以加,但生产环境高并发下外键会影响插入性能,很多团队选择在应用层保证一致性。这里加INDEX而不是FOREIGN KEY,查询快,删除用户时手动处理关联商品。

4. 商品发布与图片上传:文件存哪、怎么防重复、路径怎么拼

4.1 上传目录的配置与文件名生成策略

图片上传是二手平台绕不开的功能。JSP 表单要设enctype="multipart/form-data",Servlet 用@MultipartConfig注解接收。文件不能直接存到 WebContent 下,因为重新部署 war 包时会被覆盖。常见做法是存到服务器文件系统的固定目录,比如/data/campus_trade/upload/,然后在 Tomcat 里配一个虚拟路径映射,或者用 Servlet 读文件流输出。

我一般会在web.xml或 Tomcat 的server.xml里配:

<Context docBase="/data/campus_trade/upload" path="/upload" />

这样 JSP 里${pageContext.request.contextPath}/upload/xxx.jpg就能访问到。开发阶段图省事,直接存WebContent/upload也行,但要知道这个坑。

文件名生成用 UUID 加原扩展名:

@WebServlet("/goods/publish") @MultipartConfig(maxFileSize = 5 * 1024 * 1024) public class GoodsPublishServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part filePart = req.getPart("image"); String originalName = filePart.getSubmittedFileName(); // 提取扩展名 String ext = originalName.substring(originalName.lastIndexOf(".")); // UUID 防重名 String newName = UUID.randomUUID().toString().replace("-", "") + ext; // 上传目录 String uploadDir = getServletContext().getRealPath("/upload"); File dir = new File(uploadDir); if (!dir.exists()) dir.mkdirs(); filePart.write(uploadDir + File.separator + newName); // 存相对路径到数据库 String imageUrl = "upload/" + newName; // ... 继续保存商品信息 } }

逻辑说明:getSubmittedFileName()拿到用户本地文件名,只取扩展名,避免中文名和特殊字符问题。UUID 保证同一秒上传多张图也不会覆盖。getRealPath在 war 包解压后指向实际目录,但重新部署会丢,所以生产环境要用外部目录。

参数说明:maxFileSize限制单文件 5MB,maxRequestSize限制整个请求大小。超过会抛IllegalStateException,要在页面提示“图片不能超过 5M”。扩展名要白名单校验,只允许 jpg、jpeg、png、gif,防止上传 jsp 文件被解析。

4.2 商品列表的分页查询与分类筛选

商品多了必须分页。MySQL 用LIMIT offset, size,offset 是起始行号。Servlet 里算:

int page = Integer.parseInt(req.getParameter("page") == null ? "1" : req.getParameter("page")); int size = 12; int offset = (page - 1) * size; List<Goods> list = goodsDao.findByPage(offset, size, categoryId, keyword); int total = goodsDao.count(categoryId, keyword); int totalPages = (total + size - 1) / size; req.setAttribute("currentPage", page); req.setAttribute("totalPages", totalPages);

DAO 里拼 SQL 要注意:分类和关键词可能为空,用 StringBuilder 动态拼,但参数必须用PreparedStatement?占位。比如:

StringBuilder sql = new StringBuilder("SELECT * FROM goods WHERE status=0"); List<Object> params = new ArrayList<>(); if (categoryId > 0) { sql.append(" AND category_id=?"); params.add(categoryId); } if (keyword != null && !keyword.trim().isEmpty()) { sql.append(" AND title LIKE ?"); params.add("%" + keyword.trim() + "%"); } sql.append(" ORDER BY create_time DESC LIMIT ?,?"); params.add(offset); params.add(size);

逻辑说明:status=0只查在售商品,已售和下架的不显示。ORDER BY create_time DESC让最新发布的排前面。LIMIT的两个参数也走占位符,避免拼接。

参数说明:page从 URL 取,要做边界校验,小于 1 按 1 算,大于总页数按最后一页算。size每页 12 条,三列布局刚好四行。totalPages传给 JSP 渲染分页条。

JSP 分页条用 JSTL:

<c:forEach begin="1" end="${totalPages}" var="i"> <a href="goods/list?page=${i}&categoryId=${param.categoryId}" class="${i == currentPage ? 'active' : ''}">${i}</a> </c:forEach>

注意:param.categoryId在翻页时保留筛选条件,否则点第二页会丢掉分类。关键词同理,要拼在 URL 里并做 URLEncoder 编码。

5. 避坑与排查:登录态、中文乱码、JSP 编译报错

5.1 登录态丢失与 session 超时

现象:用户登录后,点几个页面就提示未登录,或者重启 Tomcat 后所有用户掉线。

原因:session 默认存在内存里,Tomcat 重启就清空。另外 session 默认超时 30 分钟,超时后session.getAttribute("user")返回 null。

解决:登录成功后把 user 对象存 session,每个需要登录的 Servlet 先检查:

User user = (User) req.getSession().getAttribute("user"); if (user == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; }

如果不想重启掉线,可以配 Tomcat 的 session 持久化,在context.xml里加<Manager pathname="SESSIONS.ser" />。但课程设计没必要,知道这个机制就行。

5.2 中文乱码的三处来源

现象:表单提交的中文存到数据库变成问号,或者页面显示乱码。

原因:三处编码要统一——JSP 页面<%@ page contentType="text/html;charset=UTF-8" %>、request 解码req.setCharacterEncoding("UTF-8")、数据库连接 URL 加useUnicode=true&characterEncoding=utf8。Tomcat 8 以后 GET 请求默认 UTF-8,但 POST 必须手动设。

解决:在 Servlet 的doPost第一行写req.setCharacterEncoding("UTF-8"),或者写一个 Filter 统一处理:

public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); chain.doFilter(req, resp); } }

5.3 JSP 编译报错:javax 与 jakarta 包名冲突

现象:Tomcat 10 下访问 JSP 报java.lang.NoClassDefFoundError: javax/servlet/jsp/PageContext

原因:Tomcat 10 把 Java EE 的javax.*全部改成了jakarta.*,老教程里的 JSP 标签库和 Servlet API 都是 javax 包。

解决:要么换 Tomcat 9,要么把所有 import 改成 jakarta,并换用支持 jakarta 的 JSTL 版本。课程设计建议直接用 Tomcat 9,省去改包名的麻烦。

5.4 图片上传后访问 404

现象:文件明明写到了 upload 目录,但浏览器访问图片地址是 404。

原因:getRealPath("/upload")在开发时指向WebContent/upload,但 IDE 部署时可能只复制编译后的 class 和 jsp,空目录不会被复制。或者上传目录在 war 包外,没有配虚拟路径。

解决:在 upload 目录里放一个空的.gitkeep文件,或者代码里dir.mkdirs()确保目录存在。如果是外部目录,检查 Tomcat 的server.xml<Context>配置的docBase路径是否正确,改完重启 Tomcat。

5.5 数据库连接未关闭导致连接池耗尽

现象:系统跑一段时间后报Too many connections,或者页面越来越慢。

原因:DAO 里ConnectionPreparedStatementResultSet没在 finally 里关闭,每次请求泄漏一个连接。

解决:用 try-with-resources 自动关闭:

try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { // 设置参数、执行查询 } catch (SQLException e) { e.printStackTrace(); }

ResultSet也放在 try 的括号里。如果项目大了,引入 Druid 或 HikariCP 连接池,在druid.properties里配maxActive=20,监控连接使用情况。

6. 从能跑到好用:搜索优化与部署上线的几个实操技巧

6.1 用 LIKE 做搜索的边界与替代方案

LIKE '%关键词%'在数据量小的时候没问题,但商品表超过几万行后,全表扫描会明显变慢。校园平台通常几千条数据,LIKE 够用。如果要做得好一点,可以给title加全文索引:

ALTER TABLE goods ADD FULLTEXT INDEX ft_title (title) WITH PARSER ngram;

然后查询用MATCH(title) AGAINST('关键词' IN BOOLEAN MODE)。ngram 解析器支持中文分词,MySQL 5.7 以上可用。但全文索引对短词和停用词有限制,实际效果要测。我一般先上 LIKE,真慢了再换。

另一个技巧是搜索历史记录。用户搜过的词存到 cookie 或 session,下次点搜索框自动提示。实现简单,体验提升明显:

// 搜索后把关键词存 localStorage localStorage.setItem('lastSearch', keyword); // 页面加载时回填 document.getElementById('keyword').value = localStorage.getItem('lastSearch') || '';

6.2 部署到服务器:war 包、数据库导出与 Tomcat 配置

本地跑通后,部署到云服务器让同学访问,才算完整。步骤:

  1. IDE 里导出 war 包,或者 Maven 执行mvn package
  2. 服务器装 JDK 和 Tomcat,把 war 丢进webapps目录,启动后自动解压。
  3. 数据库用mysqldump -u root -p campus_trade > campus_trade.sql导出,服务器上mysql -u root -p campus_trade < campus_trade.sql导入。
  4. 改数据库连接配置,把localhost改成服务器内网 IP,密码改成服务器 MySQL 密码。
  5. 开放安全组 8080 端口,浏览器访问服务器IP:8080/项目名

注意:服务器上的 MySQL 默认只允许 localhost 连接,要执行GRANT ALL ON campus_trade.* TO 'user'@'%' IDENTIFIED BY 'password';FLUSH PRIVILEGES;。Tomcat 的server.xmlConnectorURIEncoding设为UTF-8,避免 GET 请求中文乱码。

6.3 一个验证清单:上线前逐项检查

部署完别急着发链接,按这个清单过一遍:

检查项预期结果失败时看哪里
首页商品列表显示最新 12 条在售商品GoodsListServlet 的 SQL 和分页参数
搜索“教材”返回标题含教材的商品LIKE 拼接和关键词编码
发布商品带图数据库有记录,图片能显示upload 目录权限和虚拟路径
未登录点发布跳转登录页session 检查和拦截逻辑
卖家确认订单订单状态变为已确认UPDATE 的影响行数和权限校验
手机浏览器访问页面自适应不溢出viewport meta 和 CSS 媒体查询

这个清单是我每次部署完必跑的,能挡住八成低级问题。尤其是图片上传和 session 检查,翻车次数最多。

6.4 最后说一个习惯

我做完每个课程设计都会把数据库连接信息、Tomcat 版本、JDK 版本写在一个README.md里,连同建表 SQL 和部署步骤。过两个月回头看,或者换台电脑重新搭,能省掉重新摸索的时间。校园二手平台这个项目,技术不复杂,但细节多——编码、路径、session、分页,每个点都可能卡住。把踩过的坑记下来,下次就是经验。希望帮到你。

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

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

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

立即咨询