简介:这是一份基于JavaWeb的蛋糕店网站系统课程设计源码,适合正在准备毕业设计、课程设计或期末大作业的Java学习者直接参考。项目已获导师指导并以97分高分通过,代码完整、配置齐全,下载解压后即可部署运行。整个资源包共408个文件,大小约22MB,涵盖55个Java源码、58个JSP页面、110个编译后的class文件,以及数据库SQL脚本、XML配置文件、jar依赖库和前端CSS、JS、图片等素材,能清晰看到从商品展示、购物车到订单处理与后台管理的完整业务脉络。已有1740人学习下载,说明这套源码在同类项目中具有较好的参考价值。对于需要快速上手JavaWeb开发、理解Servlet+JSP分层设计或完成类似商城系统的同学,这份源码可以帮助节省大量搭建时间,并提供一个结构规范、可直接运行的真实项目范本。
1. 蛋糕店网站系统:一个 JavaWeb 课程设计到底考你什么
基于 JavaWeb 的蛋糕店网站系统源码,是软件工程、数据库课程设计里出现频率极高的一类题目——前台展示蛋糕、用户注册登录、下单,后台管商品和订单。很多同学下载了课程设计源码包,以为解压就能跑,结果卡在环境配置上大半天。这篇就按做这种课程设计最常见的路子,把技术选型、数据库设计、核心功能和踩坑点完整过一遍,让新手能照着搭起来,熟手也能用来查漏补缺。
2. 拿到课题先定技术栈:Servlet/JSP + MySQL 为什么是课程设计的最优解
2.1 课程设计选型的第一原则:能答辩、能跑通、能讲明白
课程设计和生产项目是两码事。生产项目要性能、要扩展性,课程设计要的是逻辑完整、能演示、能回答老师的追问。常见做法是 Servlet + JSP + MySQL,不用 SSM、Spring Boot 这类框架,原因很实在:框架封装太多,答辩时被问「请求进来之后发生了什么」,说不清楚反而扣分;而 Servlet + JSP 每一行代码都能对应到 HTTP 请求的生命周期,老师一听就知道你是真写了。
这里要注意,如果课程任务书上明确写了要用 Spring Boot,那还是按任务书来。但标题写「基于 JavaWeb」的课程设计,绝大多数对应的是 JavaWeb 课程本身,Servlet/JSP 才是考核重点,这也和网上的 JavaWeb 教学案例、黑马 JavaWeb 笔记里的项目模板保持一致。遇到难题时,至少能找到大量可参考的资料,不至于一个人闷头踩坑。
选型还要考虑部署环境。实验室机器上装 Tomcat 8.5 + JDK 1.8 + MySQL 5.7 是最稳的组合,版本搭配合理比追求新版重要得多。另外,既然课题叫「网站系统」,前端页面至少要做得像样,JSP + CSS + 少量 JavaScript 就够了,不必为了炫技引 Vue。除非你非常熟悉它,否则答辩时老师顺着前端框架追问下去,很容易把自己绕进去。
2.2 Maven 目录结构与三层包结构:把骨架搭起来
用 IDEA 创建 JavaWeb 项目时,我一般选 Maven 骨架,而不是直接建一个普通的 Web 项目。原因有两个:Maven 管理 jar 包,不用手动往 WEB-INF/lib 里塞驱动和连接池;项目结构规范,代码分到 entity、dao、service、servlet、util 五个包,老师打开项目扫一眼就能看出三层架构的意图。这一步相当于把骨架立起来,后面所有功能都是往骨架里填肉。
一个典型的 Maven 结构长这样:
cake-shop/ ├── pom.xml ├── src/main/java/com/cake │ ├── entity # 实体类:User、Cake、Category、Orders │ ├── dao # 数据访问层:接口 + 实现类 │ ├── service # 业务层:登录校验、购物车、下单 │ ├── servlet # 控制层:接收请求、调用业务、转发页面 │ └── util # 工具类:JdbcUtils、字符串工具 ├── src/main/resources │ └── druid.properties └── src/main/webapp ├── admin # 后台管理页面 ├── css / js / images └── 登录、注册、首页、购物车等 JSP对应的 pom.xml 精简版:
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>pom.xml 里值得注意两个参数:servlet-api 和 jsp-api 的 scope 是 provided,因为 Tomcat 本身自带这些 jar,不加 provided 的话,打包时容易把容器类打进去,运行时不一定会立刻报错,但 IDE 里经常出现奇怪的冲突。mysql-connector-java 的版本必须和 MySQL 对应:MySQL 5.7 配 5.1.49,MySQL 8.0 配 8.0.x,混着用最常见的报错是「Communications link failure」或者驱动类找不到。网上流传的 JavaWeb 项目完整案例(MySQL 版),绝大多数也是用这组依赖跑通的。
一个实体类的写法,以 Cake 为例:
package com.cake.entity; public class Cake { private Integer id; private Integer categoryId; private String name; private Double price; private String image; private String description; private Integer stock; // getter / setter 省略,IDEA 按 Alt+Insert 自动生成 }实体类的字段要和数据库表字段一一对应。很多同学在这一步偷懒,把数据库查出来的数据塞进 Map,结果后面取数全靠魔法字符串,代码又丑又容易写错。课程设计阶段,老老实实写实体类,是性价比最高的习惯。到了答辩环节,老师问「你的数据是怎么从数据库到页面的」,你能清晰说出实体类、DAO、Servlet、JSP 这条链,基本功这一项就稳了。
2.3 数据库建模:蛋糕店核心的 5 张表
蛋糕店系统虽然叫「网站」,核心数据模型并不复杂。常见做法是设计 5 张表:用户表、蛋糕分类表、蛋糕表、购物车表、订单表。完整版电商系统会把订单和订单明细拆成两张表,但课程设计里合成一张也能讲通,关键是状态字段要留好。
| 表名 | 核心字段 | 作用 |
|---|---|---|
| user | id, username, password, phone, is_admin | 用户和后台管理员共用,is_admin 区分角色 |
| category | id, name, sort | 蛋糕分类,首页导航和商品列表都要用 |
| cake | id, category_id, name, price, image, description, stock | 商品主体,价格用 DECIMAL 不用 DOUBLE |
| cart | id, user_id, cake_id, quantity | 购物车,user 和 cake 的多对多关联表 |
| orders | id, user_id, order_no, total_price, status, create_time | 订单主表,status 表示待支付、已支付、已发货 |
这里有一个容易被忽略的点:cake 表的 image 字段只存相对路径,不存图片的二进制内容。图片文件放 webapp 的 upload 目录或外部磁盘目录,数据库记路径。这样数据库体积小,备份迁移也方便。订单表的 order_no 要生成唯一业务单号,最省事的方案是时间戳加用户 ID 拼接,不要拿自增主键当订单号展示给用户,这是软件工程课程设计里老师爱问的一个细节。
数据库的字符集统一用 utf8mb4,不要用 utf8。原因很直接:utf8 在 MySQL 里最多存 3 字节,一些生僻字和表情符号存不进去,页面一提交就报「Incorrect string value」。这类问题排查起来很浪费时间,不如建库时一步到位。
3. 把蛋糕店系统跑起来:从建库到前后台联调的最小闭环
3.1 MySQL 建库脚本与初始数据
课程设计里的蛋糕店项目,数据库部分通常单独占一块评分,建库脚本放在项目根目录的 sql 文件夹里,比临时导出一份要专业。打开 Navicat 或者命令行,连接本地 MySQL 实例后执行下面的脚本。
CREATE DATABASE IF NOT EXISTS cake_shop DEFAULT CHARACTER SET utf8mb4; USE cake_shop; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, phone VARCHAR(20), is_admin TINYINT DEFAULT 0 COMMENT '0-普通用户 1-管理员' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, sort INT DEFAULT 0 COMMENT '排序,小的在前' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE cake ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, image VARCHAR(255), description TEXT, stock INT DEFAULT 100, FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, cake_id INT NOT NULL, quantity INT DEFAULT 1, UNIQUE KEY uk_user_cake (user_id, cake_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, order_no VARCHAR(32) NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT '0-待支付 1-已支付 2-已发货 3-已完成', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO category (name, sort) VALUES ('生日蛋糕', 1), ('慕斯蛋糕', 2), ('小甜点', 3); INSERT INTO cake (category_id, name, price, image, description, stock) VALUES (1, '草莓奶油蛋糕', 168.00, 'upload/strawberry.jpg', '新鲜草莓加动物奶油', 50), (1, '巧克力黑森林', 198.00, 'upload/chocolate.jpg', '巧克力碎与樱桃夹心', 30), (2, '芒果慕斯', 88.00, 'upload/mango.jpg', '芒果泥配戚风底', 40);建表时把外键和唯一约束一起建好,比在代码里维护数据一致性要靠谱。cart 表上加了唯一索引 uk_user_cake,同一种蛋糕重复加入购物车时,在 SQL 层就避免了产生两行重复数据,代码里只需要处理「存在就加数量、不存在就插入」的逻辑。orders 表用 CURRENT_TIMESTAMP 当默认值,插入时不用手动传 create_time,少一个出错点。
提示:执行脚本前先确认当前连接的是自己本地建的库,别一不小心把别人共享实例里的表删了。课程设计阶段吃过这种亏的人不在少数。
3.2 配置 Druid 连接池与 JDBC 工具类
数据库连接不能每次都新建,课程设计里用 Druid 连接池是常见做法。配置文件放在 src/main/resources 下,类加载时会自动读到。
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/cake_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username=root password=123456 initialSize=5 maxActive=10 maxWait=3000然后写一个 JdbcUtils 工具类,所有 DAO 都从这里拿连接:
package com.cake.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class JdbcUtils { private static DataSource dataSource; static { try (InputStream in = JdbcUtils.class.getClassLoader() .getResourceAsStream("druid.properties")) { Properties props = new Properties(); props.load(in); dataSource = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt != null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} } } }三个参数要特别留意。driverClassName 取决于 mysql-connector 版本:5.x 用 com.mysql.jdbc.Driver,8.x 用 com.mysql.cj.jdbc.Driver。url 里 characterEncoding 直接决定中文能否正常入库;serverTimezone 是 MySQL 8.x 必须带的,不加会按服务器默认时区解析时间,直接报错。Druid 的 maxWait 表示获取连接的最大等待毫秒数,设太短在高并发下容易拿不到连接,课程设计场景 3000 足够。
下方是 DAO 层的一个完整示例,用于查询所有蛋糕:
package com.cake.dao; import com.cake.entity.Cake; import com.cake.util.JdbcUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; public class CakeDao { public List<Cake> findAll() { String sql = "SELECT id, category_id, name, price, image, description, stock FROM cake"; List<Cake> list = new ArrayList<>(); try (Connection conn = JdbcUtils.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Cake cake = new Cake(); cake.setId(rs.getInt("id")); cake.setCategoryId(rs.getInt("category_id")); cake.setName(rs.getString("name")); cake.setPrice(rs.getDouble("price")); cake.setImage(rs.getString("image")); cake.setDescription(rs.getString("description")); cake.setStock(rs.getInt("stock")); list.add(cake); } } catch (Exception e) { throw new RuntimeException(e); } return list; } }Java 7 的 try-with-resources 写法会自动关闭 PreparedStatement 和 ResultSet,连接池场景下 conn.close() 的实际动作是归还连接而不是真正关闭。这里强调一个规范:永远用 PreparedStatement 而不是 Statement。它既能防 SQL 注入,又不用手动拼接引号,是课程设计代码里最值得坚持的习惯。
3.3 用户登录与 Session 校验:前后台权限分离
蛋糕店网站分前台用户和后台管理员,最简单有效的角色区分方式,是在 user 表加一个 is_admin 字段。登录成功后把整个 User 对象放进 Session,后续页面从 Session 拿人,再判断 is_admin 决定能不能进后台。
package com.cake.servlet; import com.cake.entity.User; import com.cake.service.UserService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserService(); @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"); User user = userService.login(username, password); if (user == null) { req.setAttribute("error", "用户名或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); return; } HttpSession session = req.getSession(); session.setAttribute("loginUser", user); if (user.isAdmin()) { resp.sendRedirect(req.getContextPath() + "/admin/index"); } else { resp.sendRedirect(req.getContextPath() + "/cake/list"); } } }setCharacterEncoding 必须在读取请求参数之前调用,放在 doPost 第一行是最稳妥的。登录失败用 forward 转发到登录页并带一个 error 字段,JSP 里用${error}就能显示提示;登录成功用 sendRedirect 重定向,避免刷新页面时重复提交表单,这是 PRG 模式最基本的应用。
后台接口用 Filter 统一拦截,比在每个 Servlet 里复制粘贴权限判断要优雅得多:
package com.cake.filter; import com.cake.entity.User; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebFilter("/admin/*") public class AdminFilter 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); User loginUser = null; if (session != null) { loginUser = (User) session.getAttribute("loginUser"); } if (loginUser != null && loginUser.isAdmin()) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() + "/login.jsp"); } } }注意 getSession(false) 和 getSession() 的区别:前者拿不到 Session 时返回 null,不会强制创建新 Session。在过滤器里用 getSession(false) 是正确写法,否则每个未登录的拦截请求都会白白创建一个 Session 对象,这是低质量代码的典型特征。过滤器本身又是一个得分点,因为它说明了「横切逻辑」为什么要从业务代码里抽出来。
3.4 蛋糕列表与购物车:把页面和数据串起来
前台首页的核心是蛋糕列表。Servlet 查出数据,放到 request 域,转发给 JSP,JSP 用 JSTL 循环渲染。这条链路短,但它是整个 JavaWeb 课程的缩影。
package com.cake.servlet; import com.cake.entity.Cake; import com.cake.service.CakeService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @WebServlet("/cake/list") public class CakeListServlet extends HttpServlet { private CakeService cakeService = new CakeService(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<Cake> cakeList = cakeService.listAll(); req.setAttribute("cakeList", cakeList); req.getRequestDispatcher("/cake_list.jsp").forward(req, resp); } }对应的 JSP 片段:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <div class="cake-grid"> <c:forEach items="${cakeList}" var="cake"> <div class="cake-card"> <img src="${pageContext.request.contextPath}/${cake.image}" alt="${cake.name}"> <h3>${cake.name}</h3> <p class="price">¥${cake.price}</p> <a href="${pageContext.request.contextPath}/cart/add?cakeId=${cake.id}" class="btn">加入购物车</a> </div> </c:forEach> </div>JSP 里所有路径都带上${pageContext.request.contextPath},这是课程设计里最常见的路径坑。不加的话,当页面在 /cake/list 这个地址下渲染,浏览器会把相对路径解析成 /cake/css/xxx,样式全丢。加入购物车的链接指向 CartAddServlet,把蛋糕 ID 作为请求参数传过去。
购物车核心逻辑在 CartService 里:先判断 Session 里有没有登录用户,没有就跳登录页;有就查购物车表里该用户是否已经加过这个蛋糕,加过就把数量加一,没加过就插入新记录。Servlet 层只做参数接收和页面跳转:
@WebServlet("/cart/add") public class CartAddServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } int cakeId = Integer.parseInt(req.getParameter("cakeId")); cartService.add(user.getId(), cakeId); resp.sendRedirect(req.getContextPath() + "/cart/list"); } }这个例子用 doGet 是为了演示简单,实际项目里修改数据的操作应该用 POST。Integer.parseInt 收到非数字参数会抛 NumberFormatException,这里最好先 try-catch 再返回错误提示,属于容易被老师挑刺的边界情况。电商系统里购物车一般存 Session 或 Redis 不走数据库,但课程设计里存数据库反而能展示你对表关系设计的理解,答辩时讲清两种方案的取舍就是亮点。
4. 避坑:运行与答辩时最常见的 5 个翻车点
4.1 IDEA 运行 JavaWeb 项目:Tomcat 版本不对,报错报在莫名其妙的地方
现象:IDEA 里点运行,Tomcat 启动到一半直接失败,控制台刷出java.lang.NoClassDefFoundError: javax/servlet/...,或者一堆ClassNotFoundException,日志指向的代码位置和你写的类完全对不上。
原因:Tomcat 10 之后把 javax.servlet 包迁移到了 jakarta.servlet 名下,而课程设计代码基本都是按 javax 写的。版本不对,Tomcat 加载 Servlet 时找不到对应类,报错就会落在各种奇怪的位置。
解决:在 IDEA 的 Run Configuration 里把 Tomcat 换回 8.5 或 9.0,这是最省事的方案。另一个高频问题是改了 JSP 不生效——IDEA 默认的 On Update Action 可能是 Restart Server,每次改页面都要重启。在 Deployment 配置里把 On Update Action 和 On Frame Deactivation 都改成 Update resources,改完 JSP 直接刷新浏览器就能看到效果。这属于 IDEA 运行 JavaWeb 项目配置里的基本功,但很多新手不知道。
4.2 JSP 中文乱码:请求、响应、数据库三层都得设置
现象:页面显示「???」,或者注册提交中文用户名后数据库里存了一串乱码,怎么查都查不出问题在哪。
原因:请求编码、响应编码、数据库连接编码三层里至少有一层没设置。中文乱码从来不是单点问题,是整条链路的问题。
解决:请求侧在读取任何参数之前调用req.setCharacterEncoding("UTF-8");响应侧在写页面之前调用resp.setContentType("text/html;charset=UTF-8");JDBC URL 里加characterEncoding=utf8;建库用 utf8mb4。四个地方全对上,乱码基本绝迹。还有一个容易忽略的:IDEA 里文件本身的编码。Settings → Editor → File Encodings 里把 Global Encoding、Project Encoding、Default encoding for properties files 全部设成 UTF-8,否则代码里的中文注释编译出来就是乱码,运行期才暴露,最隐蔽。
4.3 数据库连接失败:时区、驱动版本、字符集三连查
现象:第一次查询就报Access denied for user 'root'@'localhost',或者Communications link failure,每次报错还不一样,看起来像网络问题。
原因:无外乎三种。密码不对、驱动版本和 MySQL 版本不匹配、时区没设置。驱动版本选错时最有迷惑性,MySQL 8.0 环境配 5.1.49 驱动,有时能连上,有时报 SSL error,有时报 time zone 问题。
解决:先检查 druid.properties 里 username 和 password 能否在命令行直接登录;再看驱动版本,MySQL 5.7 配 5.1.49,MySQL 8.0 配 8.0.x;最后确认 url 里有没有serverTimezone=Asia/Shanghai。字符集问题可以配合命令行执行SHOW VARIABLES LIKE 'character_set%';,把库、表、连接三个层面的字符集都看一遍,缺哪个补哪个。
4.4 页面和图片 404:路径问题的三个经典来源
现象:首页能打开,但 CSS、JS、图片全部 404,页面没有样式,丑得没法演示;或者蛋糕图片裂图,数据库里路径明明是对的。
原因:第一,JSP 里用了相对路径,比如css/style.css,页面在/cake/list这种带路径的地址下,浏览器会解析成/cake/css/style.css,自然找不到;第二,图片路径存的是upload/xxx.jpg,渲染时没有拼 contextPath;第三,图片上传到了 IDEA 的源码目录,而 Tomcat 实际运行的是部署目录,两者不是同一个位置。
解决:JSP 统一用绝对路径,最省事的方式是在<head>里加一句<base href="${pageContext.request.contextPath}/">,或者每个资源路径前都写${pageContext.request.contextPath}。Servlet 重定向也同理,sendRedirect必须拼 getContextPath。课程设计阶段,把蛋糕图片提前放在 webapp/upload 下、数据库路径写死,是最不容易翻车的方案;上传功能作为加分项最后再做,别让图片显示问题毁掉整个演示。
4.5 答辩被问「项目难点」:把功能讲成设计思路
现象:演示一切顺利,老师一问「你的项目架构是什么」,当场卡壳,只能说「我照着网上的源码改的」。这不是个例,是课程设计答辩最常见的翻车现场。
原因:只跑通了功能,没梳理请求的完整链路,也没有主动设计任何亮点。功能是抄的,思路是空的,老师一深问就露馅。
解决:准备一条主线,用一分钟讲清楚——浏览器发请求,Tomcat 根据 @WebServlet 找到对应的 Servlet,Servlet 调用 Service 处理业务,Service 调 DAO 操作 MySQL,结果放到 request 或 session 域,最后 forward 到 JSP 渲染成 HTML。再准备两个主动设计的点,比如「登录校验用 Filter 统一拦截」「数据库连接用 Druid 连接池复用」。这两点代码量都不大,但能证明你理解 JavaWeb 的核心机制。课程设计不要求功能多复杂,更看重你能不能把自己写的代码讲清楚。
5. 从「能跑」到「高分」:三个低成本进阶点
第一个进阶点:写一个 CharacterEncodingFilter,把乱码处理从每个 Servlet 里收口。过滤器里统一设置请求和响应编码,代码量不到十行,但整个项目里所有 Servlet 都能删掉重复的两行编码设置。答辩时老师看到你用 Filter 解决横切问题,而不是在每个页面复制粘贴,印象分立刻不一样。
第二个进阶点:购物车加入「未登录也能加购物车」的处理。把购物车数据放在 Session 里,用户登录后合并到数据库。这样一来,访客体验完整了,你也多了一个可以讲的业务场景——Session 购物车和数据库购物车怎么合并、什么时候合并、重复商品怎么处理。这个功能实际做起来只要二十行代码,但大部分同学只做了登录后才能加购物车的版本,你能做出来就是差异化。
第三个进阶点:用 AJAX 做用户名重复校验。注册页面输入用户名后,失焦时发一个异步请求到 Servlet,后端查数据库返回「可用」或「已存在」,不用等用户点提交才知道注册失败。实现方式是在 JSP 里写 XMLHttpRequest,Servlet 返回 JSON 字符串,前端解析后显示提示。这个功能技术含量不高,但能覆盖 AJAX、JSON、Servlet 三个考点,性价比很高。
最后验证的时候,用两个角色各走一遍全流程:普通用户注册、登录、加购物车、下单;管理员登录后台、查看订单。浏览器按 F12 打开 Network 面板,逐个请求确认状态码是 200 或 302,重点看有没有 404 和 500。能跑通只是及格线,能把每一条请求链路的来龙去脉讲清楚,才是课程设计真正的目标。
做完这三个点,你的蛋糕店系统在功能上和网上下载的源码包没有本质区别,但代码结构和答辩表现会明显高一个档次。我自己当年做类似项目时,最大的教训就是只顾着堆功能,没花时间把请求链路串成故事讲。后来带项目,我也总跟新人说,先想清楚「这个功能为什么这么设计」再动手写,比多写一百行代码更有价值。希望帮到你。
本文还有配套的精品资源,点击获取