简介面向JavaWeb初学者的蛋糕商城项目源码包可作为课程设计、毕业设计或项目实战参考。系统按客户、商品、订单三大模块组织实现用户注册登录、地址信息维护管理员客户管理、重置密码商品分类与搜索、文件上传、三类推荐位以及购物车与订单流转购物车采用CookieJSON实现贴合教学演示场景。压缩包共225个文件大小约10.97MB主要包含52个Java源码、52个class编译文件、23个JSP页面、16个CSS样式、5个JS脚本、11个JAR依赖库、若干图片及SQL初始化脚本等能够支撑从数据库设计到前端页面的完整对照学习。已有6784人浏览学习。借助分层DAO实现和清晰的功能模块划分可快速理解登录校验、购物车存取、订单提交及管理员推荐商品等核心流程适合用于巩固JavaWeb知识或扩展成更完整的商城系统。1. 蛋糕商城这个 JavaWeb 项目值不值得花时间复现如果你正在找 JavaWeb 课设、毕设的完整案例或者只是想弄明白「JSP Servlet MySQL 到底怎么串成一条线」这个蛋糕商城项目是个标准得不能再标准的样本。它的功能覆盖登录注册、商品列表、购物车、下单、后台管理正是大多数学校 JavaWeb 课程要求的那一套闭环。我第一次拆这类项目时以为得折腾很久结果核心代码量也就几千行比起 Spring Boot 那套全家桶它反而更适合用来理解 HTTP 请求从浏览器到数据库的完整路径。对它最大的误判是「太老了没用」实际上 Servlet JSP 的基础扎实了后面看 Spring MVC 的 DispatcherServlet 会顺畅得多。这份资源能解决的问题非常具体交一份能演示、能答辩、能现场改需求的 JavaWeb 作业。2. 先搞清运行链路一个请求在蛋糕商城里的完整旅程2.1 三层架构不是摆设JSP、Servlet、DAO 各管一段这套蛋糕商城是典型的 JSP Servlet JavaBean JDBC 结构没有引入 Spring 和 MyBatis。第一次打开代码时别急着点运行先按三层把包结构过一遍。我一般先看src目录下的包名servlet放控制器、service放业务逻辑、dao放数据库操作、entity或bean放实体类外加一个util放 JDBC 工具和过滤器。这样的分包方式在几乎所有课设项目里都能看到没有框架帮你管对象每一层都是手动 new 出来的反而比框架更容易看清调用关系。// 典型的 Servlet 处理流程以用户登录为例 WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); User user userService.login(username, password); if (user ! null) { request.getSession().setAttribute(user, user); response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }上面这段代码是这套项目里最常见的控制器写法。它做了三件事从前端取参数、调用 service 层做业务判断、根据结果决定重定向还是转发。注意sendRedirect和forward的区别重定向是浏览器重新发起一次请求URL 会变转发是服务器内部跳转URL 不变。登录成功用重定向可以避免刷新页面时表单重复提交这是个很实用的细节。再看 service 层和 DAO 层它们之间通过接口解耦。UserService 里调用 UserDaoUserDao 用 JDBC 操作数据库。这套项目里 JDBC 工具类通常长这样public class DBUtil { private static String url jdbc:mysql://localhost:3306/cake_shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static String username root; private static String password 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }这里的连接参数值得记一下。useUnicodetruecharacterEncodingutf8解决中文乱码serverTimezoneAsia/Shanghai解决高版本 MySQL 的时区报错com.mysql.cj.jdbc.Driver是 MySQL 8.0 的驱动类名。如果项目用的是 MySQL 5.x驱动类名要写成com.mysql.jdbc.Driver这一点后面避坑章节会再强调。2.2 从数据库表看懂这个项目的业务边界一个蛋糕商城核心业务就是「用户看到蛋糕→加购物车→下单」。对应到数据库最少要有这么几张表用户表、分类表、商品表、订单表、订单明细表。这个项目的建表脚本通常在sql目录或项目根目录下文件名一般是cake_shop.sql。CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, phone VARCHAR(20), address VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, description VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8; CREATE TABLE cake ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, description TEXT, image VARCHAR(255), stock INT DEFAULT 0, KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, order_no VARCHAR(32) NOT NULL, total_price DECIMAL(10,2) NOT NULL, status INT DEFAULT 0 COMMENT 0-待付款 1-已付款 2-已发货 3-已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8;字段设计上有个常见问题订单明细表不能省。很多课设项目只有订单表把商品名和价格直接冗余在订单表里这样一个人买三个蛋糕就要生成三张订单逻辑上其实是拆错了。正确做法是「订单主表 订单明细表」主表存总价和状态明细表存每个商品的数量和单价。这个项目如果只有主表也别急着改先看它是怎么处理的再决定要不要拆。表与表之间的关联关系也直接对应页面跳转cake.category_id关联分类表做按类别筛选orders.user_id关联用户表查我的订单order_item.cake_id关联商品表展示购买明细。orders表名最好加反引号因为order是 MySQL 的保留关键字坑过不少人。3. 本地跑起来IDEA Tomcat MySQL 的完整配置3.1 版本匹配关系为什么 JDK、Tomcat、MySQL 不能随便配我拿到这个资源的第一反应是先确认三件事JDK 版本、Tomcat 版本、MySQL 版本。这三者之间是有一条隐形的兼容链的。这个蛋糕商城是 JSP Servlet 项目代码里import javax.servlet.*那它就必须跑在 Tomcat 9 或更早版本上因为 Tomcat 10 起把命名空间改成了jakarta.servlet.*直接把项目丢进 Tomcat 10 会报 ClassNotFoundException这是最常见的翻车点。JDK 版本建议 Java 8。虽然 JSP 在 Java 11 上也能跑但很多课设代码里用了老版本 API编译级别不匹配会出现「程序包不存在」这种看不懂的报错。MySQL 用 5.7 或 8.0 都行关键是驱动和 URL 要配套这一点在下文配置里会写全。3.2 IDEA 里部署从打开项目到浏览器看到登录页IDEA 中运行 JavaWeb 项目不少新手卡在「代码没报错但浏览器就是 404」。核心原因多半是没把项目打成 Artifact 并挂到 Tomcat。我按自己的操作习惯说一遍用 IDEA 打开项目如果有.idea目录就直接 Open没有就新建一个空项目再把src和web目录导进来。看到web/WEB-INF/web.xml存在说明这是一个完整的 Web 应用。打开 Project Structure快捷键CtrlAltShiftS在 Artifacts 里点 号选择 Web Application: Exploded然后把项目里web目录设为 Web Resource Directory。把lib目录下的所有 jar 包添加为库依赖。这个项目离不开mysql-connector-java和jstl.jar如果缺失运行时会报驱动找不到或 JSTL 标签无法解析。配置 TomcatRun → Edit Configurations → → Tomcat Server → Local。在 Deployment 标签里点 号把刚才建好的 Artifact 添加进去Application context 修改为/这样访问路径就是http://localhost:8080不用加项目名。在 Server 标签里确认 HTTP port 是 8080JRE 选择你装的 Java 8。如果 8080 被占用改成一个不常用的端口比如 8090。# 如果 Tomcat 启动后控制台出现这种日志说明部署成功 # 14-Sep-2024 10:22:11.138 信息 [main] org.apache.catalina.startup.Catalina.start 服务器启动在 [1,234] 毫秒内 # Deployment of web application archive ... has finished in [180] ms部署完成后浏览器输入http://localhost:8080/index.jsp能看到蛋糕列表页就说明跑通了。如果你只配置了 Tomcat 但没添加 ArtifactTomcat 会正常启动但访问任何 URL 都是 404。这个过程属于「看着简单实际每一步都有各自的坑」尤其是 Artifact 那一环IDEA 的术语让不少新手直接把项目配置成了普通 Java Application自然是跑不起来的。3.3 数据库初始化与连接参数检查建库导 SQL 之前先看一眼项目的配置文件。在这个蛋糕商城里连接参数可能写在src/db.properties、src/jdbc.properties或者直接写在 DBUtil 里。我习惯先搜关键词jdbc:找出所有数据库连接串确认指向的库名和账号密码。# db.properties 示例 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/cake_shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456然后把cake_shop.sql导入 MySQL。命令行导入或 Navicat 都行注意执行前先创建数据库否则表结构没有归属。mysql -u root -p -e CREATE DATABASE cake_shop DEFAULT CHARSET utf8 COLLATE utf8_general_ci; mysql -u root -p cake_shop cake_shop.sql导入成功后可以用一条简单查询验证数据是否完整SELECT COUNT(*) FROM cake;。如果返回结果大于 0 且没有中文乱码说明编码问题处理得好。如果中文变成了问号大概率是characterEncodingutf8没配上或者 MySQL 数据库本身的字符集建库时就建错了。这一步做完再回头启动 Tomcat登录页面如果还在报错就往避坑章节里找原因。4. 避坑把蛋糕商城跑起来的 5 个翻车点4.1 现象Tomcat 启动正常浏览器全是 404这个坑我见过不计其数次。IDEA 的控制台里 Tomcat 明明打印了Server startup in [1,234] milliseconds但打开页面就是 404。原因多半是 Artifact 没有部署成功。Tomcat 启动了但它加载的是一个空应用。处理办法是重新检查 Deployment 那一栏有没有把 Artifact 加进去并确认 Application context 设置的不是/xxx而是/。很多人以为配置 Tomcat 路径就够了其实 Tomcat 要加载的是 Artifact不是磁盘上的某个文件夹。另外注意如果之前运行过旧的 Tomcat 实例把旧实例停掉再启动否则端口占用也会导致页面访问到旧应用。4.2 现象控制台报 ClassNotFoundException: com.mysql.jdbc.Driver项目跑起来一点登录按钮后台直接弹出这个异常。这个异常已经是「经典中的经典」了。原因是驱动类名写死成了旧版 MySQL 的com.mysql.jdbc.Driver但当前 MySQL Connector/J 是 8.x驱动类已经改名为com.mysql.cj.jdbc.Driver。解决方法是改配置或代码里的Class.forName()参数同时把jdbc:mysql://后面的 URL 补上serverTimezoneAsia/Shanghai。高版本 MySQL 驱动对时区要求很严格不写时区会直接报The server time zone value ... is unrecognized。顺手把useSSLfalse也加上否则控制台会刷一大段 SSL 警告。4.3 现象页面中文全部乱码登录后用户名显示成问号这个坑在 JSP 项目里几乎必现。乱码有两层请求乱码和响应乱码。如果表单提交的中文到了 Servlet 里就变成???是请求编码问题。如果数据库里的数据读出来在页面上是乱码是响应编码和数据库编码的双重问题。解决的套路是三层一起改。第一层JSP 页面顶部pageEncodingUTF-8和contentTypetext/html; charsetUTF-8都要有。第二层Servlet 里在读取参数前加request.setCharacterEncoding(UTF-8)设置响应编码用response.setContentType(text/html;charsetUTF-8)。第三层数据库连接 URL 加characterEncodingutf8建库指定DEFAULT CHARSET utf8。如果项目里有 CharacterEncodingFilter检查它有没有配置/*拦截路径和encoding参数。这层解决好了基本上永久告别乱码。4.4 现象商品图片显示一个裂开的图标蛋糕商城肯定是图文列表为主图片挂了整个页面显得很业余。这个项目里的图片一般有两种存法放在/web/images/静态目录或放在/web/upload/目录。如果 HTML 里的img src/upload/xxx.jpg但项目实际没有 upload 目录就会裂开。解决方法是先找到图片真实的磁盘路径然后在 IDEA 的 Tomcat 配置里加一个虚拟目录映射。如果不改 Tomcat也可以直接把图片拷到web/images/下把数据库里的 image 字段值改短一些路径写成相对路径images/xxx.jpg。这里没有统一解法因为每个项目的静态资源配置不一样唯一确定的是「图片路径问题 HTML 里的路径 服务器实际物理路径对不上」。4.5 现象JSP 页面上 JSTL 标签全部原样输出不解析页面顶部写了c:forEach但浏览器直接把这些标签当纯文本显示出来。这说明 JSTL 依赖没有完整引入。jstl.jar和standard.jar两个 jar 包必须同时在lib目录里。有些项目只用了一个就会产生这种诡异现象。另外要确认web.xml的 web-app 版本信息是否和 Tomcat 匹配。Tomcat 9 对应web-app_4_0.xsdTomcat 8 对应 3.1。web.xml 头部写的是 2.5 一般也能跑但 JSP 对 EL 表达式的支持在不同版本上有差异。遇到标签不解析先把 jar 包补齐再把web.xml头拷成和 Tomcat 版本匹配的模板。这条经验我印象很深当年一个项目页面上的${}一直原样输出最后发现是 web.xml 声明版本低了。5. 从「能跑」到「能交」四个改造点与代码示例5.1 密码明文改 MD5至少别让答辩老师一眼看穿课设项目默认的 user 表里password 直接存明文登录逻辑也是取出来明文比较。虽然你只是交作业但答辩现场如果老师问「密码安全怎么处理」你得拿得出方案。最简单的增强是加 MD5 工具类注册时存摘要登录时比对摘要。public class MD5Util { public static String encrypt(String source) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(source.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { String hex Integer.toHexString(0xff b); if (hex.length() 1) { sb.append(0); } sb.append(hex); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(MD5加密失败, e); } } }使用方式很简单注册时password MD5Util.encrypt(password)再存入数据库登录时取出表单密码做一次 MD5再与数据库里的摘要比较。逻辑上只改两三个调用点。如果你想再进一步加个盐值MD5Util.encrypt(username password)这种细节写在答辩说辞里非常加分。5.2 商品列表加分页一个通用 Servlet 改写思路商品多的时候一次性查全表页面会越来越长。课设里被问到这个问题的概率极高所以分页是值得优先做的改造。思路是先算出总件数和总页数再通过 LIMIT 取当前页数据。public class PageBeanT { private ListT list; // 当前页数据 private int pageNum; // 当前页码 private int pageSize; // 每页条数 private long total; // 总记录数 private int totalPage; // 总页数 public PageBean(int pageNum, int pageSize) { this.pageNum pageNum; this.pageSize pageSize; } }// DAO 里查商品时改成这样 public ListCake findCakesByPage(int pageNum, int pageSize) { Connection conn DBUtil.getConnection(); String sql SELECT * FROM cake ORDER BY id LIMIT ?, ?; PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); ResultSet rs ps.executeQuery(); // 遍历结果集封装 Cake 对象... }(pageNum - 1) * pageSize是分页里最容易理解错的地方。第 1 页要跳过 0 条第 2 页跳过 10 条第 3 页跳过 20 条公式就是(当前页-1) * 每页条数。JSP 页面底部再放上一组「上一页 1 2 3 下一页」的链接把pageNum参数拼到 URL 上。答辩时老师顺着点两页逻辑清楚这一项就是加分项。5.3 购物车从 Session 搬到数据库业务复杂度的一次升级原始的购物车实现一般是MapInteger, Integer或CartItem的 List 放在 Session 里。这个方案能跑但用户体验经不起「关浏览器就没了」的追问。把购物车持久化到数据库是一次典型的业务升级这里只说设计思路代码量较大放到项目里逐步改。购物车表需要三个字段维度支撑用户 ID、商品 ID、数量。一张代码实现上最简单的表结构就是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) ) ENGINEInnoDB DEFAULT CHARSETutf8;唯一键uk_user_cake很关键它的作用是同一个用户加同一件商品时数据库层面不会产生重复记录。DAO 层先执行 UPDATE 设置数量 1如果影响行数为 0再执行 INSERT 插入一条新记录。这种「先更新再插入」的机房写法避免了先查一次再判断的啰嗦逻辑。改造时注意把购物车内的总价计算逻辑从 JSP 挪到 Service 层否则 JSP 里塞一堆 Java 计算代码答辩观感不好。5.4 后台管理加一个简单的权限拦截Filter 的实战用法项目里如果有后台管理页面那它大概率有一个AdminServlet或单独的admin/目录。但如果所有页面都能被直接访问后台就形同虚设。加一个 Filter 做登录拦截是 Servlet 规范里非常经典且好答辩的知识点。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; Object admin req.getSession().getAttribute(admin); if (admin null) { resp.sendRedirect(req.getContextPath() /admin/login.jsp); return; } chain.doFilter(request, response); } }这段代码的作用是拦截所有/admin/开头的路径Session 里没有管理员标记就踢回登录页。注意WebFilter(/admin/*)这个路径写法/admin/*只拦 admin 目录下的资源/*会把登录页也拦住造成死循环这是最常见的误配置。Filter 是 JavaWeb 阶段最重要的知识之一改完这个答辩时老师问「页面权限怎么控制的」你就可以直接指这段代码说事。6. 验证收尾一套不用写测试用例的验收方法6.1 主流程黑盒验证表项目改完别只点两个页面就收工。我给自己定了一个习惯把主流程列成一张表逐项去跑每跑通一项才算数。这套蛋糕商城至少需要验证这些场景编号操作路径预期结果1注册新用户提示成功数据库 user 表多一条记录2登录成功首页右上角显示用户名购物车可访问3登录失败页面提示「用户名或密码错误」4按分类筛选蛋糕列表只显示该分类下商品5加入购物车购物车徽标数量 16提交订单生成订单号库存扣减7后台管理发货订单状态从已付款变为已发货8退出登录Session 失效访问受保护页面跳转登录页跑完这八项整个项目的核心业务闭环就都有底了。不信你可以试试多数「看似能跑」的项目在这张表里能找到至少一两个 bug。6.2 踩坑后的调试习惯先日志后断点这个项目里遇到诡异问题我一般不会先打断点而是在关键方法入口加一行System.out.println或临时日志。Servlet 项目不像 Spring Boot 自带监控面板它的「黑匣子」就是控制台。请求参数、执行到哪一步、SQL 语句拼成了什么样全往控制台打。日志确认了问题范围再上断点逐行看这是最省时间的路径。条件断点也很有用。比如只在用户名等于admin时才停if (admin.equals(username)) { System.out.println(debug: admin login attempt); // 在这里打断点 }6.3 答辩前必须做的一件事把项目里所有硬编码的绝对路径查一遍。比如C:/Users/xxx/Desktop/upload/1.jpg这种写死在代码里的路径换台机器就挂。搜索关键词C:和/Users/把绝对路径改成相对路径这是课设项目最常见的隐性炸弹。从那以后我每次拿到新的课设项目都强制自己先花十分钟搜这两个关键词再启动服务器这个习惯帮我少加了很多次班。希望帮到你。本文还有配套的精品资源点击获取