
两年前有人问我商城类项目的毕业设计到底选什么技术栈合适。我当时给的答案跟现在一样SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 这一套做成母婴商城就是最稳的练手方案。母婴商城听起来只是“电商换了个类目”拆开了看商品展示、用户体系、购物车、下单、支付回调、订单管理、会员积分这些核心链路一个不落典型的前后端分离项目。源码我完整读过模块划分干净代码规范度不错SQL 脚本和说明文档都齐全。用来做毕业设计、课程设计或者给从 Java 后端转全栈的人练手都非常合适。这套系统能干什么用户端注册登录、分类浏览、关键词搜索、购物车加减、提交订单、模拟支付、查看订单状态、积分累积管理端维护商品分类、上下架商品、处理订单发货业务闭环是完整的。这篇文章我不讲虚的直接把系统的架构思路、后端核心逻辑、前端联调方案、数据库设计要点和部署阶段踩过的坑一条条说清楚拿到源码的同学按这个思路去捋代码会省很多时间。1. 项目全貌与选型拆解1.1 母婴商城为什么非走前后端分离用户端是家长群体浏览商品、加购物车、下单集中在手机和 PC 浏览器上管理端是运营人员处理商品和订单两个端的页面形态、操作习惯差异很大。前后端分离之后用户端可以做单页应用管理端也可以用独立的布局模板两边互不干扰。对小体量的商城项目来说前后端分离带来的最大好处其实是部署清晰。前端打包成静态文件扔到 Nginx 或者后端静态资源目录里后端独立打成 Jar 包运行任何一端出了问题都方便单独排查。接口测试直接用 Postman 或者 Apifox 调不再依赖页面渲染出结果。这个能力恰好也是现在企业里真实电商项目的主流协作方式做完这个项目前后端接口约定、联调流程这些经验是能直接迁移到工作中的。有不少同学问过为什么不做成 JSP 或者 Thymeleaf 那种服务端渲染。我只能说母婴商城这种业务落点清晰、页面交互丰富的项目用前后端分离能更好体现 Vue3 的组件化优势。而且你拿它写简历或者答辩的时候“前后端分离 RESTful 接口”这个描述本身就比“服务端模板渲染”更贴近当下的技术趋势。1.2 技术栈选型背后的逻辑技术栈选型不是越新越好而是要看生态成熟度和学习成本匹配。SpringBoot2 虽然 SpringBoot3 已经出了几年但 2.x 依然是资料最多、坑最少、第三方 starter 兼容性最好的版本。对新手来说搜一个问题能搜到一堆现成答案比版本多新重要得多。项目里各个依赖的版本都锁定过启动不会出现莫名其妙的版本冲突。Vue3 Element Plus 已经是目前前端组件库的主流搭配组合式 API 写业务逻辑比 Vue2 的选项式更清晰。状态管理用的是 Pinia比 Vuex 轻TypeScript 支持也更自然。前端工程用 Vite 做构建冷启动速度快代理配置也简单。MyBatis-Plus 解决的是 CRUD 重复劳动的问题。单表查询不用写 XMLBaseMapper 自带增删改查分页、逻辑删除、条件构造器都是现成的。项目里面复杂一点的联表查询依然保留了 XML 方式这是很健康的搭配——不是无脑 all in 自动化而是在自动化够用的地方放开手用。MySQL8.0 相比 5.7窗口函数、JSON 字段、更好的索引优化器都是实打实的升级。商城类的统计查询比如“销量排行”“按分类统计订单量”用窗口函数能写出更简洁的 SQL。数据库脚本是按 8.0 语法写的你要是还用 5.7部分脚本可能会报错这一点接收源码后要第一时间确认环境版本。1.3 文档和 SQL 脚本才是最容易被忽略的资产很多同学拿到源码第一时间就跑去看代码结果数据库连不上、前端依赖装不上、接口文档对不上浪费一上午。这个项目我很欣赏的一点是文档把启动步骤、端口配置、账号初始化、目录结构、接口说明都写了。尤其是doc目录下的 SQL 脚本建库建表、初始数据、测试账号一次性到位。接过别人源码的人应该都有同感代码写得烂还能忍没有数据库脚本和文档是真的没法忍。所以拿到这套源码后建议第一个动作就是打开文档把环境要求、启动顺序、账号密码截图存下来再动手碰代码。2. 后端核心架构与业务模块落地2.1 分层骨架与统一接口规范后端包结构是典型的单体分层controller、service、mapper、entity、dto、vo、config、common、exception。controller 只做参数接收和结果返回业务逻辑写在 service数据访问全部走 mapperentity 对应数据库表vo 负责给前端拼装字段。很多人容易把 vo 和 entity 混着用结果前端多一个字段后端就要改实体类这个项目里拆得比较清楚。统一返回结构是Result所有接口返回的 JSON 都是{ code, message, data }三层。这么做的好处是前端 axios 拦截器只需要判一次code 200异常处理直接走全局拦截。对应的全局异常处理器用RestControllerAdvice业务异常、参数校验异常、未知异常分开处理返回给前端的信息不会出现一堆堆栈。拦截器处理的是登录态校验。白名单放行登录、注册、商品列表、商品详情这些公开接口其余接口要求请求头里带 Token。Token 校验没通过时返回固定错误码前端路由守卫拿到这个码之后统一跳转登录页。这个链路是每个电商项目都必须有的理解透了后面自己写权限也顺手。2.2 用户、商品、分类、积分模块的落地细节用户模块核心是注册登录。注册时密码用 BCrypt 加密存储绝对不允许明文落库。登录成功后给前端返回 Token服务端用拦截器解析 Token 拿 userId后续接口就通过这个 userId 去操作购物车和订单。密码加密和 Token 这两件事面试答辩的时候属于必问点建议把原理吃透。商品模块需要注意的点是分类和商品的关系。分类表用parent_id做父子级前端可以一次性拉全部分类在内存里建树展示二级甚至三级分类菜单的时候就不用频繁请求后端。商品表里冗余了category_id而不是直接存分类名称这样修改分类名称的时候商品表不用跟着动。商品列表页做条件筛选时MyBatis-Plus 的 LambdaQueryWrapper 动态拼条件非常方便不存在的时候就查全部存在就加过滤条件。推荐位和商品上下架是运营维护频次最高的功能。is_recommend和status两个字段配合首页推荐位只查推荐且上架的商品管理端修改状态后用户端即刻生效。这个设计虽然是常规操作但作为完整的商城系统它保证了“运营后台和管理端交互”的业务逻辑是自洽的。会员积分模块也是母婴商城的一个特色。注册送积分、每日签到送积分、下单按订单金额送积分积分可以抵扣部分付款金额。积分表记录每次变动的收入和支出明细防止一个用户反复刷积分导致数据对不上。这里就体现出一个好的商城项目至少有完整的数据闭环而不是只做几个静态页面。2.3 购物车与订单状态流转购物车模块第一版通常只做登录后的状态。当前端用户未登录时购物车数据放浏览器 localStorage登录后调接口把本地数据同步到后端以追加或合并的方式处理。同步策略需要单独写一个方法避免同一种商品重复插入两条记录。订单模块是重中之重。下单时前端提交的是购物车里勾选的商品集合和收货人信息后端要做三件事计算总金额、扣减库存、生成订单主表和订单明细表。订单主表存订单编号、用户、总金额、状态、收货信息订单明细表存每个商品的快照信息包含商品名称、图片、单价、数量、小计。快照这个词很关键商品价格后续改了历史订单里的价格不应该跟着变这就是明细表要冗余商品名称和价格的原因。订单状态的流转是面试高频题。待付款、待发货、待收货、已完成、已取消五个状态每个状态对应一组用户可以触发的动作待付款可以取消或支付支付成功后变为待发货卖家后台发货后变待收货买家确认收货后变已完成。后端在更新状态的时候必须校验当前状态是否允许目标状态流转比如已取消的订单不能再支付。这个状态机逻辑不复杂但如果没有状态校验就会出现并发下重复发券、重复扣库存的问题。库存扣减上有一个细节建议下单时先扣库存支付超时后再回滚库存这样能避免超卖。如果不想做得太复杂就在创建订单前做一次库存校验库存小于购买数量时直接提示“库存不足”。这是所有商城项目都会遇到的并发问题哪怕你只做了乐观锁答辩时也能讲出东西来。3. Vue3 前端实现与接口联调细节3.1 工程结构、Vite 代理与 axios 封装前端工程是基于 Vue3 Vite 搭建的目录结构分views、components、api、store、router、utils。views 按业务模块分文件夹用户端、管理端分开组件按功能拆分页面里只负责组合组件和调用接口。开发环境下跨域问题靠 Vite 代理解决。前端开发服务器默认跑在 5173 端口后端接口在 8080 端口浏览器直接请求会产生跨域。配置里把/api开头的请求代理到http://localhost:8080并且开启changeOrigin前端代码里写接口路径的时候统一写/api/xxx不写完整的后端地址。这样开发环境不会跨域生产环境改成 Nginx 反代后前端代码也不需要变动。axios 封装要强调拦截器的作用。请求拦截器统一把用户 Token 从 Pinia 里取出来塞到请求头 Authorization。响应拦截器里判断业务状态码code 200时直接返回data部分业务失败时弹出错误提示并 reject。这样每个页面调用接口的时候只需要关心成功的数据不用重复写错误提示。3.2 购物车与结算页的交互核心购物车页的交互是整个前端最体现水平的地方。全选、单选、取消选中的结算状态要在本地状态里维护每个商品的勾选状态切换后底部结算栏的“合计金额”要即时重算。计算逻辑用computed实现遍历购物车数据把所有勾选商品的单价乘以数量累加这一步不会涉及调用后端接口纯粹是前端状态计算。商品数量在购物车里做加减时要同时更新本地状态和后端接口里的数量。这里的坑在于用户连续快速点击加减号时接口请求可能是乱序返回的最终数量跟页面上显示的不一致。常规方案是点击后先更新本地数据节流或者防抖再提交接口保证界面响应比接口请求快。这个交互细节做好了使用体验会有明显提升。从购物车跳结算页需要把勾选的商品 id 和数量以参数方式传递过去。因为跳转页面后组件会重新创建单纯放内存变量的方式会丢数据。项目里比较稳妥的方案是把勾选结果写到 sessionStorage 或者用 Pinia 状态持久化结算页再从里面读出来展示收货人表单和商品清单。这里不建议把整个购物车都传过去而是只传选中的部分。3.3 路由守卫与 Token 过期处理前端路由分公开页面和需要登录的页面。公开页面有首页、商品列表、商品详情、登录注册订单结算、个人中心、管理后台需要登录。路由守卫在每次跳转前统一拦截页面需要登录而本地没有 Token 时跳转到登录页并携带redirect参数登录成功后自动回到原来想去的页面。管理后台要注意的是角色权限。普通用户登录后也能通过路由地址进管理页这不行。后端接口层面管理员接口会做角色校验但前端的菜单和路由也要按角色过滤没有管理员角色就把管理端路由直接摘掉。两侧都做了限制才是一个完整的权限控制方案。Token 过期是联调阶段最常遇到的问题。后端 Token 一般有一个过期时间前端响应拦截器发现返回码是“未登录或 Token 失效”时要清理本地登录态并跳转登录页。如果 Token 过期后不清理页面会一直卡在请求报错的状态里用户体验很差。细节上还要注意登录页如果有多个组件同时发请求不要都触发跳转最好做一个全局的跳转标记避免重复路由。4. 数据库设计与 MyBatis-Plus 使用要点4.1 核心表结构设计背后的思路商城系统最核心的几张表是用户表、商品表、分类表、购物车表、订单主表、订单明细表、积分明细表。设计上要把握几个原则命名用下划线风格统一加mall_前缀字段必须有注释金额字段用decimal(10,2)不能用float。浮点数累计金额会出现精度问题电商项目里这是硬性要求。用户表mall_user主要字段包含用户名、加密密码、手机号、头像、积分、状态、创建时间。用户名要加唯一索引登录时根据用户名精确查找。密码字段的长度留到 128BCrypt 加密后的字符串会比较长不要用 32 位长度的 varchar 存。商品表mall_product需要注意字段的语义。价格和原价分开存原价用于展示划线价。库存和销量分开销量用于排序。推荐位、是否新品、上下架状态都用tinyint(1)表示。商品详情用text类型存富文本内容或者图片 URL。在category_id上加普通索引商品列表按分类查询时能走索引。订单主表mall_order的订单编号用order_no生成规则一般是“时间戳 随机数”或者“日期 userId 随机数”数据库层面加唯一索引。收货信息直接在主表冗余三个字段收货人姓名、电话、地址。不要为了“规范化”把收货信息拆成单独的表订单创建后收货地址变更是另一个业务主表冗余是电商系统的常见做法。订单明细表mall_order_item是理解“快照”概念的最佳样本。它存了product_name、product_image、price这些冗余字段。用一条简单的 SQL 就能解释清楚下单时商品名称叫“婴儿纸尿裤 L 码 64 片”一个月后商家把商品改名为“婴儿纸尿裤 L 码 60 片”如果订单明细没存快照用户查历史订单就会看到改完的名字价格也会对不上。商品中心可以随便改订单数据必须不变这就是明细表冗余字段的价值。购物车表mall_cart_item在user_id和product_id上建联合唯一索引同一个用户同一个商品只有一条记录。数量字段单独更新勾选状态字段checked单独存储方便结算时直接按状态查。4.2 MyBatis-Plus 高频用法与分页配置MyBatis-Plus 使用前要先引入依赖然后在启动类或者配置类里加入分页插件。网上很多代码下载下来分页不好使就是因为少了MybatisPlusInterceptor这个配置。分页插件配置代码如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配置好之后分页查询用Page对象一行搞定PageProduct page new Page(current, size); LambdaQueryWrapperProduct wrapper Wrappers.lambdaQuery(); wrapper.eq(Product::getStatus, 1) .like(StringUtils.hasText(keyword), Product::getName, keyword) .orderByDesc(Product::getSales); PageProduct result productMapper.selectPage(page, wrapper);这段代码把条件筛选、模糊搜索、排序、分页一次完成。尤其要留意.like的第一个参数是布尔值为false时这个条件不参与拼接。这样商品名没传关键词时SQL 里就不会出现空的条件也不会报错。MyBatis-Plus 的另一个高频用法是批量操作。上下架一批商品、批量修改推荐位可以用updateBatchById或者自己写 XML 的foreach批量更新。条件构造器不只能查还能更新比如“把积分大于 1000 的用户等级改为高级会员”这种逻辑用LambdaUpdateWrapper写起来比传统的update set更直观。逻辑删除也值得提一下。商品和订单这类数据不适合物理删除删了之后统计报表会出问题。配置logic-delete-field之后MyBatis-Plus 的删除操作会自动转成update set deleted 1查询条件也会自动带上deleted 0。这个配置我在上面展示过实际项目里收益很大强烈建议保留。5. 环境搭建、部署避坑与常见问题速查5.1 从零到本地跑通全流程第一步确认 JDK 版本。这个项目基于 SpringBoot2推荐 JDK8 或 JDK11不建议用 JDK17。SpringBoot2 在 JDK17 下虽然也能跑但部分版本会有反射相关的报错没必要给自己挖坑。第二步安装 MySQL8.0 并初始化数据库。执行doc目录下的.sql文件从建库开始跑完成后确认一下三张核心表的行数确认商品表有初始数据、用户表有测试账号再进下一步。第三步修改后端配置文件。application.yml里面的数据库连接地址、用户名、密码改成自己本地的值。启动类右键运行看到 “Started” 日志没有报错再测试访问后端地址能返回 JSON说明后端跑通了。第四步安装前端依赖。前端目录下执行npm install如果下载慢可以换国内镜像源。安装完成后执行npm run devVite 会起一个本地开发服务器浏览器打开控制台会自动代理到后端接口。能正常展示商品列表和图片前后端联调就通了。第五步初始化管理员账号。文档里给了默认的管理员账号登录管理端后可以改密码。管理端入口和用户端的路由是分开的权限走角色判断。5.2 常见问题速查表问题现象可能原因解决方案前端页面能打开但列表数据空后端没启动或数据库没建表先确认后端启动日志再查数据库表结构和数据请求接口报 404前端代理路径和后端 context-path 不一致检查 Vite 代理的/api和后端server.servlet.context-path是否匹配数据库连接报Public Key Retrieval is not allowedMySQL8.0 默认认证方式JDBC URL 拼接参数allowPublicKeyRetrievaltrueuseSSLfalse密码框加密后登录失败数据库中密码不是 BCrypt 格式用文档里的初始账号或重新执行用户表 INSERT 脚本订单提交不成功提示库存不足库存字段低于购买数量在管理端把商品库存调大再进行测试npm install 报错依赖版本冲突或网络问题删除node_modules和package-lock.json重新安装确认 Node 版本大于等于 16购买流程到支付页跳不过去模拟支付回调地址没配置将支付回调地址改为http://localhost:8080/api/payment/callback控制台中文乱码编码格式问题IDE 中统一设置 UTF-8MySQL 连接加characterEncodingutf85.3 让这套源码能撑起答辩和简历的建议近几年毕业设计答辩的评审越来越看重“完整度”和“能不能讲清楚为什么”。母婴商城的业务链路完整已经是加分项。在此基础上如果你想把项目再往上拔一个层次有几个比较安全的扩展方向。第一个方向是加 Redis 缓存。把商品分类、首页推荐位、商品详情这些高频读少写的接口加上缓存讲清楚缓存更新策略是主动删除还是过期失效能体现出你对性能优化的理解。第二个方向是搜索优化。当前的商品搜索是like模糊查询数据量上来之后性能会下降。可以把关键词搜索换成 MySQL 全文索引或者引入专门的搜索组件用 SQL 的方式保留现有代码风格。第三个方向是支付与订单超时处理。现在是模拟支付回调如果你能把订单超时自动取消做成定时任务或者延迟队列配合数据库轮询在并发和分布式场景的讨论上会更有话题点。这几个方向不需要都做选一个做扎实把细节讲透效果比堆一堆新技术名头要好得多。我个人在实际跑这类项目时的体会是拿到源码不要急着先改代码先跟着文档把项目跑起来、把核心流程走一遍然后画一张请求流程图把一次下单请求经过前端、后端、数据库的每一步都标清楚。等这一步做完你对整个系统的理解已经超过了大多数只停留在“会运行”层面的同学。把这个项目吃透再去读别的开源商城源码你会发现它们的长相都差不多——因为电商的底层逻辑就是这套东西换的是壳不变的是业务闭环。