
简介这是一份基于JSP的二手交易平台管理系统完整源码面向Java Web学习者、毕业设计学生以及需要快速搭建交易类项目的开发者。系统涵盖用户注册登录、商品发布浏览、订单交易、后台管理等核心模块并注重数据库设计、安全机制、异常处理等关键实现能够完整体现一个Web应用从页面展示到数据持久化的全过程。压缩包共2000个文件以JSP页面、Java类、XML配置、JS/CSS前端资源、PNG图标及SQL脚本为主大小约52.57MB目录结构清晰便于按控制层、服务层、数据访问层分层查阅。当前已有90人学习浏览借助该源码可深入理解JSPServletJDBC的经典开发模式掌握前端交互、业务逻辑与数据访问的协作方式也能参考其性能优化和AJAX异步更新思路为毕业设计或项目答辩提供可运行、可扩展的完整范例。1. 二手交易平台源码不是拿来跑通的是拿来改的拿到一个基于JSP的二手交易平台管理系统源码.zip多半是要交Java课程设计或毕业设计。可很多人解压后第一反应是东西在哪、怎么打开第二反应是跑起来了但看上去很粗糙。这个标题背后真正值钱的部分不是那几行JSP标签而是整个JavaWeb的经典分层JSP做展示、Servlet做控制、JavaBean做模型数据库落在MySQL上。这套结构是JavaWeb课设最稳的骨架也是面试时最容易被追问的MVC到底烂熟没有。这篇笔记我就按实际开发顺序讲怎么把zip还原成能跑的项目表怎么建交易链路怎么做最后用一条一条踩坑经历告诉你什么东西会翻车。你不需要懂太多Spring只要会Servlet语法就能把这个项目改成答辩时拿得出手的样子。2. 先立住技术底座JSPServletJavaBean这套结构决定了你后面好不好改2.1 从zip到能响应的最小环境JDK、Tomcat、MySQL三件套怎么配二手交易平台这个规模我推荐你直接用最经典的一组版本JDK 8、Tomcat 9.x、MySQL 5.7。这三个版本都是老而稳网上资料最多报错随便搜都有答案。JDK 11以上不是不行但有的老项目用了内部API切换版本后会出现莫名其妙的反射报错没必要为了图新给自己挖坑。解压zip后你会看到一个典型的JavaWeb工程目录。Eclipse导入时选Existing Projects into WorkspaceIDEA里选Open直接定位到源码根目录。结构一般是这种样子secondhand-platform/ │ src/ │ com/platform/bean/ # 实体类 │ com/platform/dao/ # 数据库操作 │ com/platform/servlet/ # 控制器 │ com/platform/filter/ # 编码/登录过滤器 │ WebContent/ │ index.jsp │ login.jsp │ register.jsp │ goods_list.jsp │ goods_detail.jsp │ cart.jsp │ order.jsp │ admin/ # 后台管理页面 │ static/ # css/js/images │ WEB-INF/web.xml └── sql/ └── secondhand.sql # 建库建表脚本导入之后第一件事是把JRE和Tomcat关联起来。在Eclipse里Servers窗口新建Tomcat v9.0 ServerRuntime Environment指向你的Tomcat解压路径。IDEA用户更简单Run Configuration里选Tomcat ServerDeployment里把项目打成的war包加进去。这一步最容易出错的是JDK版本不匹配我一般习惯把Compiler compliance level统一设成1.8省得后面报Bad version 55.0之类的错。2.2 看懂WEB-INF这一整套目录哪层负责控制、哪层负责展示、哪层负责数据如果你把项目翻开了看到一个Servlet的doGet方法里面全是out.println()输出HTML先别急着嫌弃。这是很多早期课设项目的通病把Java代码和HTML混在一起。规范的JSP项目应该是低内聚、高分层JSP页面只负责渲染用c:forEach和${}取值不写复杂的Java逻辑。Servlet接收请求调用DAO层方法处理业务然后把结果放到request或session里forward到JSP。Bean层是纯数据载体字段名跟数据库列名一一对应。DAO层只有一个职责获取Connection、执行SQL、关闭资源。我在这个项目里一般会先做两个基础类DBUtil负责数据库连接BaseServlet负责反射分发请求。分发的意义是减少Servlet类的数量——你不想一个登录功能就写一个LoginServlet里面还要分doGet/doPost。做一个基础分发之后一个模块对应一个Servletaction参数决定走哪个方法代码量能压缩三分之一。2.3 为什么这套架构值得选以及什么时候你会后悔选它从项目本身来看JSP这套技术确实老但选它有一个非常现实的理由课程设计和毕设的评分标准里功能覆盖完整度比技术先进性占的比重更大。SpringBoot集成JSP虽然也能做但配置路径、打包war部署的坑比单纯JSP项目多一圈对不熟悉Maven的师弟师妹很不友好。JSPServlet的模型在Tomcat里天然可跑右键Run一下就出页面出错的概率低得多。什么时候会后悔当你真要上生产环境、面对高并发时JSP这套就顶不住了。页面首次编译慢、前后端耦合深、和前端工程师协作困难。但这个平台是课设不是电商双11量级完全不同。真要被问到为什么不选SpringBoot你答一句这个项目重点是理解HTTP请求如何贯穿JSP/Servlet/DAO三层就够得体了。3. 把数据库设计读透二手交易平台到底要几张表3.1 用户、商品、订单、留言四张核心表的主键关系与字段设计打开sql目录下的secondhand.sql你会看到建库建表语句。大多数二手交易平台的设计逃不出这四张核心表用户表、商品表、订单表、留言表。有些还会加一张收藏表或者分类表但我先按最标准的四张来讲。用户表的核心字段就这几个id、username、password、phone、address、avatar、create_time。密码字段我建议用varchar(64)虽然课设阶段很少真正做加密但至少不要在数据库里存明文。商品表是重头戏id、seller_id、title、description、price、original_price、category、images、status、create_timestatus字段用int表示0在售、1已卖出、2下架这套状态机放到下一小节细讲。订单表则有id、order_no、goods_id、buyer_id、seller_id、amount、status、create_timeorder_no用时间戳加随机数生成。留言表最简单id、user_id、goods_id、content、create_time。主键全部用自增id外键不加物理约束、只保留逻辑关联是有意的。二手交易平台的项目规模用不上ON DELETE CASCADE加上外键之后删除用户会被约束卡住课设演示时反而添乱。3.2 商品状态与交易流程从在售到已卖出的状态机怎么定这是我见过最多人做错的地方。很多同学给商品表加一个is_sold布尔值然后订单表里再存一个订单状态两个状态互相打架商品已卖出但订单还显示待付款或者订单已取消但商品还挂着。一套严格的交易流程状态机应该是这样0 在售 - 用户点击购买 - 生成订单状态变1锁定 1 锁定 - 买家付款/确认 - 状态变2已卖出 0 在售 - 卖家主动下架 - 状态变3下架 1 锁定 - 买家超时未付/取消 - 状态回0重新在售这个设计里锁定状态非常关键。它解决的是并发下单问题A用户看到商品在售下单后商品立刻变成锁定B用户再来买时提示商品已被人拍下。如果没有这个状态两个人同时下单两个订单都生成但商品只有一个后面就要做复杂的库存校验。二手交易平台的每件商品都是独一无二的锁定状态轻松解决了这个边界问题你在答辩时把这套逻辑讲出来老师会觉得很完善。3.3 初始化SQL脚本照着建库建表的注意事项在MySQL中执行导入脚本前有几个地方需要动手改。项目提供的secondhand.sql里数据库名一般是secondhand_db字符集用utf8mb4而不是utf8因为utf8mb4能存下emoji和生僻字。执行命令我用这种方式-- 直接执行建库建表脚本注意先设置编码 CREATE DATABASE IF NOT EXISTS secondhand_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE secondhand_db; -- 用户表 CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, phone VARCHAR(20) DEFAULT NULL, address VARCHAR(255) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里几个细节值得注意。用户名加唯一索引uk_username注册时数据库层面就挡住了重复账号只靠Java代码里查一次并不保险。create_time用DEFAULT CURRENT_TIMESTAMP插入时不用显式赋值。表引擎必须用InnoDB外键和事务都依赖它MyISAM在这个项目里没有存在意义。导入时如果报Unknown collation错误十有八九是MySQL版本的问题导致的排序规则不一致改成utf8mb4_general_ci立刻就好。商品表和订单表的SQL就不逐条贴了你打开脚本照着改即可。我特别提醒一句脚本里如果有DROP TABLE IF EXISTS执行前先掂量一下库里有没有开发数据花半天填进去的测试数据被一条脚本清空的痛苦我经历过。4. 核心交易链路从商品发布到下单成交这几个Servlet必改4.1 商品发布表单到后台文件上传与图片保存几乎所有二手交易平台都要有发布商品页面这里面最常被卡住的不是参数接收而是图片上传。我在这类项目里最常用的上传组件就是commons-fileupload加commons-io在web.xml里不配任何东西直接在Servlet里解析请求。核心代码长这样// 商品发布Servlet中的文件上传处理段 protected void publish(HttpServletRequest request, HttpServletResponse response) { // 1. 创建DiskFileItemFactory设置内存缓冲区和临时目录 DiskFileItemFactory factory new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); // 1MB 内存缓冲 factory.setRepository(new File(/tmp/upload_tmp)); // 2. 用ServletFileUpload解析请求 ServletFileUpload upload new ServletFileUpload(factory); upload.setFileSizeMax(5 * 1024 * 1024); // 单个文件最大5MB // 3. 分别取出普通表单字段与文件字段 ListFileItem items upload.parseRequest(request); String title null, price null; String imageUrl null; for (FileItem item : items) { if (item.isFormField()) { // 普通输入框比如商品标题、价格 if (title.equals(item.getFieldName())) { title item.getString(UTF-8); } if (price.equals(item.getFieldName())) { price item.getString(UTF-8); } } else { // 文件字段生成唯一文件名并保存到项目外部目录 String fileName System.currentTimeMillis() _ item.getName(); String saveDir D:/upload/secondhand; File dir new File(saveDir); if (!dir.exists()) dir.mkdirs(); item.write(new File(saveDir, fileName)); imageUrl /upload/ fileName; } } // 之后调用DAO插入数据库 goodsDao.insert(title, price, imageUrl); }这段代码有三个参数要按你的环境改setRepository的临时目录在Linux/Windows上路径写法不同Windows上直接写D盘路径时注意目录要存在。imageUrl存的是虚拟路径但文件真正保存到了D:/upload这个项目外目录所以你要在Tomcat里配一个虚拟映射——下面避坑章节会展开讲。4.2 购物车与订单生成事务边界放在哪一层有了商品之后交易链路的下一环是把商品加入购物车和下单。先明确一点JSP课设项目的购物车用一张数据库表实现是完全可以的没必要用Session存集合。原因是Session购物车存到服务器内存用户刷新一下就丢数据库表干净且能保留用户的历史记录。核心表结构就是cart_id、user_id、goods_id、add_time不需要amount字段因为同一个商品只允许出现一次——加入购物车时先查是否已存在存在则提示购物车已有该商品。生成订单这一步一定要用事务。如果先改商品状态、再插入订单记录中间任何一步抛异常就会出现商品锁定了但订单不存在这种事故。Servlet层直接调用DAO我习惯在DAO里把两步包进同一个事务// 订单DAO中的事务控制段 public void createOrder(Order order) throws Exception { Connection conn null; PreparedStatement ps1 null; PreparedStatement ps2 null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 插入订单记录 String sqlOrder INSERT INTO t_order (order_no, goods_id, buyer_id, seller_id, amount, status) VALUES (?, ?, ?, ?, ?, 0); ps1 conn.prepareStatement(sqlOrder); ps1.setString(1, order.getOrderNo()); ps1.setInt(2, order.getGoodsId()); ps1.setInt(3, order.getBuyerId()); ps1.setInt(4, order.getSellerId()); ps1.setBigDecimal(5, order.getAmount()); ps1.executeUpdate(); // 2. 锁定商品 String sqlGoods UPDATE t_goods SET status 1 WHERE id ? AND status 0; ps2 conn.prepareStatement(sqlGoods); ps2.setInt(1, order.getGoodsId()); int rows ps2.executeUpdate(); // 关键如果商品已经是锁定状态影响行数为0必须回滚 if (rows 0) { conn.rollback(); throw new RuntimeException(商品已被他人购买); } conn.commit(); } catch (Exception e) { if (conn ! null) conn.rollback(); throw e; } finally { DBUtil.close(conn, ps1, ps2); } }这段代码思路上有两个值得答辩时讲清楚的点。第一conn.setAutoCommit(false)之后所有的SQL都在同一个事务里要么都成功要么都回滚不会出现脏数据。第二UPDATE语句里带上了AND status 0这个条件这是一种乐观锁思路——先尝试修改通过executeUpdate()的返回值判断是否真的抢到了商品。如果两个用户同时下单数据库层面的行锁会保证只有一个UPDATE成功另一个影响行数为0直接回滚。4.3 我的发布/我的购买两个查询页面的SQL写法差异后台我发布的商品和前台我买到的商品两个页面看起来相似但SQL逻辑完全不同这是很多新手写串的地方。我的发布页查询的是商品表条件是seller_id 当前用户查询结果是商品列表展示的是商品图、标题、价格。而我的购买页查询的是订单表条件是buyer_id 当前用户但你要展示的不仅仅是订单号还要把商品标题、商品图片join出来。-- 我的发布直接查商品表 SELECT id, title, price, image_url, status, create_time FROM t_goods WHERE seller_id ? ORDER BY create_time DESC; -- 我的购买需要关联商品表获取商品信息 SELECT o.order_no, o.amount, o.status AS order_status, o.create_time, g.title, g.image_url FROM t_order o INNER JOIN t_goods g ON o.goods_id g.id WHERE o.buyer_id ? ORDER BY o.create_time DESC;两个查询的差异决定了前端JSP页面的渲染方式。我的发布页可以有一个下架/重新上架按钮操作的是商品表的状态字段我的购买页则根据订单状态显示待付款/已付款/已取消的文字或按钮。把这两条SQL理清楚前后台功能就不会打架了。5. 运行与改造避坑JSP项目最容易翻车的 5 个典型问题5.1 页面改完不生效是浏览器缓存还是Tomcat编译缓存现象是改了JSP文件刷新浏览器后页面没有任何变化甚至改了Servlet重启Tomcat也没用。出现这个现象时先看浏览器是否有缓存——F12打开控制台Network标签下勾选Disable cache再刷新一次。如果还不行那就是Tomcat的work目录里保留了JSP编译成Java和class的缓存文件。原因与解决Tomcat编译是增量式的JSP文件的时间戳没变就不重新编译。找到Tomcat安装路径下的work/Catalina/localhost/项目名目录整个删掉再重启TomcatJSP就会被强制重新编译。IDEA部署时如果你改了Servlet代码但没触发HotSwap也会出现旧class还在的情况最稳妥的方式是Rebuild Project不要依赖IDEA的热部署它对这个老架构支持得很差。5.2 中文乱码一条线全排查数据库连接URL是第一嫌疑现象是注册后查看用户列表中文名字全是问号或者乱码。排在第一位的原因是数据库连接URL少了characterEncoding参数导致Java向MySQL写入数据时用了错误的字符集。第二嫌疑是JSP页面本身没设置pageEncoding第三嫌疑是POST请求的编码过滤器没生效。// DBUtil中正确的JDBC连接串写法 private static final String URL jdbc:mysql://localhost:3306/secondhand_db ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai;这段连接串里useUnicodetrue和characterEncodingutf8必须成套出现缺一不可。serverTimezoneAsia/Shanghai是MySQL 8必须的参数MySQL 5.7不写也能跑。代码检查顺序我建议先看web.xml里的CharacterEncodingFilter有没有配再看连接串最后看JSP顶部的% page contentTypetext/html;charsetUTF-8 %。顺着这条线查5分钟必定位。5.3 图片上传后前端展示404路径映射没配数据库里存的不是真实路径现象是上传成功后数据库里有/upload/xxx.jpg但页面上图片裂开浏览器访问http://localhost:8080/upload/xxx.jpg直接404。原因是图片被存到了项目外部的D:/upload目录Tomcat默认只服务项目部署目录里的静态资源它根本不知道D:/upload这个目录的存在。解决方式是在Tomcat的conf/server.xml的Host节点里加一段Context配置Host namelocalhost appBasewebapps unpackWARstrue autoDeploytrue !-- 虚拟目录映射URL路径 /upload/ 对应磁盘目录 D:/upload/ -- Context path/upload docBaseD:/upload reloadablefalse / /Host加了这段配置后浏览器请求/upload/xxx.jpg时Tomcat就会去D:/upload目录查找物理文件。你还需要保证D:/upload目录存在且Tomcat进程有读取权限。Windows系统下路径中的反斜杠要写成D:/upload这种正斜杠格式Tomcat对反斜杠处理在某些版本上会报错。如果你把项目打包war部署的时候不想改Tomcat配置文件更优雅的方案是写一个自定义Servlet专门处理图片读取但课设阶段用Context映射是最省事的。5.4 前端页面JSP里到处是Java代码显示逻辑和业务逻辑混杂现象就是你打开一个JSP文件里面% if (...) { %到处都是接下来就是改一处页面要同时小心Java逻辑和HTML标签。这个问题的根源不是写法本身而是方便。很多课设源码为了直白直接在JSP里写Java代码取数据不用EL表达式和JSTL标签。开发时确实快但你做中期演示时Product Owner也就是你的指导老师提出页面价格要显示红色加粗你不得不在一堆Java代码里找HTML的位置。解决方式推荐在JSP页面统一改用EL表达式和JSTL核心标签% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:forEach items${goodsList} varg div classgoods-card img src${g.imageUrl} alt商品图片 / h3${g.title}/h3 p classprice${g.price}/p c:if test${g.status 0} a hrefcart?actionaddgoodsId${g.id}加入购物车/a /c:if /div /c:forEach${g.price}这种EL表达式会自动调用JavaBean的getPrice()方法页面上没有任何Java脚本片段前端看着清爽你也不用担心JSP引入的Java代码编译失败连累整个页面无法显示。5.5 数据库连不上、端口被占用、内存溢出环境类问题汇总这三个问题属于打开一个老项目时的经典三连。连不上数据库时先执行telnet localhost 3306看端口通不通不通就检查MySQL服务有没有启动通了再检查用户名密码是否为源码默认的root/123456这种老项目十有八九配置写死了搜索jdbc:mysql即可定位配置文件。Tomcat启动报端口被占用时找到占用8080的进程关掉或者改server.xml里的Connector端口。内存溢出绝大多数是JDK版本太高导致换回JDK 8就安静了。6. 部署上线前用一套自查清单检验你的项目能不能撑住演示项目做得差不多了别急着打包交差。我最后一次验收课设项目时有一套固定的检查流程按这个顺序过一遍演示时基本不会翻车。第一关是数据层。登录管理员账号看后台的用户列表、商品列表是否正常显示。这里要特别留意空数据的情况——数据库里一条测试数据都没有时页面会不会报空指针或者显示一堆空表格。一个顶多花10分钟就能修好的方案在DAO层把查询结果为空时的null改成new ArrayList()JSP页面上配上c:if test${empty list}的友好提示。第二关是编码与路径。找一台干净的电脑按下面的步骤做一次模拟验收把项目导出成war包放到Tomcat的webapps目录启动后访问首页注册一个新用户发布一件二手商品带图片再另注册一个账号去下单确认商品在我的发布里状态从未售出变成已锁定。这一遍走完前面所有的乱码、图片404、状态不同步问题都会暴露出来。第三关是边界操作。反复刷新商品详情页观察库存对应的状态有没有异常变化快速双击立即购买按钮看会不会生成两个订单。这些动作在课设答辩现场是高频出现的意外提前用一笔测试数据跑几遍比到时候慌慌张张改代码体面得多。如果你还有精力可以把分页加进去——商品列表超过20条时用LIMIT ? OFFSET ?做分页这个是老师最爱问的加分点。我自己的习惯是留一个备份SQL脚本放在项目外的安全位置每次跑数据测试前先备份。JSP项目里一个PreparedStatement的?占位符数量写错就可能让整张表的数据变得不可用。做二手交易平台功能可以朴素但这种稳定性和安全性上的小节恰恰是拿分的关键。希望这篇笔记能帮你把这个经典JavaWeb项目从能跑做到经得起问祝顺利。本文还有配套的精品资源点击获取