JavaWeb网上书店系统源码+数据库导入运行避坑指南与核心代码解析 简介这是一套基于JavaWeb的网上书店系统期末大作业项目源码面向计算机相关专业学生适合作为课程设计或结课作业参考。项目覆盖图书展示、分类浏览、购物车、订单管理、用户登录注册等常见电商业务模块可帮助理解JavaWeb分层开发、JSPServlet交互及数据库设计思路。压缩包共108个文件含31个Java后台类、29个JSP页面、13个XML配置、1个SQL数据库脚本以及配套CSS样式、JS脚本和图片素材整体大小6.75MB目录结构清晰。项目已获导师指导并通过为97分高分作品代码完整能展现前后端交互到数据持久化的典型流程。目前已有1709人学习下载适合需快速完成课程设计或系统学习电商类JavaWeb项目的读者。1. JavaWeb 网上书店系统源码加数据库期末大作业最常见的“标配答案”JavaWeb 网上书店系统再配一份初始化好的数据库脚本这基本是期末大作业压缩包里最经典的形态。它不像企业级系统那样复杂但功能粒度刚好卡在课程考核范围内前台注册登录、图书列表、图书详情、购物车、下单结算后台分类管理、图书维护、订单状态更新。整个闭环把 Servlet、JSP、JDBC、MySQL 这些课程核心串在一起老师想看的点它都有代码量又克制在个人能掌握的范围内。这类压缩包适合两类人一类是时间紧、需要一个可靠骨架而不是从零造轮子的期末选手另一类是已经把“单表 CRUD”写熟了、想看看一个完整项目怎么把多个模块串起来的学习者。它的价值不是代码写得多漂亮而是能让你在两周内得到一条从数据库到页面的完整链路。不过我得先说句泼冷水的话解压不等于能跑。我见过不少同学卡在导入报错、数据库连不上、页面 500 这些环节最后反而比从零写还耗时。下面的内容就从“拿到 zip 之后先看什么”开始把导入、配库、核心代码路径、常见坑一次讲透。2. 从 zip 到能演示先确认项目形态再决定导入方式拿到压缩包后第一件事不是立刻在 IDE 里双击打开而是先确认它属于哪一类 JavaWeb 工程。我一般会看压缩包根目录有没有 pom.xml有说明是 Maven 工程依赖可以从中央仓库拉取没有则多半是传统 Eclipse Web 工程依赖都堆在 WEB-INF/lib 目录里。这两种项目导入 IDEA 的方式完全不同提前分清能省下后面大半天的排错时间。按“源码数据库期末大作业”这类包的惯例绝大多数不是 Maven 工程。这不算坏消息反而意味着数据库脚本、lib 目录、WebContent 结构都是齐全的只要本地环境匹配就能跑。真正麻烦的是环境不匹配源码是 JDK7 写的本机只有 JDK17数据库脚本是 MySQL5.7 导出的本机装的是 MySQL8代码默认数据库账号密码不对。这些是后面避坑章的主题这里只讲怎么把项目形态认准。2.1 先看目录结构pom.xml 和 WebContent 决定导入姿势一个典型的非 Maven 期末项目目录结构长这样项目根目录 ├── src │ ├── com/xxx/controller │ ├── com/xxx/dao │ ├── com/xxx/entity │ ├── com/xxx/filter │ └── com/xxx/util ├── WebContent │ ├── admin │ ├── css │ ├── images │ ├── WEB-INF │ │ ├── lib │ │ └── web.xml │ └── index.jsp └── bookstore.sqlsrc 是源码WebContent 是 Web 根目录WEB-INF/lib 里放的是项目运行必须的 jar 包。如果看到的是 src/main/java 和 src/main/webapp那就是 Maven 工程。两种形态的导入路径不一样Maven 工程在 IDEA 里以“Maven Project”方式打开等右下角依赖下载完成后会自动识别非 Maven 工程要用 Open 选项目根目录如果 IDE 没识别成 Web 项目就右键项目选 Add Framework Support把 Web Application 勾上。导入之后还要确认两件事Project Structure 里的 Web Resource Directory 是否指向 WebContent非 Maven或 src/main/webappMavenArtifacts 里是否生成了 war exploded 而不是带后缀的压缩包。war exploded 的意思是展开目录运行改 JSP 不用重新打包IDE 里 F9 后刷新页面就能看到效果调试体验比整包 war 好很多。Eclipse 用户则是 File Import Existing Projects into Workspace前提是压缩包里的 .project 和 .classpath 没被删。2.2 课程考核视角下的技术选型为什么 JSPServlet 仍是主流经常有人问都到期末了为什么不用 Spring Boot我的观点很直接你选型之前要先想明白这门课的考核点。期末大作业答辩时老师追问的通常是课堂里出现过的概念请求生命周期、Session 原理、Filter 的作用、JDBC 预编译、事务边界。一套 JSPServletJDBC 的项目任何一点都能直接从代码里指出来讲。如果直接拿 Spring Boot 项目来改功能确实更“现代”但老师问“Servlet 容器是什么、web.xml 里配置了什么、请求怎么被分发到 Servlet”时你很难用 Boot 的自动配置讲清楚。而且 Spring Boot 项目肉眼可见地与课程内容有距离答辩时容易被当成“网上找的高级代码”反而不讨好。Spring Boot 当然不是不能用前提是你自己能把注解原理讲到老师满意但这花费的时间比改一套 JSP 项目多得多。选型还要看资源可维护性。存量最大、最容易找到同类参考的也是 JSPServlet 项目报错信息在搜索引擎里一搜就有大量匹配结果换机器、换数据库版本时能自己排查。抱着“最简单可靠”的原则我建议把 JSPServlet 项目先跑通有余力再谈升级。2.3 本地跑起来的最小步骤数据库脚本先行先把数据库脚本导入再启动项目。顺序不要反否则前端一打开就报 SQL 错误。导入这一步用命令行最稳避免图形客户端隐藏了字符集问题# 用 UTF-8 字符集登录并导入数据库脚本 mysql -uroot -p --default-character-setutf8 bookstore.sql # 启动 TomcatWindows 下对应 startup.bat cd apache-tomcat-8/bin ./startup.sh # 检查 8080 端口是否被监听 curl http://localhost:8080/如果用的是 IDEA 内置 Tomcat不需要手动执行 startup但数据库还是要先导。导入完成后别急着点运行先把 JDBC 连接串确认好打开源代码里的 db.properties 或 DBUtil.java把 jdbc.url、username、password 改成你本机的值库名必须和 SQL 脚本里的库名完全一致。一个典型连接串是这样jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/bookstore_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password你的密码这里的参数每一个都有作用useSSLfalse 是为了避免本地开发时握手警告serverTimezoneAsia/Shanghai 解决 MySQL8 驱动与本地时区不一致的报错characterEncodingutf8 保证中文字符正常存取。改完配置后启动访问地址一般是 http://localhost:8080/项目上下文路径/上下文路径在 IDEA 的 Artifact 名称里默认跟项目名相同。3. 数据库设计与初始化5 张基础表撑起书店系统的完整闭环网上书店的数据库脚本是整份资源里最容易被忽略、又最能体现完成度的部分。老师很难细看每一行代码但打开数据库看一眼表结构就知道你有没有完整的前后台数据闭环。一个基础的书店系统最少需要 5 张表用户表、分类表、图书表、订单表、订单明细表。如果购物车要求“退出登录后购物车仍在”再加一张购物车表就行。下面这个建表结构覆盖了绝大多数期末大作业的规格也适用于你拿到包之后核对脚本是否完整。3.1 表结构拆解用户、分类、图书、订单、订单明细CREATE DATABASE IF NOT EXISTS bookstore_db DEFAULT CHARACTER SET utf8mb4; USE bookstore_db; -- 用户表username 唯一 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 保存MD5摘要, real_name VARCHAR(30), phone VARCHAR(20), address VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 分类表 CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, sort_order INT DEFAULT 0 COMMENT 越小越靠前 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 图书表 CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, title VARCHAR(120) NOT NULL, author VARCHAR(50), price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, sales INT DEFAULT 0, cover VARCHAR(255) COMMENT 封面相对路径, description TEXT, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_book_cat FOREIGN KEY (category_id) REFERENCES t_category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单主表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 业务订单号, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成 4已取消, receiver_name VARCHAR(50), receiver_phone VARCHAR(20), receiver_address VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES t_user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单明细表 CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, book_id INT NOT NULL, book_title VARCHAR(120) COMMENT 下单时冗余商品名, book_price DECIMAL(10,2) COMMENT 下单时成交价, quantity INT NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES t_order(id), CONSTRAINT fk_item_book FOREIGN KEY (book_id) REFERENCES t_book(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 里的关键设计意图是订单明细中冗余了 book_title 和 book_price而不是下单时去关联查询图书表。因为图书价格和书名可能会改历史订单必须保留下单那一刻的快照。如果没有冗余字段老师把一本图书的价格改掉你的历史订单金额就对不上了。这属于数据库设计里的快照思维答辩时能主动讲出来是明显加分项。3.2 字段设计上的三个边界价格精度、库存、外键第一个边界是金额字段。价格、成交价、订单总额全部用 DECIMAL(10,2)不许用 FLOAT 或 DOUBLE。二进制浮点数存 0.1 会有误差金额算错是网上书店项目最不能接受的低级问题。如果脚本里出现了 float price建议你动手改成 DECIMAL。第二个边界是库存和商品状态。t_book 里既要 status 表示上下架也要 stock 表示库存量。前端“图书列表”只展示 status1 的商品下单时先判断 stock 是否足够再执行 UPDATE t_book SET stock stock - 数量。这里的库存更新要直接写在 SQL 里不要先 SELECT 出来在 Java 中算完再 UPDATE那在并发场景会出问题。期末虽然不要求高并发但这个写法体现了你对数据库并发的理解。第三个边界是外键。期末项目建议保留外键理由很简单老师看 ER 图和表关系时直观而且单机演示不会有性能压力。但如果脚本里没有外键你也不用主动补勉强加外键反而可能导致导入时因插入顺序不对而失败。金额和状态字段上加 COMMENT 也是一个好习惯脚本可读性会高很多。3.3 数据库脚本导入命令行与客户端两种方式导入脚本最稳的是命令行mysql -uroot -p source D:/work/bookstore.sql;也可以不进入交互模式直接用重定向mysql -uroot -p --default-character-setutf8 bookstore.sql如果偏好图形工具用 Navicat 或 SQLyog 右键运行 SQL 文件即可但执行前要把连接字符集设为 utf8mb4不然中文字段注释在客户端显示乱码。不管哪种方式导入完成后都要执行 SHOW TABLES; 确认表都建出来了。这里有一个容易被忽略的坑很多压缩包里的 SQL 脚本并没有写 CREATE DATABASE 和 USE只有 CREATE TABLE。如果你直接执行表会被建到 MySQL 默认库里而项目连接串写的却是 bookstore_db启动后就会报“表不存在”。解决方法是先手动创建一个同名库再在脚本开头补一行 USE bookstore_db;。反过来如果脚本里有 CREATE DATABASE 而本地已经有同名库执行时会报错或者覆盖原有数据导入前要看清楚。另一个现象是导入时提示“Table already exists”。原因通常是脚本开头没有 DROP TABLE IF EXISTS你重复执行了。处理方式不是去改脚本而是先 DROP 掉旧表再重新 source。期末演示前我建议养成一个习惯用一台干净的库执行一次完整导入保证脚本可以从零重放。4. 核心代码路径登录、购物车、下单这条线怎么串起来数据表建好后整个项目的灵魂在三条链路登录态怎么维持、购物车怎么存、下单怎么保证原子性。这三个问题也是老师最爱在答辩时追问的。很多同学的代码能跑但让他讲“为什么购物车要放 Session 不放数据库”就卡住了。这一章把三条链路的关键代码写出来并说明每个选择背后的理由。4.1 登录接口预编译查询、MD5 与 Session 状态登录逻辑我一般这样写protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 密码做 MD5 后再比对 String encrypted MD5Util.md5(password); User user userDao.findByUsernameAndPassword(username, encrypted); if (user null) { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } HttpSession session req.getSession(); session.setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /index); }这里有两个关键点。第一SQL 查询必须用 PreparedStatement 占位符不能字符串拼接否则登录接口会成为 SQL 注入的入口。第二用户表里存的是 MD5 摘要而不是明文密码比对时把输入密码也做 MD5。MD5 本身不算安全加密但期末项目里“存摘要”这个意识比算法强度更重要老师追问时你可以主动说“生产环境应该换成 BCrypt”。登录成功后把 User 对象放进 Session后续 JSP 页面用${sessionScope.loginUser.username}显示当前用户。后面的请求要判断“是否已登录”不能在每个 Servlet 里重复写判断用一个 Filter 统一拦public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); if (session ! null session.getAttribute(loginUser) ! null) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() /login.jsp); } } }在 web.xml 里给这个 Filter 配置需要保护的 URL 前缀比如 /order/、/cart/。注意一定要放行静态资源和登录页面本身否则 CSS、图片全被拦下来页面样式会丢。这个问题我在避坑章里专门讲。4.2 购物车Session 里存 Map 还是落数据库表购物车有两种实现方式。一种是纯 Session把购物车对象存在会话里操作简单、响应快但用户清浏览器缓存或换设备就丢失。另一种是数据库表每次加购都写 t_cart_item登录后在任意设备都能看到购物车但代码量和查库次数都增加。期末场景我强烈建议用 Session 实现理由很实际演示时购物车在同一个浏览器会话里操作不需要“换设备不丢”这种偏离课程重点的能力。Session 方案的核心就是一个简单的购物车对象public class Cart implements Serializable { private MapInteger, Integer items new HashMap(); public void addItem(int bookId, int quantity) { items.put(bookId, items.getOrDefault(bookId, 0) quantity); } public void removeItem(int bookId) { items.remove(bookId); } public MapInteger, Integer getItems() { return items; } public void clear() { items.clear(); } }Map 的键是图书 ID值是数量。加入购物车的 Servlet 里这样操作HttpSession session req.getSession(); Cart cart (Cart) session.getAttribute(cart); if (cart null) { cart new Cart(); session.setAttribute(cart, cart); } cart.addItem(bookId, 1); resp.sendRedirect(req.getContextPath() /cart.jsp);注意 session 里取出来要判空。购物车实现 Serializable 是为了支持 Session 持久化虽然本地 Tomcat 不一定用到但写上了老师会觉得你考虑过会话钝化问题。刷新购物车页面时根据 Map 里的 ID 批量查图书信息并按价格和数量计算总价。如果原来的包把购物车做进了数据库表也能跑。你需要处理的是合并逻辑登录时把临时 Session 购物车合并进数据库取出时再合并回 Session。这个复杂度对期末来说不值当除非你的评分标准里明确写了“跨设备购物车同步”。4.3 下单事务订单主表与明细表的原子性下单是整个项目里最需要讲清楚事务的环节。用户点击“提交订单”后要做三件事写订单主表、写订单明细表、扣减库存。这三件事必须同生共死不能订单主表写成功、明细写失败否则会出现金额对不上的脏数据。public boolean createOrder(int userId, String receiverName, String receiverPhone, String receiverAddress, MapInteger, Integer items) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启手动事务 // 1. 插入订单主表返回自增主键 String orderNo System.currentTimeMillis() (int)(Math.random() * 1000); double total 0; // 先根据 items 里的 bookId 查出价格算出 total String insertOrder INSERT INTO t_order(order_no, user_id, total_amount, status, receiver_name, receiver_phone, receiver_address) VALUES(?,?,?,0,?,?,?); PreparedStatement ps conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); ps.setString(1, orderNo); ps.setInt(2, userId); ps.setDouble(3, total); // ... set 收货人信息 ps.executeUpdate(); ResultSet keys ps.getGeneratedKeys(); keys.next(); int orderId keys.getInt(1); // 2. 循环插入订单明细 String insertItem INSERT INTO t_order_item(order_id, book_id, book_title, book_price, quantity) VALUES(?,?,?,?,?); // 3. 扣减库存 String updateStock UPDATE t_book SET stock stock - ? WHERE id ? AND stock ?; conn.commit(); // 全部成功才提交 return true; } catch (Exception e) { if (conn ! null) { conn.rollback(); // 任何一个步骤失败都回滚 } return false; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }这段代码的核心是 setAutoCommit(false)、commit、rollback 三个动作。默认情况下 JDBC 每次 executeUpdate 都自动提交一旦第一条 SQL 成功后面的失败也没法撤销。把自动提交关掉后所有操作打包成一个原子单元。PS 参数里没有把生成订单号的逻辑写全实际项目中注意订单号要保证唯一时间戳加三位随机数的做法在单机演示够用但并发下可能撞车。如果你对唯一性有更高要求可以用 UUID 去掉横线或者用数据库序列。扣减库存的 SQL 里加上AND stock ?是一个小技巧如果库存不足影响行数为 0说明扣减失败直接抛出异常触发回滚。这比先查库存再 UPDATE 更安全也省去一次查询。答辩时提到这一句老师会知道你考虑过并发。5. 导入运行避坑指南5 个最常见的翻车现场与排查顺序这一章全是实操里反复出现的坑。我把它们按出现频率排序每条按“现象 → 原因 → 解决”写清楚。如果你在导入项目时卡住了先对照这一章排查大概率能解决。5.1 项目导入后一片红Tomcat 列表里没有可用模块现象IDEA 里项目目录的代码文件全是小红叉右键运行时 Tomcat Server 没有可用 Artifact页面提示“No artifacts marked”或“Web application not found”。原因非 Maven 项目从压缩包解压后IDEA 没有识别出 Web 根目录也没有把 Tomcat 的 servlet-api.jar 加到依赖里。很多包原本是 Eclipse 工程导入 IDEA 时缺少 .idea 配置需要手动补全。解决右键项目根目录选 Add Framework Support勾选 Web Application。然后在 File Project Structure Modules 里把 Web Resource Directory 指向 WebContent 目录或 src/main/webapp在 Dependencies 里添加 Tomcat 提供的 servlet-api.jar作用域选 provided。这样 IOC 报错和 be 小红叉都会消失。Eclipse 用户检查 Project Facets 是否勾了 Dynamic Web Module。5.2 JDBC 连接 MySQL 8 报“Public Key Retrieval is not allowed”现象Tomcat 启动没报错但点登录或打开首页时报 SQLNonTransientConnectionException错误信息包含“Public Key Retrieval is not allowed”或“Could not create connection to database server”。原因MySQL8 的驱动默认要求客户端与服务端之间处理公钥检索而连接串没加允许参数。老项目里用的是 MySQL5 连接驱动和5.x版本的权限方式换到 MySQL8 环境就翻车。解决把连接 URL 改成jdbc:mysql://localhost:3306/bookstore_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue并把 WEB-INF/lib 里的驱动 jar 换成 mysql-connector-java 8.x 版本。如果换了驱动还是报错检查 MySQL 用户是否有远程访问权限本地开发一般执行GRANT ALL ON bookstore_db.* TO rootlocalhost。5.3 中文乱码页面正常但数据库里是问号现象前端 JSP 显示中文正常但注册的用户名、图书名称存进数据库后变成“???”或者反过来JSP 页面直接显示乱码。原因三层编码不统一。第一层是数据库表字符集不是 utf8mb4第二层是 JDBC 连接串没指定 characterEncodingutf8第三层是 HTTP 请求体里的中文没有经过 UTF-8 过滤器。解决建库建表统一 utf8mb4连接串加上characterEncodingutf8在 web.xml 里配置一个 CharacterEncodingFilter把 request.setCharacterEncoding(UTF-8) 和 response.setContentType(text/html;charsetUTF-8) 一起做掉。特别注意JSP 页面顶部要有% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %否则页面本身就以默认编码渲染。乱码是三天两头复现的玄学问题排查顺序永远是“页面编码 → 请求过滤器 → 数据库字符集 → 连接串”。5.4 登录后 CSS/JS 全部丢失页面点跳转 404现象登录前首页样式是好的登录成功跳转后页面变成纯 HTMLCSS 和图片全部加载不出来。有的点击“图书详情”会 404。原因资源引用用了相对路径。登录前你 URL 是/index.jsp相对路径解析到根目录登录跳转后 URL 可能变成/user/center.jsp浏览器会去/user/css/style.css找资源找不到就返回 404。解决所有静态资源引用都改成绝对路径JSP 里用 EL 表达式拼上下文路径link relstylesheet href${pageContext.request.contextPath}/css/style.css a href${pageContext.request.contextPath}/bookServlet?actiondetailid${book.id}详情/aServlet 里重定向也别写/index.jsp要写resp.sendRedirect(req.getContextPath() /index)。Filter 拦截时则要放行 css/images/js 前缀否则即使路径对了也会被登录拦截器挡掉。5.5 数据库导入成功系统却说表不存在或“用户名或密码错误”现象SQL 脚本执行成功、SHOW TABLES 能看到表Tomcat 也正常启动但输入页面上的管理员账号却登录不进去或者某个页面报“Table xxx.t_user doesnt exist”。原因第一脚本里没有 USE 指定库名表建到了别的库里项目连接的是 bookstore_db里面是空的。第二管理员账号不是不存在是密码字段里存的摘要和代码里比对方式不一致比如代码用的是明文比对数据库里存的是 MD5或反之。解决先执行SELECT DATABASE();确认当前库是不是项目连接串里写的那个。如果库对不上用USE bookstore_db;切库后重新 source。账号问题更好排查直接查数据库里 t_user 的 password 字段看它是一段 32 位十六进制字符串还是明文。如果是摘要确认登录 Servlet 里比对前有没有做 MD5如果是明文要么把 admin 密码改成明文要么把代码里加密那一行去掉。演示前我最推荐的做法是手动执行一段 UPDATE 把管理员密码改回和代码算法一致的值。6. 演示前的 6 步自检清单与两个值得做的升级方向项目能跑只是及格线演示不翻车才是目标。我给自己定过一条规矩上机演示前不用“刚才还能跑”当理由必须重新走一遍完整流程。具体是六步在干净数据库上重新执行一遍 SQL 脚本确认从零可导入不是靠之前手动改过的小动作在撑。用 IDEA 的 Clean 清掉旧输出重新 Build 项目启动后访问首页确认没有控制台红色报错。注册一个新账号密码里带中文和特殊字符验证编码过滤器和校验逻辑都正常。走一遍完整下单流程清空购物车后加入至少三本图书结算时核对订单总额和明细金额一致。打开后台把一本书的库存改成 0 或下架回前台看这本书是否从列表消失。关掉所有浏览器窗口重新打开确认登录态需要重新验证Session 没有异常。这六步里最容易翻车的是第一步。我当年做类似项目时吃过最大的亏就是演示前临时觉得数据不干净直接手动删了几张表结果 SQL 脚本里的外键关联导致重建失败现场五分钟没登进去。后来所有演示都坚持“完整重放一遍脚本”这个习惯反而再没出过问题。如果这套骨架你已经跑得很熟还有两个值得做的升级方向。一是把项目 Maven 化把 lib 目录里的 jar 包换成 pom.xml 依赖再逐步把 web.xml 里的 Servlet 配置改成注解。Maven 化之后换机器不用再手动拷 jar项目结构也更接近真实开发环境。二是把散落在 Servlet 里的 JDBC 代码抽成 DAO 层和 Service 层让 Controller 只做参数接收和页面跳转。这个升级不改变页面表现但老师追问“你怎么理解 MVC”时你能直接指着代码说出每一层职责答辩底气会完全不同。希望帮到你。本文还有配套的精品资源点击获取