
简介JSPMySQL 校园二手物品交易系统源码面向毕业设计、课程设计及 Java Web 学习者采用 B/S 架构和 SSM 框架完整实现前台与后台支持用户注册登录、物品发布搜索、订单管理、后台分类等贴合校园闲置交易场景。包体为 7z共 202 个文件、约 12.63MB含 40 个 Java 源文件、17 个 JSP 页面、40 个 class 文件及图片、CSS、JS、XML、SQL 等资源既有可运行项目也便于源码研读。已有 21 人学习适合参考其中的 SSM 整合流程、订单模块编码和数据库表设计对完成课程设计或毕设很有帮助。通过源码可快速掌握前后台交互逻辑、分类管理方式理顺物品发布、订单流转和用户权限等关键环节基于 SQL 脚本直接部署并扩展为自己的毕业设计作品。1. 校园二手交易系统源码JSPMySQL 到底能复现什么这套源码我拆过不止一次。评审老师最常问的四个问题——密码存没存明文、商品图片怎么传上去、订单下了商品状态怎么变、数据库连接断了页面怎么表现——在这份基于 JSP MySQL 的校园二手物品交易系统里都能找到对应代码。它不是 Spring Boot 那种半自动 MVC而是 JSP Servlet JDBC 的经典 Java Web 结构登录、注册、商品发布、列表搜索、购物车、下单、后台管理都有独立页面适合毕业设计和课程设计这类必须把原理讲清楚的场景。如果你是第一次拿这个工程我的建议是先把它当成一套能改的骨架表结构齐全、三层边界清楚、扩展点好找这比直接当商城上线要靠谱得多。2. 架构与数据表三层结构怎么分五张表怎么落开始复现之前先摸结构。JSP 项目最容易劝退新手的不是语法而是不知道哪些代码负责接请求、哪些代码负责查库、哪些页面负责展示。先搞清楚这三层后面部署和改功能都会顺很多。2.1 为什么 JSP Servlet JDBC 反而是好选择很多买家第一眼看到 SSM、Spring Boot 就觉得更“高级”但我给毕设写代码更看重一句话能讲完的链路JSP 页面发起请求Servlet 接收参数并调用 DAODAO 用 JDBC 拼 SQL结果再回到 JSP。中间没有 Spring 容器也没有 MyBatis 的 Mapper 代理代码是“死”的但也是透明的。评审追问“登录状态存在哪”你可以直接指给他看 session追问“SQL 注入怎么防”你能当场打开 DAO 里的 PreparedStatement。拿这套校园二手交易系统来说发布商品、搜索、加购物车、下订单走的都是同一条链路。它不像前后端分离项目那样要同时调接口、跨域、处理 token从头到尾只有一个 Java Web 应用。毕设场景下这样做的好处是好写、好讲、好改。你换成 Spring Boot 反而要解释自动配置和框架约定绕一圈又回到业务本身。如果你是新手真正值得注意的不是“用不用框架”而是别把 DAO 代码写进 JSP。我见过不少 JSP 里直接 Class.forName 查数据库的版本跑起来没问题答辩被问“MVC 是什么”就露馅。这套源码里如果 servlet 包和 dao 包分得清楚那基本就是规范的三层结构你需要做的只是把每个包的作用读透。2.2 拿到源码先看这三个目录我拿到一个 Java Web 工程第一步不是急着启动而是先看三个地方web.xml、数据库工具类、sql 脚本。web.xml 决定所有 Servlet 映射数据库工具类决定连接参数sql 脚本决定表结构。这三处能对上项目 80% 能直接跑。目录/文件作用先看什么src/com/xx/entity实体类字段是否和数据表列名一一对应src/com/xx/daoDAO 层有没有写 PreparedStatement连接是否关闭src/com/xx/servlet控制层请求路径和 web.xml 的 url-pattern 是否一致webappWebContentJSP 页面页面里有没有直接写 Java 代码sql/campus_shop.sql建表脚本字符集、外键、初始管理员账号这里的常见错误是 IDEA 导入 Eclipse 工程后Web 根目录没设置选中了项目根目录而不是 webapp启动 Tomcat 永远找不到页面。先看清楚目录结构再动手比反复重启 Tomcat 有用得多。典型工程的目录长这样campus_shop/ ├─ src/com/campus/entity/User.java ├─ src/com/campus/dao/UserDao.java ├─ src/com/campus/servlet/LoginServlet.java ├─ src/com/campus/util/DBUtil.java ├─ webapp/login.jsp ├─ webapp/index.jsp ├─ webapp/admin/manage_user.jsp └─ sql/campus_shop.sql目录本身不会直接决定能否运行但它决定你排错时往哪个文件找。实体类对应表字段工具类对应连接配置webapp 对应页面展示三块分清楚后任何报错都能立刻定位到具体层。2.3 核心表结构五张表一次看懂校园二手交易系统拆到数据库层面核心就是用户、分类、商品、订单、留言。购物车要不要建表我的建议是不建session 就够理由在后面单独说。下面这一套建表脚本是常见做法字段名可能和源码里有出入但业务含义基本一致。CREATE DATABASE campus_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE campus_shop; CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 当前源码存的是明文改造后存 MD5 值, nickname VARCHAR(50) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, email VARCHAR(100) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0普通用户1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE category ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 分类名教材、数码、生活, sort INT DEFAULT 0 COMMENT 排序越小越靠前, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分类表; CREATE TABLE goods ( id INT NOT NULL AUTO_INCREMENT, uid INT NOT NULL COMMENT 卖家id, category_id INT DEFAULT NULL, title VARCHAR(100) NOT NULL, description TEXT, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, original_price DECIMAL(10,2) DEFAULT NULL, image VARCHAR(200) DEFAULT NULL COMMENT 上传后的图片相对路径, status TINYINT NOT NULL DEFAULT 0 COMMENT 0在售1已售2下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_uid (uid), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT二手商品表; CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号时间戳随机数, buyer_id INT NOT NULL, seller_id INT NOT NULL, goods_id INT NOT NULL, goods_title VARCHAR(100) DEFAULT NULL COMMENT 下单时的商品标题快照, price DECIMAL(10,2) NOT NULL COMMENT 下单时的成交价, status TINYINT DEFAULT 0 COMMENT 0待确认1已完成2已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE comment ( id INT NOT NULL AUTO_INCREMENT, goods_id INT NOT NULL, from_user INT NOT NULL, content VARCHAR(255) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_goods (goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品留言表;这段脚本里有几个字段选择值得注意。价格用 DECIMAL(10,2) 而不是 float是为了避免浮点精度问题商品状态用 TINYINT 而不是字符串查询时用 where status0 效率更高订单号带唯一索引因为下单时经常要用它查状态。字符集统一用 utf8mb4它能存 emoji 表情普通 utf8 在个别同学粘贴特殊符号时会报字符集不匹配。外键我只在商品表上加了一个 uid 引用订单表不设外键。原因很现实课程设计阶段经常要删数据重测外键过多会导致删不掉用户、改不了分类。订单表里的 goods_title 和 price 是有意冗余的查询订单时不用每次 join 商品表而且商品后来被修改或删除历史订单仍然保留当时的信息。2.4 订单表里为什么冗余商品标题和价格直观设计订单表只有 goods_id看订单时 join 商品表拿到标题和价格。但二手交易场景里商品卖出后可能被下架甚至删掉如果卖家改动价格历史订单显示也会跟着变。把 goods_title 和 price 直接存在订单表里相当于给这笔交易拍了快照无论商品表怎么改订单永远是下单时的样子。这是交易系统里很常见的“快照”设计答辩时可以主动讲出来。购物车不建表也是类似的取舍二手交易订单小而少购物车用 session 里的 MapInteger, Integer 就够了数量存在会话里刷新不丢关闭浏览器才清空。硬要建购物车表等于多维护一套增删改查功能收益却很小。3. 本机部署JDK/Tomcat/MySQL 版本搭配与三个必改配置MySQL 安装教程到处都有但真正卡住人的是版本组合。这一章给出的环境组合能跑通绝大多数 JSP 老项目照着配就行不要追新版本。3.1 先定版本再谈部署我自己复现 JSP 项目一向是 JDK 1.8 Tomcat 8.5 MySQL 5.7原因有两个一是 Tomcat 8.5 还用 javax.servlet 包和源码里的 import 完全兼容二是 MySQL 5.7 对 JDBC URL 的要求比 8.0 简单不需要 serverTimezone。如果本地已经装了 MySQL 8.0不是不能用但要把驱动类从 com.mysql.jdbc.Driver 换成 com.mysql.cj.jdbc.DriverURL 里加时区参数这个后面会列出来。组件推荐版本理由JDK1.8Tomcat 8.5 官方支持Java 8 语法在源码里最常见Tomcat8.5使用的还是 javax.servlet与老 JSP 项目完全兼容MySQL5.7驱动、URL 最简单免去时区参数mysql-connector-java5.1.49对应 MySQL 5.7MySQL 8.0 用 8.x 驱动IDEEclipse / IntelliJ IDEA都能跑IDEA 注意 Web 根目录设置这套环境跑校园二手交易这种多点登录、发布、下单的老项目绰绰有余。版本越新越容易碰到兼容性报错Tomcat 10 就是最典型的例子第五章会专门讲。先按这个组合跑通再考虑升级任何东西。3.2 数据库导入一条命令和两处检查把根目录下的 sql 脚本找到一般在 sql/ 或 database/ 目录文件名常见是 campus_shop.sql 或 campus.sql内容不外乎建库、建表、插管理员。命令行导入最直接mysql --default-character-setutf8mb4 -u root -p campus_shop.sql导入后进入 MySQL 做两件事确认表出现、确认管理员存在。mysql -u root -pCREATE DATABASE IF NOT EXISTS campus_shop DEFAULT CHARACTER SET utf8mb4; USE campus_shop; SOURCE /完整路径/campus_shop.sql; SHOW TABLES; SELECT id, username, role FROM user;SHOW TABLES 应该能看到 user、category、goods、orders、comment 这一批表SELECT 结果里应该有一条 role1 的管理员没有的话后台登录入口进不去。数据库导入报 utf8 错大多是 MySQL 客户端字符集和脚本不一致加 --default-character-setutf8mb4 重跑一遍基本能解决。密码字段如果能看到值说明当前源码是明文存储这是初始状态不要因此认为项目坏了。3.3 数据库连接配置改一处还是改三处很多项目把连接参数写在数据库工具类的 getConnection() 方法里也有的抽成 db.properties。两种方式本质一样关键是全局只有一份配置。改错地方是最常见的启动失败原因——你改了 A 文件代码却在读 B 文件。先全局搜索 jdbc:mysql确认落点在哪再动手。jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/campus_shop?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password你自己的密码如果本地是 MySQL 8.0改成下面这套jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/campus_shop?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password你自己的密码参数含义要说清楚useUnicodetrue 和 characterEncodingutf8 管中文useSSLfalse 是为了省掉 SSL 握手警告serverTimezoneAsia/Shanghai 是 MySQL 8.0 驱动强制要求的不加会报 CST 时区无法识别。驱动 jar 必须出现在 WEB-INF/lib 或 IDEA 的 Libraries 里只把 jar 下载到桌面不算数。检查办法是展开项目依赖能看到 mysql-connector-java-5.1.49.jar 才算挂上。提示如果项目里找不到 properties 文件这些参数通常会直接写在数据库工具类里改的时候保持同一份配置即可不要同时改两个地方。3.4 Tomcat 部署war 包和 IDEA 两种跑法部署要么把项目打成 war 丢进 Tomcat 的 webapps 目录要么在 IDEA 里配置 Tomcat 运行。毕业设计建议用 IDEA 方式调试时能看到报错堆栈改动页面后不必手动重启。步骤是这样的打开项目把 Project Structure 里的 Project SDK 设置为 1.8Language level 设为 8。Project Structure → Artifacts → 加号 → Web Application: Exploded名字保持 campus_shop。Run → Edit Configurations → 添加 Tomcat Server → LocalTomcat 路径选本地 8.5 安装目录。Deployment 页签添加刚才的 ArtifactApplication context 填 /campus_shop。启动后访问 http://localhost:8080/campus_shop。Application context 每次启动都会被 Tomcat 当作请求前缀我习惯固定成 /campus_shop不写 / 也不用带中文的目录名否则后面涉及 Cookie 作用域或图片虚拟路径时会多出很多坑。如果用 war 包方式把 IDEA 打好的 war 复制到 Tomcat/webapps启动后会自动解压访问路径就是 war 文件名。war 包里不能套一层同名目录否则会访问 /campus_shop/campus_shop/index.jsp 才能进首页。如果页面 404先看 Tomcat 的 webapps 目录下解压出来的结构是否合理再回 IDEA 检查 Artifact 输出。3.5 页面全 404 时先查这三个地方输入地址后页面直接 404我按顺序查三处第一Tomcat 的 webapps 下是否有解压出来的项目目录目录名和 context 是否一致第二IDEA 的 Artifact 输出目录是否包含 jsp 和 WEB-INF第三web.xml 里的 welcome-file 是不是 index.jsp以及 index.jsp 是否真的在 webapp 根目录。很多 JSP 项目默认欢迎页不是 index.jsp而是 list.jsp 或 main.jsp直接访问根路径 404 不代表项目坏了改成访问具体页面即可。拿到源码先跑通跑通之后每一处 404 都对应一个明确的配置问题。数据库配置不在这三个检查项里如果页面能打开但数据加载失败才需要回看 3.3 的连接参数。4. 核心业务代码走读登录、图片上传、购物车与订单事务跑通环境只是开始答辩和改需求都在代码里。这一章挑四个最核心的功能点走读登录、发布商品、购物车、下单。看懂这四个点整个系统的业务逻辑就通了。4.1 登录请求转发和重定向的分工登录是所有功能的前提。JSP 项目最常见的做法是 LoginServlet 接收表单提交查库把用户对象放进 session。下面是简化后的核心代码实际工程里会多几行非空校验但逻辑一样。WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { req.setAttribute(msg, 用户名或密码不能为空); req.getRequestDispatcher(login.jsp).forward(req, resp); return; } User loginUser new UserDao().findByUsernameAndPassword(username, password); if (loginUser null) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); return; } HttpSession session req.getSession(); session.setAttribute(loginUser, loginUser); session.setMaxInactiveInterval(30 * 60); // 30分钟无操作失效 resp.sendRedirect(req.getContextPath() /index.jsp); } }这段代码有三点值得看。第一失败用 forward成功用 sendRedirectforward 可以把错误信息带回登录页sendRedirect 会重新发起一次 GET避免用户刷新页面时重复提交表单。第二session 存的是 User 对象而不是用户名因为发布商品页要显示昵称、后台要读 role 字段判断管理员只存字符串后面还得再查一次库。第三setMaxInactiveInterval(30 * 60) 把无操作超时设成 30 分钟校园机房里这个时长刚好。密码在这里是明文比对源码初始状态大多如此。答辩时不要说这是安全设计直接承认这是待改进点然后按第六章的方法改成 MD5 加盐反而能变成加分项。4.2 发布商品图片上传不能写死真实路径商品发布是二手交易系统里最容易翻车的功能难点在图片上传。JSP 表单一旦带上 enctypemultipart/form-datarequest.getParameter 就拿不到普通字段了必须用专门组件解析。这套源码大多用 Apache Commons FileUpload核心逻辑如下WebServlet(/publish) public class PublishServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { User user (User) req.getSession().getAttribute(loginUser); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } DiskFileItemFactory factory new DiskFileItemFactory(); ServletFileUpload upload new ServletFileUpload(factory); upload.setFileSizeMax(2 * 1024 * 1024); // 单张图片最大 2MB upload.setSizeMax(5 * 1024 * 1024); // 整个请求最大 5MB String title null, priceText null, categoryId null, description null, image null; try { ListFileItem items upload.parseRequest(req); for (FileItem item : items) { if (item.isFormField()) { switch (item.getFieldName()) { case title: title item.getString(UTF-8); break; case price: priceText item.getString(UTF-8); break; case categoryId: categoryId item.getString(UTF-8); break; case description: description item.getString(UTF-8); break; } } else if (item.getName() ! null !item.getName().isEmpty()) { String ext item.getName().contains(.) ? item.getName().substring(item.getName().lastIndexOf(.)) : .jpg; String imageName UUID.randomUUID().toString().replace(-, ) ext; String realPath getServletContext().getRealPath(/upload); File dir new File(realPath); if (!dir.exists()) { dir.mkdirs(); } item.write(new File(dir, imageName)); image upload/ imageName; } } new GoodDao().insertGood(user.getId(), title, description, new BigDecimal(priceText), Integer.parseInt(categoryId), image); resp.sendRedirect(req.getContextPath() /goods_list.jsp); } catch (Exception e) { throw new ServletException(商品发布失败 e.getMessage(), e); } } }这里的两个参数很关键setFileSizeMax(2 * 1024 * 1024) 限制单张图片 2MBsetSizeMax(5 * 1024 * 1024) 限制整个请求 5MB能挡掉恶意大文件。图片名用 UUID 拼原始扩展名是为了避免同一目录下文件名冲突否则两个人各传一张“手机照片.jpg”就会互相覆盖。getRealPath(/upload) 返回的是 webapp/upload 目录本地调试没问题但 war 部署时它指向 Tomcat 临时解压目录重启可能被清掉这个坑第五章会专门处理。注意表单字段的取值全部用 item.getString(UTF-8)因为 multipart 请求里 req.getParameter 拿不到值而且默认编码可能不是 UTF-8。这里少写一个编码参数中文标题就会乱码。4.3 购物车用 Session代码最短但最稳二手交易不需要和电商比购物车复杂度。一份源码里如果给购物车建表存数据库反而把问题放大并发更新、脏数据、登录态失效全来了。课程设计阶段购物车存 session 完全够结构是一个 Map商品ID, 数量。WebServlet(/addCart) public class AddCartServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int goodsId Integer.parseInt(req.getParameter(goodsId)); HttpSession session req.getSession(); MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new LinkedHashMap(); session.setAttribute(cart, cart); } // merge商品已存在则数量1不存在则放入并初始化为1 cart.merge(goodsId, 1, Integer::sum); resp.sendRedirect(req.getContextPath() /cart.jsp); } }cart.merge(goodsId, 1, Integer::sum) 是 Java 8 的写法商品第一次加入时数量是 1已存在时加 1。如果项目编译级别低于 Java 8这里会编译失败改成三行 if 判断效果一样。购物车页面要遍历这个 Map根据 key 回查商品表再把价格乘数量合计。这里特意用 LinkedHashMap 而不是 HashMap因为 LinkedHashMap 保持插入顺序页面展示时商品不会乱跳。有人会问购物车不落库关闭浏览器就丢行不行对课程设计来说行。答辩时你甚至可以主动说“session 购物车是为了减少无意义写库只有确认下单才强制落库”这种取舍比为了炫技而建表更好讲。购物车本来就不是交易系统的核心数据订单才是。4.4 下单事务两张表要一起成功如果只是单表 insertJDBC 写起来很简单。但二手交易系统下单一定会改两张表orders 插一条记录goods.status 从 0 改成 1。这两步之间如果程序崩溃会出现订单有了但商品还挂着卖的情况。解决办法是把两步包进一个事务。Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启手动事务 String orderNo String.valueOf(System.currentTimeMillis()) String.format(%03d, new Random().nextInt(1000)); PreparedStatement ps1 conn.prepareStatement( INSERT INTO orders(order_no, buyer_id, seller_id, goods_id, goods_title, price) VALUES(?,?,?,?,?,?)); ps1.setString(1, orderNo); // 其余参数按 buyerId、sellerId、goodsId、goodsTitle、price 依次设置 ps1.executeUpdate(); PreparedStatement ps2 conn.prepareStatement( UPDATE goods SET status1 WHERE id? AND status0); ps2.setInt(1, goodsId); if (ps2.executeUpdate() ! 1) { throw new RuntimeException(商品已被他人购买); } conn.commit(); } catch (Exception e) { if (conn ! null) { conn.rollback(); // 订单和商品状态一起回滚 } throw new ServletException(下单失败 e.getMessage(), e); } finally { DBUtil.close(conn); }setAutoCommit(false) 表示开始手动事务两次更新之间如果 UPDATE 影响行数不是 1说明商品已经被别人买走直接抛异常catch 块里 rollback 会撤销刚才插入的订单保证两个表状态一致finally 里统一关连接。事务粒度只包住两个更新不要把前面的查询也包进来否则连接占用时间变长Tomcat 默认连接池只有 20 个连接并发一高就开始排队。用 UPDATE ... WHERE id? AND status0 而不是直接 UPDATE goods SET status1是防超卖的关键两条请求同时下单同一件商品时只有第一条能把 status 从 0 改成 1第二条 UPDATE 影响行数为 0事务回滚商品不会重复卖出。这个细节值得在答辩时单独讲一句。5. 避坑JSPMySQL 项目最常见的五个翻车场景下面这五个问题是我复现同类源码时踩过的高频坑每条按“现象 → 原因 → 解决”写。提前看完能省下大半天排错时间。5.1 启动报错Could not create connection to database server现象项目启动后访问任意页面Tomcat 日志出现 Could not create connection to database server 或 Access denied for user rootlocalhost。原因数据库连接参数和实际环境对不上。最常见的是 root 密码写错、数据库名拼错、驱动 jar 没在 classpath。很多源码自带的是作者机器上的密码比如 123456你本地是 abc123直接启动必然拒绝访问。解决先不要翻代码直接在命令行用配置里的账号密码连一次库mysql -u root -p能连上再改 db.properties 或数据库工具类连不上说明凭据就是错的。连上后把 jdbc.url 里的库名、username、driver 类名三行打印出来核对。如果报 ClassNotFoundException: com.mysql.jdbc.Driver那是驱动 jar 没挂到项目里和密码错误是两回事别混着排查。5.2 MySQL 8.0 下时区报错或连接被重置现象本地装的 MySQL 8.0用源码自带的 5.x 配置启动报 Communications link failure或者 The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。原因MySQL 8.0 改了驱动类名和默认时区要求。老驱动 com.mysql.jdbc.Driver 在 8.0 中已被弃用连接 URL 里缺少 serverTimezone 参数服务端返回的时区字段无法被 Java 序列化连接建立阶段就断了。解决驱动 jar 换成 mysql-connector-java-8.0.x配置按第三章 3.3 的 MySQL 8.0 版本改url 里加 serverTimezoneAsia/Shanghai。如果你只加 timezone 参数而不换驱动会继续报驱动类不兼容。不要试图改 MySQL 的全局时区来将就代码改连接参数比改服务器配置影响面小得多而且换一台电脑复现时别人不需要跟着改 MySQL。5.3 Tomcat 10 下大量 500ClassNotFoundException javax.servlet.http.HttpServlet现象源码在别人机器上跑得好好的你装了个最新版 Tomcat 10启动后所有 Servlet 路径全 500日志提示 ClassNotFoundException: javax.servlet.http.HttpServlet。原因Tomcat 10 的 Servlet 包从 javax.* 迁到了 jakarta.*老项目里 import javax.servlet 在编译和运行时都找不到类。这不是代码 bug而是服务器版本和源码使用的 API 版本不匹配。解决把 Tomcat 换回 8.5 或 9.0。如果老师指定必须用 Tomcat 10那就得全局把所有 javax.servlet 替换为 jakarta.servlet工作量不大但容易漏而且部分老 taglib 也要跟着变。对毕业设计来说换 Tomcat 版本是最快的后悔药不要和框架版本较劲。5.4 中文全变问号请求、响应、数据库三层都要查现象注册页填写中文昵称提交后后台和数据库里全是 ??或者 JSP 页面显示正常但搜索中文商品搜不到。原因字符集在四个环节断了链。JSP 的 pageEncoding 没设 UTF-8、Servlet 里没调 request.setCharacterEncoding、连接 URL 少了 characterEncodingutf8、数据库表用了 latin1。只要一个环节不对中文就乱。解决按这条链检查JSP 头部 contentTypetext/html; charsetUTF-8pageEncodingUTF-8每个 Servlet 处理 POST 请求第一行写 request.setCharacterEncoding(UTF-8)连接 URL 带 useUnicodetruecharacterEncodingutf8数据库建表用 utf8mb4。GET 请求的乱码比较隐蔽Tomcat 8 默认 URIEncoding 已经是 UTF-8但老项目如果改过 server.xml就得回头确认一遍。按完三步还乱码别继续调代码先重启 Tomcat有可能是 IDE 用 GBK 保存过 JSP 文件文件本身坏了。5.5 上传图片重启后全部 404现象商品发布后图片能显示重启 Tomcat 或重新打包 war 后再访问 /upload/xxx.jpg 就 404数据库里的 image 字段还指向上一次路径。原因getRealPath(/upload) 返回的是应用目录开发环境下可能是 target/exploded编译或重新部署时旧文件被清掉war 包模式下每次启动都是从压缩包解压上传的图根本不在包里自然会被覆盖或找不到。解决图片不要放应用内改成磁盘固定目录比如 D:/campus_upload再让 Tomcat 把 /upload/** 映射到这个目录。用 IDEA 就在 Deployment 里加 External Source用手动部署就改 conf/server.xml 加一个 ContextdocBase 指向 D:/campus_uploadpath 指向 /campus_shop/upload。这样重新部署只影响代码不影响用户传过的图。答辩前最好把图片目录和数据库脚本一起拷到 U 盘避免换机器演示时连环 404。6. 验证与改造答辩前两小时能做完的检查清单和加分项跑通只是第一步答辩前两小时我会按下面这张链路表走三遍。每一遍都用同一个新注册账号不能拿管理员账号混过去。6.1 完整业务链路验证清单步骤操作预期结果注册注册一个普通用户自动登录或跳转成功user 表多一条记录登录新账号登录首页右上角显示昵称发布发布一件带图片的商品列表页出现该商品图片可显示搜索按关键字搜索商品列表被过滤购物车加入两件商品购物车数量合计正确下单提交订单订单表新增记录商品状态变已售后台管理员登录能看到用户和订单数据重复下单再对同一商品下单给出“已售出”提示没有新订单这张表跑完功能闭环就成立了。中途任何一步失败顺着第五章的对应场景去查不要开着一堆浏览器标签到处试。每次失败都要在 MySQL 里看一眼数据确认是页面问题还是数据根本没落库。6.2 三个低成本改造性价比最高的三个点第一个改 PreparedStatement。如果源码里还有字符串拼 SQL把参数全部改成 ? 占位这是安全类问题答辩命中率最高。String sql SELECT * FROM user WHERE username? AND password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password);第二个改密码存储用 MD5 加盐。这个改动只涉及注册和登录两个 Servlet范围可控但讲解价值很高。String salt UUID.randomUUID().toString().substring(0, 8); String digest DigestUtils.md5Hex(salt password); // 数据库存 salt 和 digest登录时先查盐再计算哈希比对第三个改图片虚拟路径方法在第五章 5.5。把上传目录挪到应用外既是稳定性的加分项也能证明你理解 Web 容器对静态资源的管理。这三个改动合计不超过两小时比新加一个整页模块划算得多。从那以后我每次拿到 JSPMySQL 的源码都会先花十分钟确认三件事数据库脚本能原样跑通、Tomcat 不是 10、上传目录没有写在临时解压路径里。这套校园二手交易系统里最常见的坑基本都写在上面了按这个顺序复现你打开源码后可以直接进入业务逻辑而不是先被环境问题磨掉耐心。希望帮到你。本文还有配套的精品资源点击获取