简介:基于 JavaWeb 的蛋糕店网站系统源码,是一套面向 Java 课程设计、毕业设计与期末大作业场景的完整可运行项目,直接解决了学生缺乏高质量参考项目的痛点,适合作为课设模板或毕业设计蓝本。压缩包共 408 个文件,含 58 个 JSP 页面、55 个 Java 源码、18 个 jar 依赖库和 1 个 SQL 数据库脚本,并配有 CSS、JS 及大量商品图片素材;JSP 负责页面展示,Java/class 处理业务逻辑,SQL 用于初始化数据,整体仅 22.02MB,结构清晰便于导入部署。项目已获导师指导并通过 97 分高分,覆盖用户注册登录、商品展示、购物车、订单管理等典型电商模块,后台维护功能完整,代码按实体类、Dao、Service、Servlet 分层,适合初学者理解 JavaWeb 分层开发与数据库操作。下载后无需修改即可运行,可直接作为课程设计文档、答辩演示或二次开发基线,尤其适合需要提交完整项目的毕业生与在校生,目前已有 1740 人浏览学习。
1. 这个蛋糕店网站系统到底能跑通什么:课程设计的边界与价值
下载一个名为“基于javaweb的蛋糕店网站系统源码(课程设计).zip”的包,是很多计算机专业学生在课程设计阶段的常规操作。这个zip解压之后,通常不是一份需要复杂框架支撑的生产级系统,而是一套把 JSP、Servlet、JDBC、MySQL 这些 JavaWeb 核心知识点串起来的完整案例,覆盖用户注册登录、蛋糕分类展示、购物车、提交订单、后台管理订单状态这一整条业务链路。它最适合两类人:一是正在做课程设计或毕业设计、需要快速拿到一个能跑通且答辩讲得清楚的项目的大学生;二是刚学完 JavaWeb 基础、希望用一个完整案例把零散知识点连成体系的自学者。这里先说一个反直觉的结论:这类课程设计拿高分的关键,不在于用了多新的技术栈,而在于你能不能把一次完整的请求从浏览器到 Servlet 再到数据库再返回页面的链路讲明白,并且保证演示时不翻车。下面我就按“解压 zip → 配置环境 → 初始化数据库 → 读核心代码 → 排错 → 答辩准备”的顺序把这个方案讲透。
2. JavaWeb 课程设计的技术栈拆解:Servlet/JSP + MySQL 的方案为什么现在还值得做
2.1 技术选型:为什么课程设计还在用 JSP + Servlet 而不是 Spring Boot
你可能会想,现在外面找工作都要求 Spring Boot,为什么课程设计还在用 JSP + Servlet 这种“老古董”。我的看法是,课程设计的考核点本来就不在框架熟练度,而在基础知识的掌握程度。一个典型的 JavaWeb 课程设计评分表上,几乎一定会有这些条目:HTTP 请求处理流程、Servlet 生命周期、Session 会话管理、JDBC 数据库操作、MVC 分层思想。用 JSP + Servlet,这些知识点全部裸露在代码里,你能直接看到请求从哪里进来、Servlet 如何被实例化、Session 什么时候创建、JDBC 的 Connection 怎么打开和关闭。
Spring Boot 的问题在于封装得太干净了。内嵌 Tomcat、自动配置数据源、MyBatis 帮你把 SQL 结果映射成对象,整个链路对新手来说是黑匣子。答辩时老师如果问一句“你的 Session 是怎么失效的”“请求是从哪一步进到 Controller 的”,基础不扎实的人很容易答不上来。所以我给学生的建议一直是:课程设计阶段老老实实用 JSP + Servlet + JDBC,把每个环节都摸一遍,以后学框架会快得多。
这个蛋糕店网站系统就是典型的这种结构:浏览器发起请求 → JSP 页面展示 → Servlet 接收参数和处理跳转 → DAO 层用 JDBC 访问 MySQL → 数据回填到 JSP 渲染。没有 Spring,没有 MyBatis,没有 Maven 第三方依赖管理,全部用原生 javax.servlet 和 java.sql 完成。这恰恰是它适合当课程设计基线的原因,每一层都能单独拿出来讲。
2.2 源码包里的典型目录结构和运行入口
解压 zip 之后,你会看到一个标准的 Web 项目目录布局。不同作者写的包具体命名会有差异,但核心骨架通常长这样:
cake-shop/ ├── src/ │ ├── com/cake/entity/ # 实体类:User, Cake, Order, OrderItem │ ├── com/cake/dao/ # 数据访问层:UserDao, CakeDao, OrderDao │ ├── com/cake/servlet/ # 控制器:LoginServlet, RegisterServlet, CartServlet │ ├── com/cake/util/ # 工具类:DBUtil 数据库连接 │ ├── com/cake/filter/ # 过滤器:登录拦截 LoginFilter │ └── com/cake/service/ # 部分版本会有 Service 业务层 ├── web/ │ ├── index.jsp # 蛋糕列表主页 │ ├── login.jsp # 用户登录页 │ ├── register.jsp # 用户注册页 │ ├── cart.jsp # 购物车页面 │ ├── order.jsp # 订单确认页 │ ├── WEB-INF/ │ │ ├── web.xml # Web 部署描述文件 │ │ └── lib/ # mysql-connector-java 驱动 jar │ ├── css/ │ ├── js/ │ ├── images/ │ └── admin/ # 后台管理页面 └── sql/ └── cake_shop.sql # 建库建表 + 预设数据这里有几个关键点。web.xml是 Servlet 3.0 之前的项目入口,负责声明 Servlet 映射、Filter 过滤器和欢迎页;如果源码用了@WebServlet注解,web.xml 里可能只有少量配置,甚至只有一个空壳。WEB-INF/lib目录里必须有 MySQL 驱动 jar,否则运行时会直接报ClassNotFoundException,这是后面最常见的翻车点。sql目录下的建库脚本决定了你能不能看到蛋糕图片和测试账号,也是整个项目能否跑起来的前提。
2.3 系统功能模块与数据表设计
在动手部署前,先把业务模块和表结构看懂,这样后面改代码、答辩都是顺手的事。这个蛋糕店系统的功能模块一般可以分成两张表:
| 端 | 功能模块 | 核心操作 |
|---|---|---|
| 用户端 | 注册/登录 | 用户名密码校验、Session 记录登录状态 |
| 用户端 | 蛋糕浏览 | 首页展示蛋糕列表、按分类筛选 |
| 用户端 | 购物车 | 加入购物车、修改数量、删除商品 |
| 用户端 | 订单 | 确认订单、提交订单、查看我的订单 |
| 后台 | 管理员登录 | 与用户表区分或单独 admin 表 |
| 后台 | 商品管理 | 新增蛋糕、编辑价格库存、上下架 |
| 后台 | 订单管理 | 查看订单、修改订单状态(待付款/已发货/已完成) |
配套的数据库表一般就是下面这四张核心表,SQL 脚本长这样:
CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, nickname VARCHAR(50), phone VARCHAR(20), address VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE cake ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, category VARCHAR(20), price DECIMAL(10,2) NOT NULL, image VARCHAR(100), stock INT DEFAULT 100, status TINYINT DEFAULT 1 ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, cake_id INT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL );为什么要这样设计?几点经验:金额字段一定要用DECIMAL(10,2),不要用double,否则浮点精度问题会在算总价时暴露,答辩现场演示出金额差一分钱非常尴尬。订单状态status用 TINYINT 数字比字符串好维护,0 代表待付款、1 已付款、2 已发货、3 已完成、4 已取消,这就是一个简单的状态机。还有一点是表名orders加了个 s,因为order是 SQL 的保留字,裸写会报语法错误,很多新手第一次跑 SQL 脚本失败就栽在这里。
3. 把 zip 包变成能跑的网站:IDEA + Tomcat + MySQL 完整部署步骤
3.1 环境准备:JDK / Tomcat / MySQL 版本选型与 MySQL zip 安装
拿到源码 zip 后先别急着解压,先把本机环境对齐。这个项目的年代感决定了它对环境有一定挑剔,最稳妥的推荐组合是:JDK 1.8 + Tomcat 8.5 + MySQL 5.7 + IDEA 2019 到 2023 之间的版本。如果你本机装的是 JDK 11 或 JDK 17,部分老项目在 JSP 编译阶段会碰到模块限制或兼容问题,能跑但折腾。MySQL 8.0 也能用,但连接串要额外处理,我会在第 5 章避坑里专门讲。
如果你还没装 MySQL,并且拿到的是 zip 免安装版而不是 exe 安装版,操作流程是这样的:把 zip 解压到D:\mysql-5.7.40,然后在根目录新建一个my.ini:
[mysqld] basedir=D:/mysql-5.7.40 datadir=D:/mysql-5.7.40/data port=3306 character-set-server=utf8mb4 default-storage-engine=INNODB [client] default-character-set=utf8mb4接着以管理员身份打开 cmd,进入解压目录执行:
mysqld --initialize-insecure mysqld install net start mysql解释一下这三条命令。--initialize-insecure是初始化数据目录,同时生成一个密码为空的 root 用户,方便第一次登录;mysqld install是把 MySQL 注册成 Windows 服务,这样开机自动启动;net start mysql是手动启动服务。注意my.ini里的basedir和datadir必须用反斜杠或正斜杠写清楚,路径错了服务启动会闪退。初始化成功后,登录命令是mysql -u root -p,密码直接回车跳过。
3.2 导入项目与配置 Tomcat:IDEA 里从解压到跑通的完整操作
环境就绪后,开始把 zip 包变成能运行的 Web 项目。整个过程我拆成四步,每一步都有明确的验证点。
第一步,解压 zip 并用 IDEA 打开。注意选择File -> Open,选中解压后的文件夹,而不是新建项目。如果源码里有.iml文件或.idea目录,IDEA 会直接识别;如果没有,IDEA 会把它当普通 Java 项目打开,需要手动添加 Web 支持。
第二步,配置 Project Structure。按Ctrl+Alt+Shift+S进入项目结构,左侧选 Modules,在 src 目录上右键选择 Sources,把它标成源码根目录;然后点左上角的加号添加 Web 模块,把 Web 资源目录指向web目录,web.xml 路径指向web/WEB-INF/web.xml。这个动作的意思是告诉 IDEA:“这是一个 Web 项目,JSP 和静态资源从这个目录发布。”
第三步,配置 Artifacts。Project Structure 左侧选 Artifacts,点加号选择Web Application: Exploded,在弹出的界面里把web目录作为 Web Resource 目录。为什么要用 Exploded 而不是 war 包?因为 Exploded 模式直接把编译后的类和页面发布到 Tomcat 的部署目录,改完 JSP 刷新浏览器就能看到效果,不用每次重新打 war 包。这一点在课程设计调试阶段特别省时间。
第四步,配置运行环境。点 IDEA 右上角的 Add Configuration,加一个 Tomcat Server -> Local,在 Server 标签页里选本地 Tomcat 安装目录,然后在 Deployment 标签页里点加号选择刚才的 Artifacts,Application context 填/cake。最后把 Server 标签页的 URL 改成http://localhost:8080/cake/。这个 context path 就是将来访问项目的根路径,后面所有页面链接都依赖这个值,填错了会出现 404。
3.3 初始化数据库:建库脚本、连接配置和测试数据
项目配置好后,数据库的初始化是关键。用命令行或 Navicat 连接到本机 MySQL,运行sql/cake_shop.sql:
mysql -u root -p < sql/cake_shop.sql这个脚本会完成三件事:创建cake_shop数据库、新建四张业务表、插入几条蛋糕测试数据和测试账号。执行完可以再查一遍确认:
USE cake_shop; SHOW TABLES; SELECT * FROM cake;如果看到 cake 表里有数据,说明建库成功。接着改项目的数据库连接配置。大多数源码会把连接信息写在一个DBUtil.java工具类里,结构长这样:
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/cake_shop?useSSL=false&characterEncoding=UTF-8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; public static Connection getConnection() throws SQLException { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new SQLException("找不到数据库驱动", e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }这里最常改动的三个参数是:数据库名cake_shop、用户名USER、密码PASSWORD。注意 URL 里的characterEncoding=UTF-8一定不能删,否则后面中文乱码能折腾你一晚上。改完之后,启动 Tomcat,访问http://localhost:8080/cake/,看到蛋糕列表页就说明整个链路已经通了。
4. 核心代码怎么读、怎么改:从用户登录到订单下单的链路
4.1 登录与 Session 控制:Filter 拦截器与密码校验
系统跑通之后,接下来要做的不是急着改功能,而是把核心代码读通。以登录为例,这是课程设计必问的环节。最常见的实现是一个LoginServlet接收 POST 请求,用 DAO 查询数据库校验用户名密码,通过后写入 Session:
@WebServlet("/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); UserDao dao = new UserDao(); User user = dao.findByUsernameAndPassword(username, password); if (user != null) { // 登录成功:把用户对象放进 Session,标记登录状态 HttpSession session = req.getSession(); session.setAttribute("loginUser", user); resp.sendRedirect("index.jsp"); } else { // 登录失败:回显错误信息,转发回登录页 req.setAttribute("msg", "用户名或密码不正确"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } }这里要重点理解sendRedirect和forward的区别。sendRedirect是重定向,浏览器地址栏会变成index.jsp,相当于客户端重新发起一次新的请求,所以 Session 里的数据能跨请求保持;forward是服务端转发,地址栏不变,常用于把错误信息带回同一个页面。很多新手在这两种跳转方式之间混用,导致页面刷新后重复提交订单,后面订单模块我会再提到。
Session 默认 30 分钟没有操作就会失效。如果想控制这个时间,可以在web.xml里配置:
<session-config> <session-timeout>30</session-timeout> </session-config>只有登录校验还不够,系统里还有购物车、下单、后台管理等页面,不能让用户绕过登录直接访问。这时候需要一个过滤器统一拦截。常见写法如下:
@WebFilter("/*") public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(false); String path = request.getRequestURI(); // 放行不需要登录的路径:登录页、注册、主页、静态资源 if (path.endsWith("login.jsp") || path.contains("/login") || path.contains("/register") || path.endsWith("index.jsp") || path.contains("/css/") || path.contains("/images/")) { chain.doFilter(req, resp); return; } // 未登录用户跳回登录页 if (session == null || session.getAttribute("loginUser") == null) { response.sendRedirect("login.jsp"); return; } chain.doFilter(req, resp); } }这段代码最值得学习的是白名单的思路。过滤器拦截的是所有请求,但不等于所有请求都要登录。比如 css、图片、登录页本身如果被拦了,就会出现“页面排版全乱了”“用户永远跳不回登录页”的怪问题。判断路径字符串的方式虽然朴素,但在课程设计阶段够用且好讲。
4.2 蛋糕列表与购物车:JSP + Servlet + JDBC 的交互方式
主页展示蛋糕列表是用户看到的第一屏,代码链路一般是CakeListServlet从数据库查出列表,放进 request 域,转发到index.jsp用 JSTL 标签循环渲染。Servlet 端的核心逻辑:
@WebServlet("/cakeList") public class CakeListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String category = req.getParameter("category"); CakeDao dao = new CakeDao(); List<Cake> cakeList = dao.findByCategory(category); req.setAttribute("cakeList", cakeList); req.getRequestDispatcher("index.jsp").forward(req, resp); } }页面端用c:forEach遍历,避免在 JSP 里写 Java 小块脚本:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:forEach items="${cakeList}" var="cake"> <div class="cake-card"> <img src="${cake.image}" alt="${cake.name}"> <h3>${cake.name}</h3> <p>价格:¥${cake.price}</p> <a href="cart?action=add&cakeId=${cake.id}">加入购物车</a> </div> </c:forEach>这个action=add&cakeId=的 URL 写法是在 Servlet 里做业务分支的经典方式。也就是说CartServlet的doGet会先获取action参数,再决定调用哪种操作:
String action = req.getParameter("action"); if ("add".equals(action)) { // 加入购物车 } else if ("update".equals(action)) { // 修改数量 } else if ("clear".equals(action)) { // 清空购物车 }购物车数据不落地到数据库,而是放在 Session 里,用一个 Map 结构保存,key 是蛋糕 id,value 是数量。课程设计阶段这样实现完全没问题,因为购物车是临时状态,用户关掉浏览器就没了,没必要建表存。如果你发现源码里用的是 List 而不是 Map,也没关系,逻辑等价,只是修改数量的时候需要遍历查找。理解这一点,答辩时就能解释“购物车为什么能跨页面同步,是因为 Session 在同一个浏览器会话里是全局的”。
4.3 下单与订单管理:事务处理和状态字段设计
下单是整个系统里唯一需要认真对待事务的模块。用户点击提交订单,后端要完成的操作不是一条 SQL,而是一串动作:从 Session 里读出购物车、算出总金额、在orders表插入一条主记录、拿到新订单 id、再循环把每个商品插入order_item表、最后更新蛋糕库存。这一串环节里任何一步失败,都会产生脏数据。
常见的正确写法是手动开启事务:
Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关掉自动提交,手动控制事务 // 1. 插入订单主记录,获取生成的订单 id // 2. 遍历购物车,插入 order_item 明细 // 3. 更新 cake 表库存:stock = stock - quantity conn.commit(); // 全部成功,统一提交 } catch (SQLException e) { if (conn != null) { conn.rollback(); // 任何一步异常,全部回滚 } throw e; } finally { DBUtil.close(conn); }这里的关键是setAutoCommit(false)。默认情况下每条 SQL 执行完都会自动提交,如果第 2 步插入明细失败,第 1 步的订单头已经写进去了,数据库里就会多出一条没有明细的“幽灵订单”。开启事务后,要么全部成功,要么全部回滚,这是课程设计里很容易被忽视但答辩老师很爱问的考点。
订单状态字段的设计在前面表结构里已经写过,0 待付款、1 已付款、2 已发货、3 已完成。后台管理员的职责就是把这个数字往上涨一格,同时记录修改时间。整个状态机没有分支逻辑,就是一个顺序推进,简单清晰。如果你想做得漂亮一点,可以在orders表里加一个update_time字段,每次状态变更都刷新当前时间,这样后台里能看到每单走到哪一步、什么时候变的。
5. 课程设计最常见翻车现场:部署运行的 6 个高频报错与排查
5.1 ClassNotFoundException: com.mysql.jdbc.Driver
现象:Tomcat 启动不报错,但一访问查询页面就抛出ClassNotFoundException: com.mysql.jdbc.Driver,或者页面直接 500。
原因:MySQL 驱动 jar 没有进入 Web 应用的运行时目录。很多新手在 IDEA 里通过 Project Structure 把 jar 添加到了 Libraries,但那只是编译期可见,发布到 Tomcat 时并不会自动复制进WEB-INF/lib。运行时 Tomcat 只在WEB-INF/lib里找驱动类。
解决:手动把mysql-connector-java-5.1.x.jar复制到项目的web/WEB-INF/lib目录下,然后在 IDEA 里执行Build -> Rebuild Project,再重启 Tomcat。注意如果你用的是 MySQL 8.0,驱动应换成 connector/J 8.x,驱动类名可以写成com.mysql.cj.jdbc.Driver,老代码里的com.mysql.jdbc.Driver也能兼容,但会在日志里打一大段过时警告。
5.2 页面全是问号或乱码
现象:商品名称、用户昵称在页面显示成???,或者浏览器里是乱码。这个坑我在课程设计时踩过一晚上,后来发现就是编码层没对齐。
原因:JavaWeb 里的中文要经过四道关卡:JSP 文件本身的编码、Servlet 接收请求时的编码、数据库表字符集、JDBC 连接串的编码参数。四层里任何一层不一致,中文就会在某个环节变成问号。
解决:一层层检查。JSP 页面头部确认pageEncoding="UTF-8";每个 Servlet 的doPost和doGet开头加上req.setCharacterEncoding("UTF-8");建表 SQL 指定DEFAULT CHARSET=utf8mb4;JDBC URL 带characterEncoding=UTF-8。这里最容易被忽略的是 GET 请求,Tomcat 8.5 默认对 GET 的 URI 编码是 UTF-8,但如果你用的是老版本 Tomcat,GET 传到后端的中文也会乱,需要在 server.xml 里给 Connector 加上URIEncoding="UTF-8"。
5.3 Tomcat 端口被占用
现象:点启动按钮后 IDEA 弹窗报Address already in use: JVM_Bind,或者控制台里写着Port 8080 was already in use。
原因:8080 端口被其他进程占用。可能是之前调试时 Tomcat 没被完全关闭,也可能是其他软件占用,常见的有 Nacos、其他开发服务器、甚至某次异常退出留下的孤儿进程。
解决:Windows 下打开 cmd 执行netstat -ano | findstr 8080,看到最后一列的 PID 后,执行taskkill /F /PID 进程号结束它。如果这个端口确实被其他重要程序占用,直接把 IDEA 里 Tomcat 配置的 HTTP port 改成 8081,记得访问地址也要同步改成http://localhost:8081/cake/。还有一个细节:IDEA 里配置 Tomcat 时有个JMX port,也可能被占用,报错信息里端口号不同,处理方式一样。
5.4 Tomcat 能启动但访问 404
现象:Tomcat 正常启动,控制台没有报错,但浏览器访问http://localhost:8080/是 404。
原因:绝大多数情况是 Application context 配置和访问路径对不上。如果 Deployment 里设置的 context path 是/cake,那么访问根路径http://localhost:8080/自然没有应用。还有一种是页面里的链接写死成了绝对路径,比如<a href="/index.jsp">,这个路径会被解析成http://localhost:8080/index.jsp,而不是http://localhost:8080/cake/index.jsp,于是 404。
解决:先确认 IDEA 里 Deployment 标签页的 Application context 是/cake,访问http://localhost:8080/cake/看是否正常。然后打开页面源码,检查所有链接和表单 action,改成相对路径,或者用${pageContext.request.contextPath}拼接。比如:
<a href="${pageContext.request.contextPath}/cakeList">蛋糕列表</a>这样即使 later 改了 context path,页面链接也不会失效。
5.5 MySQL 8.0 连接失败与时区错误
现象:本机装的是 MySQL 8.0,跑老项目时启动报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,或者连接时提示Public Key Retrieval is not allowed。
原因:MySQL 8.0 的驱动和连接协议跟 5.x 不一样,老连接串缺少serverTimezone参数,驱动无法识别本机时区。日志里那段乱码其实是 GBK 编码的中文“中国标准时间”,表示系统时区识别出了问题。Public Key Retrieval is not allowed是因为 MySQL 8.0 默认认证插件是caching_sha2_password,客户端第一次连接需要向服务器索取公钥,连接串里没允许这个行为。
解决:把 JDBC URL 改成完整版:
jdbc:mysql://localhost:3306/cake_shop?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai如果不想改代码,也可以在 MySQL 里把账号的认证插件改回mysql_native_password:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;不过我还是建议直接改连接串,因为这是通用解法,换台电脑重新部署也不怕。
5.6 刷新页面重复下单
现象:演示时用户提交订单成功后习惯性按了一下 F5,结果数据库里出现了两笔一模一样的订单,客户被扣了两次钱。
原因:提交订单用的是 POST 请求,浏览器在刷新页面时会重新提交上一次的 POST 数据。如果 Servlet 没有做防重复提交处理,同样的请求会再执行一次完整的下单逻辑。
解决:最简单的方案是遵循 PRG 模式,下单成功后不用forward转发到订单成功页,而是用sendRedirect重定向到一个订单查询页面。重定向之后浏览器地址栏变成一个新的 GET 请求,刷新时只会刷新查询页,不会重复触发下单。如果想更严谨,可以在 Session 里存一个一次性 token,下单前比对并清除,这是后话,课程设计阶段改用重定向已经足够,讲原理时还能额外加分。
6. 答辩前你要做的三个验证与改造:让课程设计从「能跑」到「能讲」
系统能稳定跑起来之后,离高分还差一口气。这一口气就是把“能跑”变成“能讲”。我建议你在答辩前做三件事。
第一件事,准备一条完整的演示链路,并按顺序走两遍。我的顺序是:注册新用户 → 登录 → 浏览蛋糕列表 → 按分类筛选 → 加入购物车 → 改购物车数量 → 提交订单 → 退出登录 → 用管理员账号登录后台 → 把订单状态从待付款改成已发货。整个过程清空浏览器缓存走一遍,确保每一步的跳转地址都正确。这里有个血泪教训:演示时最怕的是临时输入,提前把测试账号和相关数据准备好,会比现场打字从容很多。
第二件事,在关键 Servlet 里加一行简单的日志输出。不需要引入日志框架,System.out.println就够用。比如在LoginServlet里打印接收到的用户名、查询到的用户 id,在OrderServlet里打印订单金额和订单 id。答辩时你讲到某个环节,控制台正好打出对应的请求参数和 SQL 结果,这个“看得见的链路”比任何流程图都有说服力。但注意演示前把没用的日志清掉,不要整个控制台全是无关输出。
第三件事,挑两个安全相关的点做小改造,这是答辩老师最喜欢追问的方向。第一个是密码不能明文存储,至少要做 MD5 加盐。改造起来很简单,UserDao里查用户时改用PreparedStatement的占位符,密码存储和校验改成加盐后的散列值。第二个是防 SQL 注入,如果源码里的 DAO 是用字符串拼接 SQL,一定要改成预编译:
String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password);对比直接拼接"WHERE username = '" + username + "'",预编译不仅防注入,查询性能也更好,这两点答辩时都可以展开讲。
讲到最后说一句我自己的经验:当年我做课程设计时,最崩溃的不是功能写不出来,而是明明代码和教程一模一样,页面就是乱码。后来才发现不过是 JSP 头部少了一行编码声明。从那以后我做任何 JavaWeb 项目,第一件事永远是先统一编码和数据库连接串,再谈功能。很多你现在觉得玄学的问题,追到根上都是这种基础配置没对齐。希望这篇笔记能帮你把 zip 包里的项目顺顺利利跑起来,答辩时讲到每个模块都有底气。
本文还有配套的精品资源,点击获取