把“基于SSM的学生管理系统”“JSP图书商城”这类题目从毕业设计备选列表里划掉的那一刻,我才真正开始琢磨什么项目值得花大半个学期认真做。最终我确定的方向是SpringBoot线上潮鞋商城,一个多商户多用户购物平台,既有用户端、商户端,又有平台管理端,业务链路完整,技术点也不浮夸。做完之后回头看,这个题目最大的价值在于:它不只是一个“会CURD就行”的增删改查系统,而是能把电商核心流程、多商户数据隔离、权限控制、订单状态流转这些面试常问的东西全部串起来。如果你也在纠结毕业设计选题,或者想找一个能写进简历的SpringBoot项目,这篇内容基本覆盖了从选题、设计到落地、答辩的完整过程,源码也整理在文末说明里。
1. 这个选题到底香在哪:从“烂大街管理系统”到“多商户潮鞋商城”
1.1 为什么年年有人栽在“管理系统”上
每年到了毕业设计开题季,总有大量同学选择“某某信息管理系统”“某某后台管理平台”这类题目。不是说管理系统不能做,而是这类题目的同质化太严重,评委一眼就能看出工作量和技术含量。做过的人都知道,大部分管理系统的核心就是一张表、几个页面的CURD,难点几乎没有,答辩时能讲的内容也非常有限。更麻烦的是,这类项目很难展示你对业务的理解,因为“管理”本身不是一个具体场景,缺乏可以深挖的规则。
我见过一个比较典型的翻车案例:某同学选了一个“健身房会员管理系统”,代码写了不到两千行,数据库就三张表,答辩时老师问“会员过期续费是怎么处理的”,他只能答“手动修改到期时间”。这种项目就算功能能跑,也谈不上设计,更谈不上亮点。相比之下,电商商城类项目天然带有用户行为、商品状态、订单流转、库存扣减、资金结算这些有逻辑深度的场景,任何一个点都可以展开讲出东西来。
1.2 潮鞋商城的两层巧思:场景熟悉 + 多商户结构
为什么是潮鞋,而不是普通商品?原因很简单:潮鞋商品有明确的SKU属性(鞋码、配色、发售批次),有“限量”“热门”这类标签,天然适合做商品筛选和详情展示,演示效果比卖图书、卖日用品要好看得多。更关键的是,潮鞋领域的用户心智是“多品牌、多店铺”,这正好支撑了“多商户”的设计前提:不同商户上传自己的鞋款,平台做统一流量和交易管理,商户之间互不可见、独立经营。
多商户结构,也就是常说的B2B2C模式,是区分“毕业设计普通货”和“有一定设计感项目”的分水岭。单商户商城只需要考虑一个卖家,而多商户商城必须回答几个问题:
- 不同商户的商品怎么隔离存储?
- 用户一次购买多个商户的商品,订单怎么拆分?
- 商户登录后怎么保证只能看到自己的商品和订单?
- 平台方怎么审核商户入驻、怎么结算货款?
这些问题的答案会直接体现在数据库设计和接口设计里,答辩时每一条都能拿出来讲。这也是我把题目的最终形态定为“SpringBoot线上潮鞋商城,多商户多用户购物平台”的原因。
1.3 功能范围的收敛:不要一上来就想做“淘宝”
很多同学做毕设最大的问题不是不会做,而是想要的太多。今天想加秒杀,明天想加直播,后天想加推荐算法,结果做到最后期中期都过不了。我当时给自己定了一条硬规矩:核心链路必须完整,非核心功能一律砍掉。
最终收敛出来的功能范围是这样的:
- 用户端:注册登录、浏览商品、商品搜索筛选、购物车、提交订单、模拟支付、查看订单、收货确认、评价商品。
- 商户端:商户入驻申请、商品管理(上下架、库存、SKU)、订单发货、查看营收数据。
- 平台端:商户入驻审核、商品类目管理、平台数据看板、用户管理。
砍掉的东西包括:秒杀、优惠券、拼团、直播、推荐算法、物流轨迹对接。原因是这些功能要么对基础设施要求极高(比如秒杀要搞Redis队列),要么需要第三方接口配合(比如物流查询),在毕业设计周期内很容易失控。做减法不是偷懒,而是确保每个保留的功能都能做到“能演示、能讲清、能扛住提问”。这十条也是我给准备做商城类项目的人最真诚的建议。
2. 系统架构与角色权限:多商户多用户的“多”到底怎么落地
2.1 三种角色和他们的边界
多商户商城最核心的不是代码写得多花哨,而是角色边界是否清晰。系统里一共有三类用户:平台管理员、商户、普通用户。这三个角色对应的业务操作完全不同,权限控制是系统的第一道关卡。
平台管理员:负责审核商户入驻申请,管理商品类目,查看全平台订单和数据统计。理论上平台管理员不应该去修改商户的商品,否则就破坏了平台的中立性。
商户:只能操作自己店铺范围内的东西,包括商品管理、SKU维护、订单发货、店铺信息。商户之间是隔离的,A商户不能看到B商户的商品库存,更不能处理B商户的订单。
普通用户:只能操作自己账号下的数据,包括购物车、订单、收货地址、收藏和评价。用户也不能越权查看他人的订单信息。
这三个角色我用了一套用户表 + 角色字段来区分,登录后根据角色返回不同的菜单和可用接口。用户表和商户表在物理上是分开的,商户有独立的审核状态字段(待审核、已通过、已驳回、已冻结),这样处理比在用户表里塞一个“商户类型”字段干净得多。
2.2 技术栈选型:为什么是SpringBoot打底
整个项目的技术栈选择是基于“毕业设计既要能写明白,又要扛得住追问”这个原则来定的。
| 层次 | 选型 | 选它的理由 |
|---|---|---|
| 后端框架 | SpringBoot 2.7.x | 简化配置、生态成熟、简历上最常见 |
| ORM层 | MyBatis-Plus | 单表CRUD效率高,分页插件好用,多表SQL手写灵活 |
| 鉴权方案 | JWT + 拦截器 | 无状态、前后端分离友好,能讲清楚Token校验逻辑 |
| 缓存 | Spring Data Redis | 首页轮播、商品热门榜、Token黑名单,有一定技术含量 |
| 数据库 | MySQL 5.7/8.0 | 最常用的关系型数据库,事务支持可靠 |
| 前端 | Vue3 + Element Plus | 用户端和商户端都用,组件齐全,演示效果好 |
| 构建工具 | Maven | 最主流的Java构建方式 |
当时我也纠结过要不要用Spring Cloud Alibaba,加个Nacos、Feign什么的,显得体系庞大。后来想明白了一个道理:你不是在做一个生产级微服务平台,你是在做毕业设计。单体应用完全能支撑这个体量,微服务反而会让答辩焦点从业务逻辑转移到“为什么拆服务”“拆了有什么好处”这种你未必能讲清楚的问题上。单体优先,是务实的选择。
2.3 前后端分离和“后台三件套”的布局
项目前端用的是Vue3 + Element Plus,后端只提供JSON接口,前端通过Axios调用,生产环境打包后由SpringBoot的Static目录托管。这样做的好处是,答辩的时候既能展示“我懂前后端分离的开发模式”,又不需要真的去运维一套Nginx加两台服务器——本地演示一个SpringBoot进程就能全部跑起来。
用户端页面主要包含:首页(轮播、推荐潮鞋)、商品列表页(分类筛选、关键词搜索、按价格/热度排序)、商品详情页(SKU选择、尺码库存展示)、购物车(多商户商品统一展示、按商户拆分结算)、订单确认页、个人中心(订单列表、收货地址、评价入口)。
商户端是独立的后台界面,登录后进入店铺工作台:商品列表(上架/下架/编辑)、商品发布(图片上传、SKU设置、库存填写)、订单管理(发货操作、查看订单明细)、数据概览(订单数、销售额、商品数)。
平台端同样是一个后台界面:商户审核列表、类目管理、全平台订单查询、用户管理、数据统计。三个前端界面可以打包成同一个前端工程,通过路由守卫和动态菜单区分角色,这也是比较标准的企业后台做法。
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/register", "/api/public/**"); } }3. 核心业务流程拆解:下单、结算、发货、入驻背后的实现逻辑
3.1 登录与鉴权:JWT拦截器和ThreadLocal的配合
先聊登录。用户、商户、管理员共用一个登录接口,参数是账号和密码,后端校验通过后生成一个JWT Token返回给前端。Token里面只放userId和role,不敏感。前端把Token存在localStorage里,每次请求在Header中带上Authorization: Bearer xxx。
后端拦截器干两件事:第一,解析Token,如果解析失败或过期,直接返回401;第二,解析成功后把userId、role放到ThreadLocal里,这样Controller里面调LoginUtil.getUserId()就能拿到当前登录人,非常方便。
public class JwtUtil { private static final SecretKey KEY = Keys.hmacShaKeyFor("your-secret-key-please-change-in-prod".getBytes()); public static String generateToken(Long userId, Integer role) { return Jwts.builder() .claim("userId", userId) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600_000L)) .signWith(KEY) .compact(); } }权限校验这里有个容易忽略的坑:拦截器只解决“你是否登录”,不解决“你能不能操作这份数据”。商户删除商品时,SQL里必须带上merchant_id = 当前登录商户id这个条件,否则一个商户就能操作别家商品。这是典型的数据权限问题,我是在开发中后期被一个测试同学点醒才彻底补齐的,后面在踩坑章节会展开讲。
3.2 购物车和结算拆分:一个订单还是多个订单
用户加购的时候,购物车条目会同时记录商品SKU信息和所属商户ID。因为不同商户的商品归属不同,结算时就必须做拆分:用户勾选购物车里的多个商品,如果涉及三个商户,系统会创建三个订单,每个订单包含一个商户的商品集合。
实现方式不复杂,前端把勾选的购物车条目ID传给后端,后端查出所有条目后按merchantId分组,然后循环创建订单头(订单主表)和订单明细表。为了保持简单,一个交易批次里每个商户只会生成一笔订单,订单状态统一流转。
这里还有一个细节:订单表里需要冗余存一个merchant_id字段,而不是只通过订单明细反查。因为订单列表页需要按商户维度过滤,冗余字段能省一个子查询,而且逻辑上也直观。多商户系统的日常开发里,“该冗余的字段就要冗余”,这是和单商户系统不太一样的地方。
3.3 扣库存与下单的顺序问题:先校验还是先锁
潮鞋商城最怕的就是“超卖”,也就是库存只剩1双,结果同时有三个人下单成功。我采用的方案是乐观锁 + 条件更新,重点是扣减库存时加上“当前库存大于等于购买数量”这个条件。
UPDATE product_sku SET stock = stock - #{count} WHERE id = #{skuId} AND stock >= #{count}MyBatis-Plus的update方法返回受影响行数,如果返回值是0,说明库存不足,代码里直接抛出“库存不足”异常并让事务回滚。这里我额外加了@Transactional,保证“扣减库存”和“创建订单”两个操作要么都成功,要么都失败。这个写法我在答辩前重点准备了,面试问“你怎么防止超卖”就是照这个思路回答。
3.4 模拟支付与平台结算:毕业设计也要讲清楚钱怎么分
真实支付需要接入微信支付/支付宝,毕业设计没必要也不会给你通过审核。我做的方案是“模拟收银台”:用户下单后进入支付页,点击“模拟支付”,后端生成一条支付流水,将订单状态从未支付改为已支付,同时通知商户。
比较有意思的是平台结算逻辑。多商户商城必须回答一个问题:用户付的钱怎么分?我设计得比较简练:支付成功后,订单金额拆成“商户可结算金额”和“平台服务费”两部分,平台服务费比例在商户入驻时就约定好,比如5%。平台端有一个结算单列表,记录每笔订单平台应收多少、商户应收多少,管理员可以执行“确认结算”,此时订单的结算状态变为已结算。
这一整套逻辑并不复杂,但能体现你对多商户电商业务的理解。答辩的时候老师很可能会问“平台赚什么钱”“商户怎么提现”,这两个问题能立刻回答清楚,项目档次就不一样了。
3.5 商户入驻和审核:只有“审核通过”才能开张
商户入驻的流程走了一个标准闭环:用户提交入驻申请,填写店铺名称、联系人、手机号、经营品类,系统生成一条入驻申请记录,状态为“待审核”。平台管理端看到申请后点击通过,商户账号的状态变为“已通过”,此时该商户才能发布商品。如果被驳回,会记录驳回原因,申请者可以修改后重新提交。
这里有一个隐蔽的需求点:普通用户注册之后,并没有商户后台的入口。只有审核通过的账号,登录后返回的角色才是“商户”,前端才会展示商户端菜单。所以那个“审核状态”字段不只是在商户表里放着好看,它是登录后返回不同页面权限的依据。
4. 数据库表设计:多商户商城的地基怎么打才稳
4.1 核心表清单和表之间的关系
表结构是答辩时高频被翻出来的内容,我整理成了下面的主要表:
| 数据域 | 表名 | 说明 |
|---|---|---|
| 用户 | user | 账号、密码(BCrypt加密)、昵称、手机号、角色 |
| 商户 | merchant | 店铺名称、联系人、审核状态、平台服务费率 |
| 类目 | category | 潮鞋类目,比如篮球鞋、跑步鞋、板鞋 |
| 商品 | product | 商品基本信息、所属商户、类目、上下架状态 |
| 库存 | product_sku | 尺码、颜色、库存数、价格,一个商品多个SKU |
| 购物车 | cart_item | 用户ID、SKU ID、数量、选中状态 |
| 订单 | order | 订单号、用户ID、商户ID、总金额、状态、支付时间 |
| 订单明细 | order_item | 订单ID、SKU ID、商品快照、单价、数量 |
| 支付流水 | payment | 订单ID、支付金额、支付状态、支付时间 |
| 结算单 | settlement | 订单ID、平台服务费、商户结算金额、状态 |
| 收货地址 | address | 用户ID、收货人、电话、省市区、详细地址 |
| 平台轮播 | banner | 图片URL、跳转链接、排序值 |
一个商品对应多个SKU,这是潮鞋类目的刚需。以一双热门球鞋为例,可能有40码到45码六个尺码,有经典配色和限量配色两种SKU,每个SKU价格和库存不同。订单明细表里保存的是“商品快照”——包含商品名、图片、SKU描述、单价,原因是商品后续可能改名、改价,但订单作为交易凭证必须保留当时的信息。
4.2 为什么不滥用外键,为什么金额必须用decimal
多商户系统里跨表查询特别多,如果每个表都加物理外键,插入和更新会有额外性能损耗,而且在分库分表场景下外键基本不可用。我实际设计时,表与表之间只保留逻辑关联,也就是merchant_id、sku_id、order_id这些普通索引字段,关联关系写在SQL的JOIN条件里。这样开发灵活,数据量上来后也方便拆表。
金额类型是另一个经常被忽视的点。刚开始有个同事把订单金额设计成了double,结果计算总价时出现了一长串小数,原因就是浮点数的二进制精度问题。所有金额字段统一使用DECIMAL(10, 2),Java实体类用BigDecimal接收。这是电商项目里非常基础但重要的一个规范,写进设计文档里会显得你很专业。
4.3 订单号的生成规则:时间戳加随机数不只是为了好看
订单号我采用的格式是:yyyyMMddHHmmss + 4位随机数 + 用户ID后四位,长度大概20位。为什么不能只用自增ID?因为订单号在支付、对账、客服沟通里是要给人看的,自增ID会暴露订单量,而且跨系统通信时自增ID太容易猜测。用时间戳加随机数,既保证了大致有序,也让每个人都能从订单号里读出下单时间,演示时也更好解释。
如果以后要做高并发,这个方案会产生重复风险,可以换成雪花算法。但在毕业设计这个场景,时间戳加随机数已经足够,而且答辩时你可以主动说出“如果订单量大,可以升级为雪花ID”,这个加分项是白给的。
5. 开发过程中踩过的坑:超卖、越权、文件图片存储这类问题
5.1 数据权限漏洞:看似只能看到自己的,其实能操作别人的
这个坑说出来挺丢人的,但很值得写。最初我在商户端删除商品时,只写了deleteById(productId),没带商户ID条件。理论上登录商户B,只要知道商品ID,就能把商户A的商品删掉。虽然前端菜单根本不显示别人的商品,但只要打开控制台手动调接口,立刻越权。
后面我花了一个晚上把所有商户接口、用户接口全部梳理了一遍,统一改成“先查出归属,再判断当前登录人是否有操作权限”的写法。比如商户修改商品:
Product product = productService.getById(productId); if (!product.getMerchantId().equals(currentMerchantId())) { throw new BusinessException("无权操作该商品"); }这条经验换成一句话就是:前端隐藏按钮不是权限控制,后端接口校验才是。答辩时主动提到这一点,评委大概率会追问你怎么做的权限控制,正好可以把JWT、ThreadLocal、数据权限三层讲一遍。
5.2 库存为负数:乐观锁解决了超卖,但别忘了校验商品状态
超卖问题用乐观锁解决之后,我又遇到了一个次生问题:一个已经下架的商品,如果用户在商品详情页停留了很久再下单,接口还是会去扣库存。因为扣库存SQL只看库存条件,没检查商品状态。
后来在下单接口里增加了前置校验:商品必须处于上架状态、SKU必须是启用状态、库存充足、商户状态正常,四条校验都通过才允许创建订单。这个顺序必须放在扣库存之前,否则会出现“扣了库存但订单创建失败”的尴尬情况。事务可以保证数据一致性,但提前拦截无效请求能让用户的体验好很多。
5.3 图片上传:本地目录存储加虚拟路径映射
商品图片是电商项目的门面,一开始我把图片转成Base64塞到数据库字段里,结果商品列表接口返回的数据量大到页面明显卡顿。后来改成文件上传方案:后端接收MultipartFile,保存到本地的/upload/目录,文件名用UUID + 原扩展名,然后把文件路径返回给前端。
为了让前端能通过URL直接访问图片,SpringBoot里要配置一个虚拟路径映射,把请求路径/images/**映射到本地磁盘目录:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceLocations("file:" + System.getProperty("user.dir") + "/upload/"); } }这个方案在小体量项目里完全够用,部署时也只需要多拷一个upload目录。如果是大项目,就得考虑OSS或MinIO了,不过我建议毕业设计阶段就到本地存储为止,讲清楚“生产环境可以换对象存储”就足够了。
5.4 跨域问题和缓存设计的两个小坑
前后端分离开发时,前端开发服务器运行在5173端口,后端在8080端口,直接请求接口会被浏览器拦截。解决方式是后端加一个全局CORS配置,允许指定来源访问。这里注意一个细节:allowedOriginPatterns别用*加allowCredentials(true)的组合,浏览器会直接报错,要用具体的来源列表或带通配的Pattern。
Redis缓存我最初只缓存了首页banner和商品分类,后来商品搜索列表也加了Redis缓存,还会在商品上下架时手动删除对应的缓存key。当时看效果不错,但实际上碰到过一个问题:发布新商品后,商品列表迟迟不显示新数据,因为列表缓存的key没有和商品变更联动。所以我把缓存key设计成包含“商户ID + 类目ID + 页码”的格式,并在商品上下架接口里主动清理相关列表缓存。因为毕业设计的数据变更频次不高,这种“主动失效”的方案比设置复杂过期策略要直接得多。
6. 拿到源码后怎么跑起来,答辩怎么讲才有亮点
6.1 源码结构和初始化步骤
一个合格的SpringBoot项目源码,拿到手后应该可以“三步跑起来”:创建数据库、改配置文件、启动项目。我的项目目录结构大致是这样的:
shoe-mall ├── sql/ # 数据库初始化脚本 │ └── shoe_mall.sql # 建库、建表、演示数据 ├── upload/ # 上传图片本地目录 ├── src/main/java │ ├── config/ # 跨域配置、拦截器注册、Redis配置 │ ├── controller/ # 用户端、商户端、平台端接口 │ ├── service/ # 业务逻辑层 │ ├── mapper/ # MyBatis-Plus的Mapper接口 │ ├── entity/ # 实体类 │ ├── common/ # 统一返回结果、异常处理、工具类 │ └── ShoeMallApplication.java ├── src/main/resources │ ├── application.yml # 数据源、Redis、文件上传配置 │ └── mapper/ # 复杂SQL的XML文件 ├── frontend/ # Vue3前端工程 │ ├── src/views/ # 用户端页面 │ ├── src/views/merchant/ # 商户端页面 │ ├── src/views/admin/ # 平台端页面 │ └── vite.config.js # 本地开发代理配置 └── README.md # 项目说明和部署文档数据库脚本里预置了三类演示账号:一个平台管理员、两个审核通过的商户、几个普通用户,还造了一些潮鞋商品数据和订单数据。这样演示的时候不用现场注册、现传图片,打开首页就能看到效果。
6.2 本地启动会遇到的两个最常见问题
先说端口占用。SpringBoot默认8080,前端开发Server默认5173,如果你本机端口被其他进程占了,启动会直接报错。解决方式很简单,改application.yml里的server.port,或者改前端vite.config.js里的代理目标,保持两边一致即可。
再说前端运行依赖。前端工程需要Node.js环境,建议用长期支持版本,先npm install再npm run dev。如果node_modules安装慢,可以换国内镜像源。这里提醒一句:源码压缩包里通常不会带node_modules,一定要先装依赖再跑前端,这个步骤不能跳过。
6.3 答辩现场怎么演示和讲思路
答辩的时间一般不长,演示要抓重点,我建议按这个顺序来:
- 先演示用户端完整购物场景:注册/登录 → 浏览潮鞋商品 → 点击详情选尺码SKU → 加入购物车 → 结算生成订单 → 模拟支付 → 查看订单状态。这条链路跑通,业务闭环就展示清楚了。
- 切换到商户端,演示发货流程:进入订单管理,找到已支付订单,执行发货,订单状态变为已发货。
- 切换到平台端,展示商户审核流程和数据看板,说明平台如何介入管理。
- 打开数据库设计文档或表结构,重点讲“多商户如何隔离”“订单如何拆分”“库存如何防超卖”。
答辩时老师大概率会问这几个问题:项目难点是什么、某个状态是怎么流转的、如果并发高怎么办。对应的回答思路分别是:多商户数据权限隔离、订单状态机设计(待支付→已支付→已发货→已收货→已完成)、乐观锁扣库存配合事务保证一致性。
6.4 如果还想往上加分,可以在哪些点上扩展
做完基础版本之后,时间和精力允许的话,有几个扩展点性价比很高:
- 商品搜索接Elasticsearch,替换掉SQL模糊查询。这个改动比较大,但讲出来就是一个完整的进阶亮点。
- 秒杀场景复用已有的SKU库存表,加上Redis预扣库存,可以讲高并发削峰。
- 后台增加Spring Security替换手写拦截器,虽然学习成本高一点,但安全这块专业度会明显提升。
- 图表展示订单趋势、销售排行,使用ECharts很容易实现,视觉效果好,答辩时挺加分的。
我的建议是,如果当前版本已经稳定,优先保证把核心链路吃透,扩展点只在你有余力的时候再加。毕业设计的本质是“用一学期的时间证明你能独立完成一个完整软件系统的分析、设计、开发、测试和文档编写”,而不是堆砌功能数量。
源码头尾的README里我写了详细的启动步骤、账号密码和演示脚本,照着走就能跑出效果。希望这篇内容能帮你少走几个我在开发时踩过的弯路,也希望你答辩那天能从容地把每一处设计讲清楚。