简介:面向JavaWeb初学者的完整蛋糕商城项目,涵盖客户、商品、订单三大核心模块,前台支持用户注册登录、修改信息、购物车与提交订单,后台支持管理员对客户、商品分类、推荐位及订单的增删改查。购物车基于Cookie+JSON实现,商品图片采用文件上传,搜索与推荐功能均有落地,适合作为课程设计、毕业设计或Servlet/JSP综合练手项目。资源共225个文件,以52个Java源码、52个编译class、23个JSP页面、16个CSS样式、11个Jar依赖及若干图片资源为主,压缩包大小约10.97MB,附带SQL数据库脚本,导入配置后即可运行。已有6781人学习下载,项目结构清晰、功能完整,能帮助读者快速理解JavaWeb分层开发、会话管理、文件上传、JSON交互等关键知识点,是一个可直接运行并二次扩展的实战案例。 如果你正准备做JavaWeb课设、毕设,或者刚学完Servlet和JSP想找一个完整项目练手,那“蛋糕商城”这个题目非常值得认真做一遍。它不是那种烂大街的后台管理系统,而是带前台展示、用户操作、订单流程的完整电商闭环,能帮你把JavaWeb的技术点全部串起来。
我自己的经验是:把蛋糕商城完整走通一遍,胜过刷十几套零散的功能demo。因为这个项目覆盖了JavaWeb最核心的几块内容——前端页面交互、Servlet控制层、JSP视图渲染、MySQL数据持久化、Session会话管理,以及最终的打包部署。整个链路跑通后,你对JavaWeb的理解会有一次质的提升。
这篇文章我会从一个实际开发者的视角,把整个蛋糕商城项目的设计思路、数据库表结构、核心功能实现、部署发布和常见坑位全部过一遍,尽量做到你照着操作就能跑起来。
1. 项目整体设计与功能拆解
1.1 为什么选择“蛋糕商城”作为JavaWeb练手项目
很多人在选课设题目时容易走极端:要么是简单的图书管理、员工管理这种CRUD项目,功能太单薄,做完感觉什么都没学会;要么一上来就想做秒杀系统、社交平台,技术栈和业务复杂度超出JavaWeb阶段的能力范围,最后变成抄代码都抄不明白。
蛋糕商城刚好卡在中间位置。它的业务模型是标准的“用户-商品-订单”三段式电商架构——用户登录后浏览商品,加入购物车,提交订单并填写配送信息。这个流程虽然简单,但把电商的核心链路完整走了一遍。而且蛋糕商品天然自带分类属性(生日蛋糕、慕斯蛋糕、提拉米苏等),做分类筛选和商品检索时比图书管理系统更有说服力。
从项目展示角度来说,蛋糕商城的视觉效果也好。蛋糕的商品图普遍精美,页面做出来漂亮,答辩演示时加分很明显。这点不要小瞧,同样的代码水平,一个界面清爽、商品图好看的商城,和一个只有文字表格的管理系统,给人留下的第一印象完全不一样。
1.2 功能模块划分
我建议按两条线拆功能:
前台用户模块
- 用户注册、登录、退出
- 首页商品列表和分类浏览
- 商品详情页
- 购物车管理(加入、修改数量、删除、清空)
- 订单确认和提交
- 个人订单列表查询
后台管理模块
- 管理员登录
- 商品分类管理
- 商品信息管理(上架、下架、编辑、库存调整)
- 订单管理(查看订单详情、修改订单状态)
这里需要注意一下:很多初学者的项目后台和前台没有做权限区分,普通用户也能访问管理员页面。正确的做法是设置一个管理员账号,后台接口必须校验Session中的用户角色,不是管理员就直接拦截掉。
1.3 技术选型:为什么先别急着上框架
蛋糕商城项目我强烈建议不要用Spring Boot,直接用原生的Servlet + JSP + MySQL + Tomcat来做。
原因很简单:框架会帮你隐藏太多细节。你搭一个Spring Boot项目,写个Controller方法然后调Service调Mapper,可能半天就跑通了。但分布式架构里的请求流转、Session原理、参数传递、重定向和转发这些底层概念,你全都没有真正接触过。将来面试被问“Servlet生命周期”“Forward和Redirect的区别”“Session和Cookie的关系”,你会发现自己脑子里是空的。
用原生的Servlet + JSP做一遍蛋糕商城,你被迫要手动处理所有流程:
- 手动在WEB-INF/web.xml里配置Servlet映射,理解URL到代码的对应关系
- 手动接收并处理请求参数,理解getParameter的原理
- 手动管理Session会话,理解为什么登录状态能保持
- 手动拼接JSP页面,理解数据如何从后台输出到前端
这些知识是整个JavaWeb的地基。等你自己用原生方式走通了,再去学Spring MVC会快非常多,因为你已经知道框架帮你做了哪些事情。我见过太多直接上手Spring Boot的初学者,遇到问题完全无从下手,就是因为地基里的概念是空的。
2. 开发环境搭建与数据库设计
2.1 使用IDEA 2023从零创建JavaWeb项目
现在网上很多教程还在讲Eclipse和MyEclipse的操作,但现实中大部分学校和企业已经全面转向IDEA。这里我以IDEA 2023版本为例,讲一下完整的创建步骤。
IDEA 2023中新建项目的入口有所调整:点击File → New → Project,在左侧选择“Jakarta EE”,然后勾选“Web Application”。注意这里不选“Servlet”和“JSP”依赖模板——IDEA的Jakarta EE模板默认带的是Servlet 6.0规范,对应Tomcat 10以上版本。如果你用的是Tomcat 9或更低版本,需要手动把Servlet API版本对齐,否则部署时会报ClassNotFound之类的错。
我的建议是直接使用Tomcat 9 + Servlet 4.0的组合,这是目前教程资料最多、兼容性最好的搭配。具体操作路径是:
- 新建空的Java项目,手动添加Web目录结构(src/main/java、src/main/webapp)
- 在Project Structure → Facets 中添加Web模块,指定web.xml路径
- 在Project Structure → Artifacts 中配置Web Application Exploded,并添加Lib目录
- 把mysql-connector-java驱动Jar包放到WEB-INF/lib下
- 配置Tomcat运行环境,Deployment中选中刚才的Artifact
这几步是IDEA用JavaWeb最容易出问题的地方,很多新手在Artifacts环节漏了Lib目录,导致运行时找不到MySQL驱动,报ClassNotFound。你可以在Project Structure里直接给Artifact添加了所有lib依赖,也可以在WEB-INF下手动建一个lib目录放Jar包,两种方式效果一样。
2.2 数据库表结构与关键设计
蛋糕商城我设计了5张核心表:用户表、商品分类表、商品表、订单表、订单项表。下面是具体的字段设计:
用户表(user)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int 主键自增 | 用户ID |
| username | varchar(50) 唯一 | 用户名 |
| password | varchar(64) | 密码,密文存储 |
| phone | varchar(20) | 联系电话 |
| address | varchar(255) | 配送地址 |
| create_time | datetime | 注册时间 |
商品分类表(category)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int 主键自增 | 分类ID |
| name | varchar(50) | 分类名称 |
| sort | int | 排序字段 |
商品表(product)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int 主键自增 | 商品ID |
| category_id | int | 所属分类 |
| name | varchar(100) | 商品名称 |
| price | decimal(10,2) | 价格 |
| image | varchar(255) | 商品图片路径 |
| description | text | 商品描述 |
| stock | int | 库存数量 |
| status | int | 上下架状态,1上架0下架 |
订单表(orders)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int 主键自增 | 订单ID |
| order_no | varchar(32) | 订单编号 |
| user_id | int | 下单用户 |
| total_price | decimal(10,2) | 订单总金额 |
| receiver_name | varchar(50) | 收货人 |
| receiver_phone | varchar(20) | 收货电话 |
| receiver_address | varchar(255) | 收货地址 |
| status | int | 状态:0待处理1已发货2已完成3已取消 |
| create_time | datetime | 下单时间 |
订单项表(order_item)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int 主键自增 | 订单项ID |
| order_id | int | 所属订单 |
| product_id | int | 商品ID |
| product_name | varchar(100) | 商品名称(快照) |
| product_image | varchar(255) | 商品图片(快照) |
| price | decimal(10,2) | 成交单价 |
| quantity | int | 购买数量 |
这里面有两点特别提醒各位。
第一,价格字段一定要用decimal,不要用float或double。很多教材示例里用double存价格看着没啥问题,但实际浮点数运算会有精度丢失,比如19.9 + 0.1 可能得到20.0000000004这种鬼数字。金额精度这种问题是电商系统的致命伤,从一开始就养成好习惯,所有涉及钱的字段全部用decimal。
第二,order_item表里冗余了product_name和product_image。这属于典型的“快照设计”,因为商品名称和图片后续可能会修改,但订单里的成交信息应该保持下单那一刻的样子。这个经验在实际项目中非常常见,面试官问起订单设计时如果能说出快照这个概念,印象分会好很多。
2.3 建表SQL的核心思路
建表时注意加注释、统一字符集、设置合理的索引和外键。下面给出几段关键SQL作为参考。
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(64) NOT NULL COMMENT '密码', `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `address` varchar(255) DEFAULT NULL COMMENT '配送地址', `create_time` datetime DEFAULT NULL COMMENT '注册时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL COMMENT '分类ID', `name` varchar(100) NOT NULL COMMENT '商品名称', `price` decimal(10,2) NOT NULL COMMENT '价格', `image` varchar(255) DEFAULT NULL COMMENT '图片路径', `description` text COMMENT '商品描述', `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存', `status` int(11) NOT NULL DEFAULT '1' COMMENT '1上架 0下架', PRIMARY KEY (`id`), KEY `idx_category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';整库统一使用utf8mb4字符集,这个强烈建议一步到位。utf8mb4是utf8的超集,支持存emoji表情,而且不存在中文乱码问题。如果你还在用utf8,遇到特殊字符会有各种神奇问题,直接换成utf8mb4省心得多。
3. 核心功能实现与实操细节
3.1 用户登录注册与状态管理
用户模块是商城的第一道门面,也是最容易出细节问题的部分。这里我挑几个容易被忽略的点说一下。
第一个是密码存储。直接明文存数据库等于把用户信息裸奔,一旦数据库泄露全部遭殃。正确做法是加盐后做SHA-256哈希。具体思路是:注册时生成一个随机盐值,把盐值和密码拼接后计算哈希,把盐值和最终哈希一起存入数据库;登录时用同样的盐值重算哈希并与库中比对。
// 密码加盐哈希处理 String salt = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16); String hashedPassword = DigestUtils.sha256Hex(salt + password); // 将 salt 和 hashedPassword 存入数据库我见过很多校内项目的密码字段直接明文,虽然课设评审不一定在意,但将来简历上写的项目如果有这种低级安全漏洞,面试官问起来是很尴尬的。
第二个是会话状态管理。用户登录成功后,把用户信息存到Session中,之后的请求从Session里判断是否登录。对应的核心逻辑是:
// 登录成功后保存用户信息到Session HttpSession session = request.getSession(); session.setAttribute("user", user); session.setMaxInactiveInterval(30 * 60); // 30分钟过期 // 未登录时拦截跳转 User user = (User) session.getAttribute("user"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; }如果想做得完善,可以手写一个LoginFilter放在web.xml中配置拦截规则,把需要登录才能访问的URL统一包进来。这样可以避免在每个Servlet里重复写登录判断逻辑,代码会清爽很多。
第三个是验证码问题。为了项目完整性,建议加一个简单的图形验证码。用Java原生BufferedImage画验证码图片,后端生成随机字符串后存到Session,提交时比对Session中的值和用户输入值,一致才放行。这块代码量不大,但对项目的完整度提升非常明显。
3.2 商品展示与购物车
商品列表页的核心是搞清楚“JSP页面如何拿到后端的数据”。这里有两种做法:一种是在Servlet里查询完数据,使用request.setAttribute把List传给JSP;另一种是直接在JSP里写Java代码查库。第一种是标准的MVC做法,推荐学习;第二种代码写在JSP里虽然省事,但会把页面搅成一锅粥,后期维护非常痛苦。
列表页的核心代码结构大致如下:
// 商品列表Servlet String categoryId = request.getParameter("categoryId"); List<Product> productList = productService.findByCategory(categoryId); request.setAttribute("productList", productList); request.getRequestDispatcher("/product_list.jsp").forward(request, response);JSP端用JSTL和EL表达式取出数据:
<c:forEach items="${productList}" var="p"> <div class="product-card"> <img src="${p.image}" alt="${p.name}"> <h3>${p.name}</h3> <span class="price">¥${p.price}</span> <a href="${pageContext.request.contextPath}/product/detail?id=${p.id}">查看详情</a> </div> </c:forEach>注意${pageContext.request.contextPath}这一句是JSP中获取项目上下文路径的标准姿势,很多新人写死路径,部署时改了应用名就全部404。
购物车的实现方案建议用Session存储。用Session保存一个HashMap,键是商品ID,值是购物车条目的数量,每次加入购物车时更新Map,使用Session存象如下:
Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); } cart.put(productId, cart.getOrDefault(productId, 0) + 1); session.setAttribute("cart", cart);为什么不建议用Cookie存购物车?因为Cookie容量只有4KB,存不了太多商品,而且数据在客户端可以被用户随意篡改。Session存购物车是Servlet阶段的经典做法,真实项目中高性能场景会用Redis,但那是Spring阶段的内容,这里先不做扩展。
3.3 订单提交与事务处理
订单流程是整个项目技术含金量最高的部分,因为涉及多表操作和事务控制。提交订单时,需要同时完成:
- 向orders表插入一条订单记录
- 向order_item表插入多条订单项记录
- 扣减product表的库存
这三个操作必须放在同一个事务里,任何一个失败都要全部回滚。否则会出现“订单生成了但库存没扣”或者“库存扣了但订单没生成”的数据不一致问题。
JDBC原生事务的标准写法是关闭自动提交,执行完所有SQL后手动commit,出错则rollback:
Connection conn = null; try { conn = DriverManager.getConnection(url, user, password); conn.setAutoCommit(false); // 关闭自动提交 // 1. 插入订单表 // 2. 插入订单项表 // 3. 扣减库存 UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ? conn.commit(); } catch (Exception e) { if (conn != null) { conn.rollback(); } throw e; } finally { if (conn != null) { conn.setAutoCommit(true); conn.close(); } }这里有个小细节值得专门提一下:库存扣减的SQL要带上库存判断条件,写成UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?,并且要检查受影响的行数。如果影响行数为0,说明库存不足,需要抛出异常回滚事务。这种方式能有效防止超卖,比先查库存再更新的方式更可靠。
订单编号建议用时间戳加随机数的方式生成,格式类似202401011200001234。注意不要用自增ID直接当订单号对外展示,那样会暴露订单数量信息,不安全。
4. 项目部署发布与问题排查
4.1 打war包与Tomcat部署
项目开发完,最终要部署到服务器上才能给用户访问。这里推荐把项目打成war包,扔到Tomcat的webapps目录下运行。IDEA中的操作是:Project Structure → Artifacts → 点击加号 → Web Application Archive,然后Build → Build Artifacts,就能在out目录下生成war包。
把war包复制到Tomcat的webapps目录,启动Tomcat后会自动解压发布。启动命令是:
# Linux环境启动Tomcat cd /opt/tomcat/bin ./startup.sh # Windows环境启动Tomcat cd C:\apache-tomcat-9.0.73\bin startup.bat启动后通过http://服务器IP:8080/项目名/来访问。如果你测试时用的是8080端口,正式部署时建议改成80端口,这样用户访问时不用在URL里输入端口号。修改方式是在Tomcat的conf/server.xml中,把Connector标签的port="8080"改成port="80"。
4.2 Apache + Tomcat联合发布的思路
如果你的项目要上线到Windows Server环境,很多人会纠结是直接暴露Tomcat端口还是搭配Apache一起用。从实际部署的角度来说,Tomcat本身就是一个独立的Web服务器,完全可以直接对外提供服务,但不建议直接暴露8080端口对外,因为:
- Tomcat对静态文件的处理能力不如专用HTTP服务器
- Tomcat较多暴露的是动态应用层,直接暴露到公网有更多安全隐患
- 使用Apache做前置转发,可以把静态资源交给Apache处理,动态请求转发给Tomcat,性能更好
使用Apache转发到Tomcat的常见做法是用mod_proxy模块,在Apache配置中加入以下内容:
ProxyRequests Off ProxyPass /cakeShop http://localhost:8080/cakeShop ProxyPassReverse /cakeShop http://localhost:8080/cakeShop这样用户访问Apache的80端口,Apache会把请求转发给Tomcat的8080端口,对外表现为只有Apache一个入口。这种方式在实际项目中很常见,你在简历上写“项目部署在Windows Server + Apache + Tomcat环境”,面试官会觉得你有真实的部署经验。
4.3 常见问题速查表
最后把我做这个项目时踩过的坑整理成一张速查表,希望对你有用。
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 访问页面报404 | URL写错或项目部署路径不对 | 检查web.xml中Servlet映射路径,检查访问URL中的项目名是否与war包名一致 |
| 启动Tomcat报端口占用 | 8080端口被其他进程占用 | 修改server.xml端口,或命令行执行netstat -ano找占用进程kill掉 |
| 数据库中文乱码 | 连接URL缺少编码参数 | JDBC连接串加characterEncoding=utf8,页面统一utf-8 |
| ClassNotFoundException: com.mysql.jdbc.Driver | MySQL驱动Jar包没导入 | 把mysql-connector-java.jar放到WEB-INF/lib目录 |
| 部署后页面样式全丢 | 静态资源路径写成了相对路径 | 页面CSS/JS引用用${pageContext.request.contextPath}拼绝对路径 |
| 登录后刷新又变未登录 | Session配置过期时间过短或IDEA频繁重启 | 检查Session超时配置,排查那个请求没带Session返回 |
注意:第一个404的问题最隐蔽的是路径匹配错误。Servlet配置/hello,访问时项目名后面只需要加/hello就可以了,但在JSP里跳转时容易多写一层目录,逐个检查跳转目标地址是排查这类问题的关键。
从我个人做了多个JavaWeb课设项目后的体会来看,能在原生Servlet阶段把项目做扎实,是Java后端学习中性价比最高的投入。这个阶段的过程虽然比用框架更繁琐,但也正因为繁琐,你才会逼自己去理解每一个环节背后的原理。蛋糕商城做完之后,建议你再亲手用Spring Boot重写一遍同一个项目,把两次开发的体验对比一下,你会发现自己对JavaWeb的理解已经不在一个层面上了。
本文还有配套的精品资源,点击获取