每年毕业季,总有一批人被“做个系统”这四个字折磨得睡不着觉。你要是随便逮一个刚做完毕设的学长问哪个方向最好交差,十有八九会得到同一个答案:电商,而且最好是带点特色的电商。蛋糕商城这个选题,说白了就是把通用电商套了一层烘焙的外衣——用户注册登录、蛋糕分类浏览、加购物车、下单支付、后台发货管理,一套链路下来,Spring Boot+JavaWeb的核心知识点基本全覆盖了,答辩时也容易讲清楚。
这篇文章就是围绕这套系统的完整落地过程来写的。如果你是正在做毕设的学生,或者想拿Spring Boot练手的Java后端新人,我这里会把功能的拆法、表该怎么建、状态怎么流转、支付怎么接,还有版本选择这些最容易翻车的地方,一次性讲明白。每个环节我都会告诉你为什么这样做,哪些坑是我自己踩过的,哪些地方能省则省。
1. 为什么选Spring Boot:它到底帮你解决了什么
1.1 从Servlet到Spring Boot,JavaWeb开发经历了什么
很多教程默认你直接从Spring Boot开始学,但如果不理解它到底解决了什么问题,出了问题你连排查的方向都没有。
早些年写JavaWeb,用的是Servlet+JSP。那时候一个登录功能要写一个Servlet类,在web.xml里还要注册映射,改一个URL都得重新配一遍。后来Spring MVC出现,把请求分发这块整理清楚了,但SSM(Spring+Spring MVC+MyBatis)整合依然要写一堆XML配置文件,什么数据源配置、事务管理器配置、Mapper扫描配置,每一样都能把人搞疯。而且配置写错了,启动直接报错,报错信息还看不懂。
Spring Boot的理念简单粗暴——约定大于配置。它替你做好了所有默认决策:内嵌了Tomcat服务器,打包后一条java -jar就能跑;用starter机制把常用依赖打包好,你只要引入一个spring-boot-starter-web,Spring MVC、JSON转换、默认配置全都有了;自动装配功能会在启动时扫描依赖,自动创建需要的Bean。做个蛋糕商城这种规模的单体应用,你几乎不需要写任何XML配置。
1.2 单体就能解决的事,别为了酷炫上微服务
我见过不少同学为了显得“高大上”,非要在毕设里塞一套微服务架构,拆五六个服务,最后人仰马翻。蛋糕商城这种项目,注册用户几千人就了不起了,并发量更是可以忽略不计。单体应用部署简单、调试方便、事务天然保证一致性,这些优点在项目规模不大的时候就是压倒性的。
单体和微服务的取舍标准很简单:看团队的维护成本和业务复杂度有没有超出单体的承受能力。没有的话,老老实实写单体,把时间省下来做业务功能,比什么都强。评委老师也不会因为你用了微服务就给你加分,功能完整、逻辑清晰才是得分点。
1.3 这套组合适合谁
- 正在准备毕业设计的学生——代码量适中,演示效果直观;
- 想转Java后端的新人——作为第一个完整项目可以打通整套技能栈;
- 需要面试谈资的人——电商系统的通用解法可以迁移到任何业务场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 蛋糕商城的业务模块怎么拆:别一头扎进代码
很多人拿到题目第一反应就是打开IDE开始建表建类,这个顺序反了。无论用什么技术栈,先把业务边界划清楚,代码只是水到渠成的事。
2.1 用户端功能清单
用户端要满足的核心场景就是“浏览→下单→支付→查订单”。逐条拆下来是这样的:
- 注册登录:用户名+密码注册,密码必须加密存储,登录成功后生成Token(或用Session)保持会话状态;
- 蛋糕分类与搜索:按分类(生日蛋糕、慕斯、芝士等)筛选,按名称关键字搜索,配套分页;
- 商品详情:展示蛋糕图片、尺寸规格(6寸/8寸/10寸)、甜度选项、价格、库存、描述,也可以加上销量排序;
- 购物车:添加商品、修改数量、删除、勾选结算;
- 下单选地址:从购物车勾选商品生成订单,填写收货人、电话、地址,还可以加上期望配送时间;
- 支付:接入模拟支付或真实支付通道;
- 订单管理:查看未支付/待发货/待收货/已完成订单,确认收货和评价。
蛋糕这种商品有一个特殊性——它和普通数码产品不同,不是标准品,客户很可能需要指定配送时间。所以订单表里最好加一个delivery_time字段(期望配送时间),管理端发货时可以参考这个字段安排生产档期。这个细节不复杂,但很体现业务思考,答辩时讲出来是个加分项。
2.2 管理端功能清单
管理端是给店家用的,功能比用户端少,但权限等级更高。
- 管理员登录:这部分跟用户登录共用一套用户体系,只是角色不同;
- 商品管理:上架/下架、新增/编辑蛋糕信息、维护库存和分类;
- 订单管理:查看所有订单、发货操作、处理退款;
- 数据统计:统计每日/每周销售额、订单量、热门商品排行榜。
这里有个常被忽略的点:管理端的接口不能跟用户端混在一起不加区分。建议在Controller的URL前缀上就分开,比如用户端接口放在/api/user/**,管理端接口放在/api/admin/**,然后通过拦截器区分权限,用户端校验登录,管理端额外校验管理员身份。这样代码结构清晰,后续加权限控制也好扩展。
2.3 一个必须想清楚的设计:订单状态机
订单状态的流转是整个系统的核心逻辑。最常见的设计是:
待支付(0)→ 待发货(1)→ 待收货(2)→ 已完成(3)
另外还有两个旁路状态:已取消(4)、退款中(5)。
状态只能按顺序推进,不能跳转。比如待收货的订单不能直接变成待支付,已取消的订单不能再次发货。处理状态流转时,建议用状态字段+条件判断双重校验,别相信前端传过来的状态值。比如用户点了取消订单,后端要先判断订单当前状态是待支付才能执行取消逻辑,否则直接拒绝。
边界情况也要提前想好:待支付订单超过30分钟自动取消(可以用定时任务扫表),已发货订单超过15天自动确认收货(也可以用定时任务实现)。这些逻辑不写,系统也能跑,但答辩时被问到线上订单怎么处理,你要能答得上来。
3. 数据库设计:这几张表和数据关系想清楚再动手
3.1 核心表结构
电商系统的表结构高度相似,蛋糕商城也不例外。核心表一共六张:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| user | 用户/管理员 | id, username, password, role, phone, email, created_time |
| category | 蛋糕分类 | id, name, sort |
| cake | 商品 | id, category_id, name, image, price, description, stock, status, sales, size_option |
| cart | 购物车 | id, user_id, cake_id, quantity |
| orders | 订单主表 | id, order_no, user_id, total_amount, status, receiver_name, receiver_phone, receiver_address, delivery_time, created_time |
| order_item | 订单明细 | id, order_id, cake_id, cake_name, price, quantity, subtotal |
size_option这种字段可以存“6寸,8寸,10寸”这样的字符串,用逗号分隔,简单实用。不要为了追求“规范化”硬拆一张规格表出来,毕设项目维护起来反而麻烦。
3.2 订单明细为什么要单独一张表
订单主表orders存的是“整单”信息,一条订单对应一个收货人和一个总金额。但一个订单可能包含多个蛋糕:一个生日蛋糕加两盒小甜品,所以在orders下面要挂order_item明细表,一行一个商品。
这地方有个细节值得注意:order_item表一定要冗余保存下单时的蛋糕名称和价格,也就是所谓的“价格快照”。原因很朴素——商品下架了、改价了,历史订单依然要能正确显示当时买了什么、多少钱。如果不冗余,你只能靠外键去关联cake表,一旦蛋糕被删掉,订单明细就查不出数据了。
3.3 几个想清楚再写的业务SQL
分页查询蛋糕列表,配合分类筛选:
sql复制SELECT * FROM cake
WHERE status = 1
AND (category_id = #{categoryId} OR #{categoryId} IS NULL)
AND (name LIKE CONCAT('%', #{keyword}, '%') OR #{keyword} IS NULL)
ORDER BY sales DESC
LIMIT #{offset}, #{pageSize}
统计某天销售额:
sql复制SELECT IFNULL(SUM(total_amount), 0) FROM orders
WHERE status IN (1, 2, 3)
AND created_time >= '2025-01-01'
AND created_time < '2025-01-02'
像这种统计SQL,status只算已支付、已发货、已完成这些“真的付了钱”的状态,待支付和已取消的订单不能计入销售额。日期范围用>=和<这种左闭右开方式,比BETWEEN AND更安全,因为你永远不知道某个时间点会不会带时分秒。
4. 支付功能:很多人卡在这里,其实有标准化流程
支付是电商系统里让新手最发怵的模块,但实际流程是标准的,别被表面复杂度吓住。
4.1 先用模拟支付打通整个链路
第一版建议先不做真实支付,搞一个模拟支付页面:用户点击“去支付”,跳到一个输入密码的页面,随便输入密码点确认,直接把自己的支付回调接口模拟调用一遍,订单状态变成待发货。
这样做的好处是先把“订单状态推进”这条主链路跑通,不受第三方接口的干扰。等本地逻辑没问题了,再替换成真实支付。
模拟支付的伪代码大概是这样的:
java复制// 模拟支付回调,实际项目中这里由微信/支付宝服务器调用
@PostMapping("/pay/mock/callback")
public Result mockCallback(@RequestBody PayCallbackRequest req) {
// 1. 查询订单
Order order = orderMapper.selectByOrderNo(req.getOrderNo());
// 2. 校验订单状态必须是待支付
if (order.getStatus() != 0) {
return Result.fail("订单状态异常");
}
// 3. 更新订单状态为待发货
order.setStatus(1);
orderMapper.updateById(order);
return Result.success();
}
这条链路的本质就是两个动作:校验当前状态合法,然后推进状态。真实支付的回调,无非是把这个逻辑套上签名验证和加密传输的外壳。
4.2 接真实支付通道时,轮到你写这套回调了
真实支付(微信/支付宝)的流程,跟上面模拟的逻辑基本一致,只是多了几个环节:
- 用户点击支付,后端调用微信支付预下单接口,返回一个支付二维码链接;
- 前端展示二维码,用户扫码支付;
- 微信服务器在用户支付成功后,调用你后端配置的回调URL;
- 回调里验证签名、更新订单状态、给微信返回“成功接收”的通知;
- 前端轮询订单状态或通过WebSocket刷新页面。
回调处理必须做幂等——微信回调可能会发多次,你的处理器要能接收相同的通知若干次而不产生副作用。最简单的幂等方案就是先查订单状态,只有待支付状态才执行更新。
4.3 支付安全:金额不要信前端
这条要刻在脑子里:所有涉及金额的判断,一律以后端查询到的数据为准。用户在前端下单时,订单总金额必须由后端从数据库中的商品价格重新计算,不能用前端传过来的totalAmount。数据库里的商品价格本身也要在后台保存时做校验,避免异常数据进入。
另外,用户密码、数据库连接信息这些敏感数据绝不能在配置文件里明文裸露。密码保存用BCrypt加密,配置文件里的数据库密码也不要写真实的,可以通过环境变量或配置中心注入。
5. 版本选择的坑:springboot版本太高,我劝你谨慎
这个坑很常见,尤其是现在官网默认下载的都是Spring Boot 3.x。你在网上搜索“springboot版本太高”能搜到一堆提问,不是没有原因的。
5.1 Spring Boot 3.x 到底带来了什么变化
Spring Boot 3.x 较之前有几个大的变化:
- 最低要求JDK 17。很多实验室电脑、学校机房的JDK还停在1.8,装上3.x直接起不来;
- javax.servlet包名改成了jakarta.servlet。这意味着旧代码里的
import javax.servlet.*全都得改; - Spring Security、MyBatis等各个组件的兼容版本都要同步升级,有些老教程的写法直接失效。
除非你的环境从一开始就明确是JDK 17+,否则我不建议毕业设计一上来就用Spring Boot 3.x。不是说3.x不好,而是你大概率没那么多时间去处理升级带来的连锁问题。
5.2 我推荐的黄金组合
做这类JavaWeb商城系统,最稳的搭配就是:
- JDK 1.8
- Spring Boot 2.7.x(最后一个支持JDK8的大版本)
- MyBatis 3.5.x + mybatis-spring-boot-starter 2.3.x
- MySQL 5.7 或 8.0
- Maven 3.6.x
这套组合在网上的资料最全,视频教程、文档、踩坑笔记都多,出了问题能很快搜到答案。等把业务逻辑全跑通,再升级版本也不迟。
5.3 配置里的几个要点
application.yml里几个核心配置:
yaml复制server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cake_shop?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: yourpassword
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
MySQL 8.0用com.mysql.cj.jdbc.Driver,MySQL 5.7用com.mysql.jdbc.Driver(新版驱动也兼容)。serverTimezone一定要配,不然连数据库会报时区错误。
文件上传大小限制也要记得配,蛋糕图片动不动几MB,不配的话上传接口会直接报错。这里补充一句,map-underscore-to-camel-case: true 一定要开,这样数据库里的created_time能自动映射到Java实体里的createdTime,省掉一堆别名操作。
6. 从能跑到能答辩:完成度提升清单
代码能跑、功能齐全只能算及格,想拿高分,下面这些细节值得花时间补上。
6.1 统一返回结构和全局异常处理
很多新手写的接口返回结果五花八门:有的返回字符串,有的返回Map,有的直接返回实体类。这毛病在接手多个模块之后就暴露了。建议从头定义一个统一的Result类:
java复制public class Result<T> {
private Integer code;
private String message;
private T data;
}
再加一个@RestControllerAdvice全局异常处理器,把参数校验错误、业务异常、系统异常统一转换成Result返回。好处是所有接口的返回格式一致,前端处理起来省心,代码也干净。
6.2 事务和参数校验不能少
下单这个操作涉及多张表:扣库存、写订单主表、写订单明细、清空购物车,任何一个环节出问题都不能让数据处于半完成状态。在业务方法上加@Transactional注解,保证全部成功或全部回滚。但要注意事务别加在Controller层,要加在Service层的业务方法上。
参数校验用@Validated配合@NotNull、@Size这些注解,在Controller入参直接拦截非法数据,别等业务代码里再去判断。比如创建订单时收货人手机号,直接用@Pattern注解校验格式,非法数据连Service层都进不去。
6.3 信息安全:能加一道是一道
- 密码存储必须加密。不要用MD5加盐这种老方案了,直接用Spring Security提供的
BCryptPasswordEncoder,或者jBCrypt库; - 所有Mapper操作使用
#{}预编译参数,不要用${}拼接SQL,从源头防SQL注入; - 登录状态校验用拦截器,放行登录注册等公开接口,其他接口校验Token或Session。
6.4 答辩和高频追问怎么准备
系统打开演示只是第一关,评委大概率会追问下面几个问题,提前把答案准备好:
- 订单状态是怎么流转的?为什么不能状态跳转?
- 下单时库存怎么扣?超卖问题怎么解决?
- 密码为什么要加密存?用的什么算法?
- 事务在哪个方法上加?不加会怎么样?
- Spring Boot的自动装配原理是什么?你了解starter的机制吗?
- Bean默认是单例还是多例?循环依赖你了解多少?
这些问题都是你这套系统里真实涉及的技术点,写代码的时候多留个心眼,别等答辩前一天再补概念。真到了说不清楚的时候,就老老实实说自己项目里怎么做的,也比背一段八股强。
按这个清单把项目过一遍,你的蛋糕商城系统就不只是“能跑”的水平了,逻辑严谨、功能闭环、细节到位,不管是交作业还是放到简历上,都有底气。这套系统的核心链路和设计思路,拿出去应对绝大多数JavaWeb场景,也完全够用。
