又是一年毕业设计高峰期。我身边不少同学收到的题目五花八门,最典型的就是这个——SpringBoot装修建材商城。有些人第一反应是"这不就是一个网上卖货的系统吗",然后直接按普通的增删改查去写,结果答辩的时候被老师一句"你这个项目和课程设计有什么区别"就卡住了。其实这类题目看起来朴素,背后的东西并不简单。它不是一个只有几个CRUD界面的玩具系统,而是要求你真正理解"交易平台"四个字——商品、库存、购物车、订单、支付、物流、评价,每一步都要串起来。
我做完这个题目之后最大的体会是:SpringBoot只是工具,真正的核心是业务建模和交易链路设计。这篇文章会把我的完整思路、数据库设计、核心代码逻辑、踩过的坑和答辩准备全部梳理一遍,希望给正在做这个题目的计算机、软件工程方向的同学一些参考。文章里涉及的技术方案都是我实际跑通过的,可以直接照着抄。
1. 题目拆解:装修建材商城到底要交付什么
1.1 三层关键词背后的真实需求
"基于SpringBoot装修建材商城"这个题目,我习惯把它拆成三层来看。
第一层是"装修建材商城",这是业务层。建材和普通商品有明显区别:建材有品牌、规格、型号、材质、计价单位之分,电线按卷卖、瓷砖按片或按平米卖、乳胶漆按桶卖。同一个商品下往往有多个规格,比如同一款瓷砖有300x300、600x600两种尺寸,价格和库存都不一样。如果只做一个普通商城,把所有字段塞进一张商品表里,后面做订单、做库存、做筛选的时候就会非常别扭。
第二层是"在线交易平台",这是模式层。这个词意味着系统必须具备完整的交易闭环:商品展示、加入购物车、提交订单、支付、发货、确认收货、售后。每个环节都不是孤立的,订单状态要跟着操作流转,库存要跟着订单扣减,支付回调要把订单状态翻转为已支付。很多同学只做了"下单"就停了,没有做支付、发货、收货的完整流转,这在答辩时会被一眼看穿。
第三层是"SpringBoot",这是技术层。它决定了你用什么方式把业务落地。相比更早期的SSH、SSM框架,SpringBoot通过自动装配和约定优于配置的方式把搭建成本降到了极低,让开发者把精力集中在业务逻辑本身上面。
1.2 功能清单与技术边界
做需求分析的时候,我不建议直接上手写代码。先把功能按三个端梳理清楚:用户端、商家端、管理端。
用户端负责完整的购买链路——注册登录、浏览商品、按类目搜索、加入购物车、提交订单、模拟支付、查看订单列表和详情、确认收货、评价商品、管理多个收货地址。商家端负责商品侧的操作——商品上架、编辑、下架、管理库存、处理订单发货、查看自己的销售数据。管理后台是平台运营视角——用户管理、商家入驻审核、商品类目管理、订单监控、评论管理和数据统计看板。
这里有个关键点:技术边界。不要一上来就想用微服务、消息队列、分布式事务这些重武器。一个本科毕业设计,单体SpringBoot应用完全可以承载以上所有功能。重要的是在单体架构里把业务逻辑的层次理清楚——Controller层只做参数接收和响应封装,Service层存放业务规则,Mapper层负责数据访问,事务边界画在Service方法上。这种分层意识,是评审老师真正看重的东西。
2. 技术栈选型:为什么SpringBoot是这个题目的最优解
2.1 从SSH/SSM到SpringBoot的演变逻辑
我为什么坚持用SpringBoot?因为我实际经历过配置地狱。早几年做SSH项目,光是配置一堆XML就要折腾好几天;SSM好一些,但集成三方框架时还是要自己找依赖、对版本、写各种配置类。SpringBoot把这些全部变成了"约定",主类加一个main方法就能启动,内嵌Tomcat不用单独部署,starter依赖把常用组件直接打包好。从新建项目到浏览器里跑出第一个页面,可能只需要几分钟。
这些时间省下来之后,你可以把精力真正放在业务设计上。对毕业设计而言,时间是最宝贵的资源,SpringBoot明显是效率最优的选择。
2.2 配套组件的组合思路
我最终选定的技术栈是:SpringBoot 2.7 + MyBatis-Plus + MySQL 8 + Redis + Spring Security + JWT,前端部分用了Vue 3。如果不想写前后端分离,也可以选Thymeleaf服务端渲染,本质上不影响后端设计。
每个组件都有它的理由。MyBatis-Plus提供BaseMapper,单表CRUD几乎不用手写SQL,分页插件也很成熟,做起毕设来效率很高。MySQL的数据量级和功能完全撑得住这个场景。Redis用来做验证码缓存和购物车缓存,同时也是一个能向老师展示"我懂缓存"的加分点。Spring Security做登录认证、密码加密和接口权限控制,配JWT实现无状态认证,是目前前后端分离场景的主流方案。
这里提醒一句:组件越多,出问题的地方越多。如果对Spring Security的过滤器链不熟,完全可以用自定义拦截器方案代替——通过拦截器校验JWT,一样能达到权限控制目的。选择自己驾驭得了的技术,比盲目堆新技术重要得多。我就见过有人硬上Spring Cloud,结果服务之间调不通,最后不得不回退重写。
2.3 一个被问烂的问题:为什么不上微服务
答辩时几乎必有一个问题是:"你这项目为什么不用微服务?"
我当时回答的思路是:装修建材商城的用户规模和业务复杂度,处在一个单体架构完全能承受的水平。如果强行拆成用户服务、商品服务、订单服务、支付服务,就要引入服务注册发现、网关、分布式事务、消息队列,整套链路的学习成本和运维成本都上来了。单体架构在这个场景下做清晰的模块化划分,把接口按业务域组织好,反而更利于快速开发和调试。
这个答案要的不是"我不会微服务"的回避,而是"我知道微服务是解决什么问题的手段,但当前场景不需要"的判断力。这个思维本身就是答辩的加分项。
3. 模块拆解:三端一平台的功能边界
3.1 用户端:从注册到下单的完整链路
用户端是整套系统的门面,也是我用时最多的部分。
注册登录这块,我选择JWT加Spring Security。密码用BCrypt加盐哈希之后存库,绝对不能明文落库,这是一票否决项。登录成功之后返回token,前端存到本地,每次请求在请求头里带上,后端通过过滤器校验通过后才允许访问受保护接口。登录态过期时间我设置成2小时,并做了续签逻辑:如果用户token剩余有效时间不足30分钟,后端会在响应头重新签发新token。这个细节能体现你对会话管理的思考。
商品浏览部分做了分类导航、关键字搜索、价格区间筛选和销量排序。建材类商品的详情页要展示规格参数,比如尺寸、材质、品牌、计价单位。规格参数我单独建了一张商品参数表,没有往商品表里堆字段,数据看起来干净,后期扩展也方便。
购物车是用户端交互最频繁的区域。我用的是Redis缓存加数据库落地的双方案:用户未登录时,购物车数据存在浏览器本地存储;登录后把本地购物车同步到后端Redis,结算时从Redis读取并生成订单。这样体验流畅,也体现对缓存场景的理解。
3.2 商家端:商品生命周期管理
商家端主要服务建材商户。商家登录后进入自己的店铺后台,可以发布商品、维护库存、处理订单。核心交互是商品上下架:商品发布时若库存为0,系统自动转为下架状态;商家补货后可以手动重新上架。
订单处理上,商家要对已支付订单执行发货操作,发货时填写物流单号,订单状态从"待发货"变为"已发货"。如果商品有质量问题,商家还要能处理退款申请。每次操作都记录操作时间,形成操作日志,方便后台审计。
我在写商家端时特别注意了一个点:建材的库存单位经常不一样。有的按桶、有的按卷、有的按平米,同一商品的不同规格库存数量也不同。所以库存字段不能只放在商品表,必须挂到SKU粒度上,才能真实反映"这款瓷砖600x600还有80片,300x300已经没货了"这个状态。
3.3 管理后台:权限、审核和数据看板
管理后台面向平台运营人员。功能包括用户管理、商家入驻审核、商品类目管理、全平台订单监控、评论管理和数据统计。
权限方面,我用了用户、角色、用户角色关联三张表。角色分成平台管理员、商家、普通用户三种。接口层通过自定义注解加拦截器判断当前用户角色,只有管理员能访问后台接口。这个设计是标准的RBAC模型,写起来不复杂,但能体现出权限意识。
数据统计部分,我用定时任务每天凌晨汇总前一天的商品销量、订单金额、新增用户数,写入统计表,后台首页展示近7天的趋势图。这块用后端接口输出聚合数据、前端用图表库渲染即可。很多同学把统计做成实时count,每次打开页面都去扫全表,数据量大了会卡。我设计时特意做了预聚合,答辩时说出这一点,老师会认可你的数据意识。
4. 数据库设计:把交易系统的地基打牢
4.1 核心表结构总览
数据库是整个项目里最不应该偷懒的部分。我最终用的核心表清单如下:
| 表名 | 说明 | 关键字段 |
|---|---|---|
| sys_user | 用户表 | username, password, phone |
| sys_role | 角色表 | role_name, description |
| user_role | 用户角色关联表 | user_id, role_id |
| category | 商品类目表 | parent_id, name, level, sort |
| product | 商品主表(SPU) | name, category_id, brand, status |
| product_sku | 商品规格表(SKU) | product_id, spec_name, price, stock |
| product_param | 商品参数表 | product_id, param_name, param_value |
| cart_item | 购物车表 | user_id, sku_id, quantity, checked |
| user_address | 收货地址表 | user_id, receiver, phone, province, detail |
| order_info | 订单主表 | order_no, user_id, total_amount, status |
| order_item | 订单明细表 | order_id, sku_id, quantity, price |
| payment_record | 支付流水表 | pay_no, order_id, user_id, amount, status |
| goods_review | 商品评价表 | order_item_id, user_id, rating, content |
这里说一下类目表的设计。我做成了自关联的树形结构,支持一级和二级类目,比如"地板/瓷砖"是一级,"实木地板""复合地板"是二级。商品挂到二级类目下,前台按一级类目聚合展示。这种设计比平铺一个字段列表更合理,也方便以后扩展三级类目。
4.2 商品SPU/SKU:一个标准但又容易偷懒的建模
在建材电商系统里,SPU/SKU模型是一个绕不开的点。SPU是"标准化产品单元",比如"某品牌800x800瓷砖";SKU是"具体销售单元",比如同一款瓷砖的"浅灰色600x600""深灰色600x600"。
我在做这个项目之前看过不少同学的设计,为了图省事把所有字段压进一张product表,规格用逗号拼接字符串。短期看很简单,但到了购物车和订单部分就会暴露问题:下单时买的到底是哪个规格?sku_id怎么关联?价格取哪一档?
我最终的建模是:product表只存商品公共信息,product_sku表存每个规格的价格、库存和规格名。下单时前端传的是sku_id,后端通过sku_id拿到价格和库存,生成订单明细。这个设计不仅逻辑清晰,也方便商家给同一商品的不同规格设置差异化价格,还方便后续做库存预警。
4.3 订单状态机:跑通交易链路的关键
订单状态是交易系统的灵魂。我设计了这样的状态流转:待支付(0) -> 已支付(1) -> 已发货(2) -> 已完成(3);待支付时可取消(4);已支付后可申请退款(5)。
状态流转的必要条件是状态校验。代码里每一条状态变更都要先检查当前状态是否是合法前置状态,再执行更新。比如支付订单时,先查订单当前状态,只有待支付状态才能被翻转为已支付:
@Transactional public void payOrder(String orderNo) { OrderInfo order = orderInfoMapper.selectByOrderNo(orderNo); if (order == null) { throw new BusinessException("订单不存在"); } if (!OrderStatus.WAIT_PAY.getCode().equals(order.getStatus())) { throw new BusinessException("订单状态不正确"); } order.setStatus(OrderStatus.PAID.getCode()); order.setPayTime(LocalDateTime.now()); orderInfoMapper.updateById(order); }这样做的好处是:即使前端误操作或者有人恶意篡改请求参数,后端也能挡住非法状态跳转。答辩时老师如果问"如何保证订单不会被重复支付",就用这段逻辑解释——支付回调里做了订单状态前置校验,已支付过的订单不会再次进入支付分支。
5. 核心链路实战:从商品浏览到订单支付
5.1 登录鉴权与权限控制
JWT加Spring Security的组合,是我实际跑过很多遍后觉得最稳妥的。登录接口接收用户名和密码,校验通过后生成token返回;前端请求受保护接口时,在请求头里带上"Bearer token"。后端通过JWT过滤器解析token,把用户信息放进安全上下文。
需要注意一个细节:token生成时要带上用户id和角色信息,方便后续在业务代码里取当前登录用户。我封装了一个工具方法从SecurityContextHolder里拿当前用户id,避免每个Controller都去解析token。
密码处理方面,注册时用passwordEncoder.encode()加密,登录时用passwordEncoder.matches()比对。不要自己对密码做各种位移、拼接的"加密",那些不是标准方案,评审老师看到BCrypt会认可你的安全意识。
5.2 购物车:Redis与数据库的取舍
购物车我采用"登录前后分开处理"的策略。用户未登录时添加到浏览器本地存储;登录后点击同步按钮,把本地购物车JSON提交到后端,后端合并到Redis并同时落库。Redis的key设计为cart:userId,value用hash结构,field是skuId,value是数量。
为什么用Redis而不是纯数据库?因为购物车是高频读写场景,每次增减数量都打MySQL会很浪费资源。Redis读写都在内存,对这类场景更友好。但纯Redis又有数据丢失风险,所以同步落库,定期清理无效购物车项。这个"缓存+落库"的思路本身就是一个很好的答辩素材。
有一点要注意:Redis里的购物车数量和数据库里的cart_item表要保持最终一致。我在每次结算成功之后,会把Redis里对应的购物车项删掉,同时更新数据库中的状态,不让脏数据残留。
5.3 下单与库存扣减:事务和并发
下单是整套系统里最重的流程,也是bug高发区。完整流程包括:校验SKU存在且上架、校验库存充足、生成唯一订单号和订单主记录(状态为待支付)、生成订单明细、扣减SKU库存、清空购物车对应商品。
整个过程必须在一个事务里,任何一个环节失败都要回滚。@Transactional加在Service方法上,这里我特别提醒一个坑:事务失效。如果在同一个类内部通过this调用另一个加了@Transactional的方法,事务不会生效,因为Spring事务是通过AOP代理实现的,this调用绕过了代理。我当时的做法是把下单流程拆成下单Service和事务Service两个类,或者把整个流程放在同一个事务方法里,避免内部自调用。
库存扣减的SQL建议用原子更新,不要"先查再判再更新"。我实际碰到过并发下单场景下库存被扣成负数的情况,后来改成这样:
UPDATE product_sku SET stock = stock - #{quantity} WHERE id = #{skuId} AND stock >= #{quantity}受影响行数为0就表示库存不足,直接抛异常回滚。这种带条件的原子更新,把判断和更新放在一条SQL里,并发环境下数据一致性才有保障。
支付模块在毕设里一般不用真的接入第三方支付,我做了模拟支付:用户点击支付按钮,后端生成支付流水记录,状态设为支付中,再模拟异步回调把订单从待支付翻转为已支付,同时更新支付流水状态。这个模拟回调虽然是假的,但把"回调幂等"的理念做进去了:回调时先查流水状态,已成功的回调不重复处理订单。
6. 毕设跑通之后:文档、演示与答辩
6.1 我在这个项目里踩过的坑
第一个坑是MyBatis-Plus的自动填充时间字段不生效。原因是我在insert时手动set了createTime,而自动填充策略是字段为空才填充,两边逻辑冲突。解决方法是统一:要么全部手动set,要么全部交给自动填充,不要混用。这个坑排查了我一个晚上,查出来之后发现就是个很小的细节。
第二个坑是前后端联调的跨域问题。前端地址是localhost:5173,后端是8080,端口不同直接跨域。我写了一个全局跨域配置类,注册CorsFilter:
registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600);注意allowCredentials设为true时,allowedOrigins不能直接用"",必须用allowedOriginPatterns,这点很容易踩。我最初用allowedOrigins("")配合allowCredentials(true),结果前端请求一直报跨域错误,换成allowedOriginPatterns才解决。
第三个坑是商品图片上传后的静态资源映射。图片统一上传到本地磁盘目录(通过配置项指定),前端直接访问不了,需要在配置里加资源映射,把磁盘路径映射到虚拟路径/upload/**。不要在代码里写死绝对路径,否则换环境就崩。
6.2 文档、演示和答辩的准备
毕设除了系统本身,还有三样东西要准备:需求分析文档、数据库设计说明书、答辩PPT。我的经验是文档必须跟代码一一对应。
需求分析部分,把三个端的用例图画清楚,每个用例要能对应到代码里的一个或一组接口。数据库设计说明书里,把每个表的字段说明、索引设计列出来,尤其写清楚订单状态机的流转图。这会让老师觉得你的系统设计是完整的,不是临时拼凑的。
答辩演示时,不要只点一个商品加个购物车就收工。我当时的演示顺序是:
- 注册登录
- 浏览商品,测试搜索和筛选
- 加入购物车
- 提交订单
- 模拟支付
- 切换到商家端发货
- 切回用户端确认收货
- 发表评价
- 切到管理后台查看统计图表
整个闭环走下来,老师会觉得这套系统是完整可用的,这个印象非常重要。
6.3 答辩高频问题清单
最后整理一份我在模拟答辩中积累的高频问题,附上参考思路:
| 老师常问 | 参考回答思路 |
|---|---|
| 为什么选SpringBoot? | 相比SSM更简洁,自动装配解决集成成本,适合单体快速交付 |
| 一个表为什么拆这么细? | SPU/SKU分离,订单主表明细分离,各司其职 |
| 库存是怎么扣的? | 原子update加事务,前置状态校验,并发下不超卖 |
| token过期怎么办? | 无状态JWT,短有效期加续签机制 |
| Redis挂了怎么办? | 购物车数据已落库,可从数据库恢复 |
| 你这个项目最难的点? | 订单状态机流转、库存扣减的并发控制、权限体系 |
这些问题只要在开发时认真思考过,答辩就不会慌。
项目做完之后我最大的感受是:毕业设计不是比谁堆的技术名词多,而是比谁把业务逻辑想得透。装修建材商城这个题目看着普通,但真正把它做扎实——库存粒度拆到SKU、订单状态走完闭环、权限体系覆盖三端——你学到的东西比单纯看教程多得多。如果你也在做这个题,我的建议是别急着敲代码,先把数据库设计图画清楚,把订单状态流转想明白,再动手也不迟。到那时候你会发现,SpringBoot只是一个称手的工具,真正值钱的是你对"交易这件事"的理解。