ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0商城系统全栈实战解析

SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0商城系统全栈实战解析 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这四个词组合在一起基本就是目前国内中小型Java Web项目最主流的一套技术配方了。我前前后后接手过好几个商城类项目从单体JSP商城到前后端分离的架构都折腾过看到这套网上服装商城源码的时候第一反应是这玩意儿确实值得好好拆一拆。它不是一个花里胡哨的demo而是一个把电商核心闭环用户、商品、购物车、订单真正跑起来的系统SpringBoot2负责后端接口Vue3负责前端交互MyBatis-Plus把数据库操作简化到极致MySQL8.0做底层存储还附带完整文档。无论你是准备做毕业设计、想快速搭一个电商原型还是想搞清楚前后端分离项目到底怎么串联起来这套代码都能让你少走很多弯路。1. 项目整体设计与技术选型拆解1.1 为什么选前后端分离架构先聊一个很多人纠结的问题商城系统到底该用单体JSP还是前后端分离我的看法很直接能选分离就选分离尤其是2025年以后的新项目。这套服装商城系统采用典型的前后端分离模式后端只负责提供RESTful API前端通过axios异步请求数据两边互不掺和。前后端分离最大的好处是职责边界变得异常清晰。后端不需要再关心页面渲染前端页面也完全不需要理会Java代码的逻辑。放在团队协作的场景里前端工程师和后端工程师只要约定好接口文档就可以完全平行推进开发。就算你只是一个人做项目分离架构也让你调试起来更方便——前端页面出问题了直接打开浏览器开发者工具看Network请求后端出问题了直接看控制台日志排查路径非常清晰。还有一点分离架构天然适合多端复用。这套商城系统的Vue3前端接口设计得比较规范同一套接口将来如果要出小程序端或者App端复用性是非常高的。相比之下JSP这种服务端渲染的模式改造成多端基本等于重写。这也是我在实际评估项目时优先选择它的核心原因。1.2 技术栈选型背后的真实考量逐个来看这套技术栈的选择逻辑每个选型背后都有它的现实理由。SpringBoot2有人可能觉得2025年了为什么不用SpringBoot3这是对版本迭代节奏不太熟悉。很多企业在生产环境里跑的还是SpringBoot2.x因为稳定、资料多、生态成熟。尤其是SpringBoot2.7.x已经是社区验证了无数遍的稳定版本踩坑资料一搜一大把。对于商城这种业务逻辑没有特别前沿需求的系统稳定性是压倒一切的指标。而且SpringBoot2对JDK8的完美支持让部署环境的选择范围宽了非常多。MyBatis-Plus在这个项目里承担了数据访问层的工作。这玩意儿火起来不是没有理由的。它保留了MyBatis手写SQL的灵活性又额外给了一套单表CRUD的通用实现。你在实体类上加几个注解基本的增删改查不需要写一行SQLBaseMapper里面全都给你封装好了。复杂一点的联表查询你又可以退回手写XML。这种“既要又要”的体验很符合大多数Java开发者的习惯。MySQL8.0相比5.7在窗口函数、JSON类型支持、隐藏索引、更好用的性能分析工具等方面都有明显提升。这套商城项目的数据库设计就用到了8.0特性的优势。比如商品规格可以用JSON字段存储排序统计可以用窗口函数更优雅实现。既然都是新项目就没必要抱着5.7不放了。为什么前端选Vue3而不是Vue2这个问题其实在2023年Vue2就停止维护时就已经有了答案。Vue3的Compositon API让代码组织方式从“按选项切分”变成了“按业务逻辑切分”。写商城这种业务逻辑相对复杂的项目用Composition API可以把某个功能的响应式数据、计算属性、方法集中放在一起代码可读性比Options API高出一截。加上Vite的极速冷启动开发体验完全回不去。2. 后端核心SpringBoot2与MyBatis-Plus的配合实战2.1 后端工程目录结构规划拿到这套源码之后第一件事就是看工程结构。好的工程结构本身就是一种文档一个结构混乱的项目代码写得再好后期维护也是一场灾难。这套商城的后端工程采用经典的分层结构模块职责划分得比较合理com.shop ├── controller // 接口层接收参数返回结果 ├── service // 业务层核心逻辑处理 ├── mapper // 数据访问层继承BaseMapper ├── entity // 实体类对应数据库表结构 ├── dto // 前端交互的数据传输对象 ├── vo // 视图对象响应给前端的数据封装 ├── config // 配置类比如跨域、拦截器 ├── common // 公共工具类、统一返回结构 └── exception // 全局异常处理controller这层非常薄只做参数接收和结果封装业务逻辑全部下沉到service层。这样做的好处是接口层只负责处理HTTP协议相关的细节业务层不依赖任何Web相关的东西将来要做单元测试、要做其它形式的接口暴露都不需要改动业务代码。dto和vo分开设计是我比较认可的做法。dto负责接收前端传过来的参数vo负责返回给前端的数据展示。很多人图省事直接拿entity返回给前端这在商城项目里是个大忌你总不想把密码哈希、内部状态码这些字段暴露在接口响应里吧。2.2 通用CRUD与Service的封装思路MyBatis-Plus在这个项目里的正确用法不是每个表写一大段重复的增删改查代码而是基于它的IService做一个基础封装。项目里定义了一个BaseService接口继承IService然后所有业务的Service都继承它。这套架子搭好之后比如你要对商品表做分页查询控制器里只需要注入对应的Service直接调用page方法RestController RequestMapping(/api/goods) public class GoodsController { Autowired private GoodsService goodsService; GetMapping(/page) public ResultIPageGoods pageGoods(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { PageGoods page new Page(pageNum, pageSize); LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); wrapper.eq(Goods::getStatus, 1); wrapper.orderByDesc(Goods::getCreateTime); return Result.success(goodsService.page(page, wrapper)); } }LambdaQueryWrapper是MyBatis-Plus的精华用lambda表达式引用实体字段避免把字段名写成字符串。假如后台把数据库字段改了编译期就能发现错误而不是等到运行期SQL报错才去排查。这种体验比手写QueryWrapper.eq(status, 1)好太多推荐所有人在项目里养成用Lambda版本的习惯。要特别提醒一下MyBatis-Plus的自动填充功能在这个项目里设置得很实用。create_time和update_time字段可以通过TableField(fill FieldFill.INSERT)和TableField(fill FieldFill.INSERT_UPDATE)注解配合MetaObjectHandler自动处理不用每次插入和更新数据都手动set时间。省下来的代码量不大但能有效避免漏填时间导致的脏数据。2.3 数据库设计核心表结构服装商城跟3C数码商城还不完全一样服装类目天然有颜色、尺码两个维度所以商品的SKU设计就是数据库设计的重点。这套系统的商品相关表设计是这样的思路商品表goods存放商品通用信息比如标题、主图、价格区间、销量、状态商品规格表goods_sku一个商品对应多个SKU每个SKU有独立的库存和价格商品规格项表goods_attr存放规格名称和值比如“颜色黑色”、“尺码XL”这三个表配合起来就能很好地表达一件衣服有多种颜色、多种尺码这种复杂关系。用户下单的时候选中的是某一个具体的SKU而不是笼统的“那件衣服”。这种设计是商城系统的地基地基歪了后面的库存扣减、订单金额计算全都会出错。MySQL8.0的JSON类型在规格存储上帮了大忙。一些商城系统习惯用多个关联表去存储规格关系查询的时候要多次join性能和代码复杂度都很不理想。这套项目利用8.0的JSON字段把规格详情这个相对低频变化的数据直接存成JSON查询时直接用JSON_EXTRACT函数提取性能和易用性都兼顾到了。核心的用户表、订单表、购物车表设计也比较中规中矩但这恰恰是好事。电商系统最怕开发人员过度设计把表结构搞得花里胡哨业务逻辑全被复杂数据模型拖累了。干净的、符合直觉的表设计才是长久维护的正道。3. 前端核心Vue3商城界面与交互实现3.1 Vue3组合式API在商城中的实战用法这套商城前端是基于Vue3 Vite构建的整体采用了Composition API的写法。一个典型的商城商品列表页面数据请求、筛选逻辑、分页控制可以集中在一个setup函数里管理。用Composition API和Options API的差别我用一个直观的对比来说。假设商品列表页面要管理“商品数据”“加载状态”“筛选条件”“分页信息”这四块逻辑用Options API的data、computed、methods去组织的话同类逻辑会分散在不同碎块里你改一个筛选条件要在data里改状态、在methods里改方法、在computed里改计算属性来回跳。而Composition API直接让你把筛选相关的state和改变它的action放在一个函数里逻辑的聚合性非常强。这套项目里比较有代表性的一个实现是购物车状态管理。购物车的数据要跨页面共享——用户在商品详情页加入购物车在购物车页面能看到实时数量个人中心还能看到购物车中商品的总数。项目通过Pinia管理购物车的全局状态配合localStorage做了持久化页面刷新之后购物车数据不丢。import { defineStore } from pinia export const useCartStore defineStore(cart, { state: () ({ items: JSON.parse(localStorage.getItem(cartItems)) || [] }), getters: { totalCount: (state) state.items.reduce((sum, item) sum item.count, 0), totalPrice: (state) state.items.reduce((sum, item) sum item.count * item.price, 0) }, actions: { addItem(goods) { const existing this.items.find(v v.skuId goods.skuId) if (existing) { existing.count } else { this.items.push({ ...goods, count: 1 }) } this.persist() }, persist() { localStorage.setItem(cartItems, JSON.stringify(this.items)) } } })这个写法最值得关注的是getters的响应式链。totalCount和totalPrice基于items自动计算任何时候往购物车里加商品总价和总数量自动更新完全不需要手动调用计算函数。3.2 路由管理与权限控制设计Vue3商城前端的路由设计分成三个层级来看公共页面首页、商品列表、商品详情、需要登录的页面购物车、订单结算、个人中心、后台管理页面商品管理、订单管理。公共页面直接放行需要登录的页面通过前置守卫拦截后台管理页面则在此基础上再校验管理员角色。这套项目的前置路由守卫写法配合Pinia中的用户状态能够实现一个相对完整的访问控制链路router.beforeEach((to, from, next) { const userStore useUserStore() if (to.meta.requiresAuth !userStore.token) { next({ path: /login, query: { redirect: to.fullPath } }) } else if (to.meta.requiresAdmin userStore.role ! ADMIN) { next(/403) } else { next() } })带redirect参数的跳转这个细节很好用用户没登录访问购物车会被引导到登录页登录成功后还能自动跳回原来想去的页面不会因为等会儿登录而迷失方向。这种体验上的小细节最能体现一个项目做没做用心。3.3 商城前端页面体系这套服装商城前端的页面构成覆盖了一个电商C端基本的所有核心页面首页轮播图、分类入口、热销商品推荐商品列表页分类筛选、价格排序、分页加载商品详情页SKU选择、加入购物车、立即购买购物车页数量修改、删除、全选、结算订单确认页收货地址、支付方式、商品明细个人中心页用户信息、订单列表、地址管理后台管理端方面商品发布、上下架管理、订单状态流转处理、用户管理这些功能也都齐全。从技术角度来看前端涉及的组件通信、动态路由、状态管理、生命周期钩子这些Vue3核心知识点在项目里全都能找到对应的实战场景。学完这套源码的前端再遇到Vue3面试题基本都能用项目经历回答到点子上。4. 商城核心业务模块实操拆解4.1 用户登录注册与JWT认证机制用户模块是商城系统的基础模块但这套项目的实现不只是简单的“用户名密码登录”。它引入了JWT令牌机制登录成功后后端返回一个带签名的token前端存到localStorage里。后续每个需要鉴权的请求都会在HTTP头的Authorization字段带上这个token后端通过拦截器解析它从而识别当前登录用户是谁。JWT相比传统的Session方案最大的优势就是天然适合前后端分离和水平扩展。服务器不需要维持会话状态每个请求都自带身份信息任意一台后端节点都能验证token做负载均衡和扩容都不需要额外考虑Session同步的问题。密码安全方面项目没有用MD5这种脆弱的散列算法用的是结合随机盐的加盐哈希方案。哪怕数据库泄露了攻击者拿到的是加了盐的哈希值想通过彩虹表反查原始密码的成本大大增加。在校验密码时把用户提交的密码和数据库里的盐拼在一起做哈希计算与库中的值比对。这个细节很多自研系统做得并不规范这套项目的做法可以作为标准参考。public String encodePassword(String rawPassword) { String salt IdUtil.fastSimpleUUID(); String encoded DigestUtils.md5DigestAsHex((salt rawPassword).getBytes()); return salt $ encoded; } public boolean matches(String rawPassword, String encodedPassword) { String[] parts encodedPassword.split(\\$); String encoded DigestUtils.md5DigestAsHex((parts[0] rawPassword).getBytes()); return parts[1].equals(encoded); }4.2 商品SKU与库存管理实现商品SKU设计在前面章节已经提到了表结构这里从业务操作的角度深挖实现细节。用户在商品详情页选择“黑色、XL码”此时后端需要实时返回该SKU的价格、库存和实际图片。这套项目通过一个根据规格值数组动态计算SKU的接口实现前端把选中的规格值传递过来后端匹配到具体的SKU记录返回。库存扣减是商城系统里最容易被低估的一个环节。这个项目用的方案是SQL层面的原子更新扣减库存的SQL里带上库存大于等于购买数量的条件UPDATE goods_sku SET stock stock - #{count} WHERE id #{skuId} AND stock #{count}执行完成后判断受影响行数如果为0就说明库存不足或者已经被并发请求抢光了需要回滚订单或提示用户。这个方案比先查库存再用代码判断再去更新的方案更稳妥天然规避了并发情况下超卖的问题。虽然在秒杀级别的超高并发场景下还需要引入Redis预扣库存等更复杂的方案但对普通商城系统来说已经是非常合理的折中。4.3 购物车与订单确认流程购物车这块前端用Pinia管理状态后端在用户登录后提供同步接口。这套项目的处理逻辑比较贴近真实商城的操作节奏未登录时可以先把商品存到本地购物车登录之后可以把本地购物车数据提交到后端合并。这对移动端用户场景非常友好临时浏览加购的商品不会因为登录操作而丢失。订单确认结算页是一个信息聚合页面需要同时展示收货地址列表、选定商品的价格明细、优惠信息等。这套项目通过一个聚合接口把相关数据一次性返回给前端避免前端连续调用三四个接口等待页面就绪的尴尬。从体验上看结算页的打开速度对转化率影响很大哪怕是后台系统也要注意接口的聚合设计。4.4 订单状态机与支付回调处理订单模块是整个商城的业务核心也是最容易写成一团乱麻的部分。这套项目用状态字段区分订单生命周期待付款、待发货、待收货、已完成、已取消、售后中等状态。每个状态对应哪些操作、操作后流转到什么状态在业务层都有清晰的校验逻辑。比如“待发货”的订单只能进行发货操作不能直接点击取消。支付部分项目实现了支付回调的接口处理逻辑。虽然接入的是模拟支付但处理思路完全参考了真实支付网关的流程支付成功后支付网关会异步回调后端通知接口后端收到回调需要校验并修改订单状态为待发货同时更新支付流水记录。整个过程中最重要的一条经验是支付回调接口的实现必须遵循幂等原则因为支付网关可能因为网络原因重发多次回调通知。拿到回调先查订单状态如果是已支付状态直接返回成功不能重复修改订单数据。5. 环境搭建与三步跑通项目5.1 本地环境准备清单在跑通项目之前先把环境清单列清楚避免装到一半缺东西。这套项目的运行环境要求如下JDK 1.8及以上推荐8u202或11Maven 3.6负责后端依赖管理Node.js 16Vite构建前端需要MySQL 8.0数据库存储引擎可选Docker如果想快速体验MySQL8.0直接拉镜像最快MySQL8.0的安装绕不开几个坑。Windows上安装8.0时用安装包一般没什么问题但手动初始化时容易忘记mysqld --initialize-insecure这个命令导致密码没初始化成功后续无法登录。Linux上用Docker安装相对省心docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -e MYSQL_DATABASEshop \ mysql:8.0如果是Docker方式注意确认MySQL容器内的字符集设置默认可能不是utf8mb4中文数据插入后容易出现乱码。在启动命令中加上--character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci就能避免很多中文乱码的问题。5.2 数据库初始化和后端启动步骤拿到源码后不要急着在IDE里启动先把数据库准备到位。项目的文档里会提供数据库初始化脚本一般是一个.sql文件里面包含了建库建表语句和初始数据。在MySQL8.0中执行脚本推荐用命令行或者Navicat这类图形化工具。执行完毕后检查一下关键的几张表是否有数据比如用户表里应有一个初始管理员账号商品表里应该有样例商品数据。接下来看后端的配置文件主要关注application.yml或application-dev.yml中的数据源配置spring: datasource: url: jdbc:mysql://localhost:3306/shop?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver这里有个非常典型的坑MySQL8.0的JDBC驱动类名和5.7不一样。8.0必须使用com.mysql.cj.jdbc.Driver如果配成了旧的com.mysql.jdbc.Driver启动会直接报错。另外连接串里的serverTimezoneAsia/Shanghai必须带上否则JDBC驱动和数据库服务器时区不一致时间字段的存取会差8个小时。后端启动命令在项目根目录执行mvn spring-boot:run看到日志打印出“Started Application”字样并且监听8080端口就说明后端已经成功启动了。测试接口可以用浏览器直接访问比如http://localhost:8080/api/goods/page能看到JSON数据返回就说明整个后端链路通畅。5.3 前端依赖安装与启动前端部分基于Vue3 Vite进入前端目录后执行npm install npm run devnpm install阶段如果网络状况不好可以换成国内镜像源npm config set registry https://registry.npmmirror.com项目启动后终端会输出一个本地访问地址通常是http://localhost:5173。打开浏览器如果能正常看到商城首页和商品列表说明前后端联调已经没问题。之所以能看到数据是因为Vite脚手架代理了接口请求把前端的/api前缀转发到了后端的8080端口。如果前端页面、接口不通优先检查这个代理配置// vite.config.js server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这个代理配置是开发模式下前后端联调的桥梁没有它前端会一直处于跨域困境中。生产部署时则通常由Nginx做统一的请求转发前端静态文件和后端API共享一个域名从根源上消除跨域问题。6. 常见问题与排查技巧实录6.1 数据库连接与数据初始化问题速查就我自己实操以及帮别人调试的经验这类项目跑不通的案例里百分之六十以上都卡在数据库环节。我整理了一个速查表方便你对照排查症状可能原因排查方法后端启动报Access denied for user数据库用户密码错误或权限不足检查yaml配置和MySQL用户授权启动报Unknown database数据库没有创建或名字不一致执行CREATE DATABASE shop启动报Public Key Retrieval is not allowedMySQL8.0默认认证插件的兼容问题URL参数加allowPublicKeyRetrievaltrue中文乱码连接串缺少utf8mb4设置确认URL中是否需要带characterEncodingSQL脚本导入报错脚本文件编码不是UTF-8用UTF-8编码重新保存后再执行数据库用户授权这块值得单独说。有时候MySQL8.0的root账号只绑定了localhost从本机连接没问题但如果你在Docker容器内跑MySQL而从宿主机连接就会因为host不匹配被拒绝。解决办法是创建用户时要指定允许远端访问的host或者使用%通配。6.2 前后端联调与依赖冲突问题排查前后端联调最常见的坑就是跨域。虽然开发模式下配了Vite代理但如果你绕开代理直接用IP加端口访问后端接口浏览器就会拦截请求。后端项目一般会配置全局跨域支持来解决这个问题。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true); } }依赖冲突方面Maven项目踩坑主要集中在SpringBoot版本和MyBatis-Plus版本不兼容上。MyBatis-Plus对SpringBoot2的兼容性验证做得比SpringBoot3好得多这一点也印证了这个项目坚持SpringBoot2的合理性。如果遇到nested exception is org.apache.ibatis.binding.BindingException这类问题先检查自己的MyBatis-Plus版本是否和SpringBoot2匹配优先使用项目pom文件里锁定的版本不要随便动版本号。前端依赖方面Vite和Node版本存在兼容矩阵Node版本过低或过高都可能导致npm install后dev server无法正常启动。Node 16到20之间的LTS版本基本都能跑起来如果启动报ERR_OSSL_EVP_UNSUPPORTED多半是因为Node和Vite的OpenSSL版本不匹配升级Node到18以上版本就能解决。6.3 项目扩展方向的几个思路这套系统虽然功能完整但明确留出了二次开发的扩展空间。我实操之后觉得有几个方向可以探索把订单号生成规则改成雪花算法或分布式ID方案当前方案在单机部署下没问题做集群部署时多实例并发生成订单号会有重复风险集成MyBatis-Plus的IdWorker是成本最低的改造方案。把商品搜索从SQL的LIKE模糊查询升级为全文索引或引入Elasticsearch对服装商城这类SKU属性丰富的场景基于规格标签的多维筛选体验会好很多。管理端和C端之间可以补充一层Redis缓存把商品详情这类读多写少的热点数据缓存起来接口响应速度会有一个质的提升。如果具备一定的基础把登录方式升级为OAuth2或手机号验证码登录也是比较贴近真实商用场景的改造路径。7. 写在最后的一些体会整套商城源码跑通之后我对它最深的感受就是这不是一个拼凑出来的教学代码而是一个真正按商用标准组织起来的完整项目。从数据库表设计到Controller返回结构从JWT认证到SKU库存原子扣减每个模块的处理方式都能在真实电商业务中找到对应参考。如果你打算拿它作为毕业设计建议不要只做简单的功能演示可以从上面说的扩展方向里选一个点深挖下去比如引入Redis缓存或者做秒杀场景的并发优化这样的项目完整度和技术亮点都会上一个台阶。如果你是想通过读源码来巩固Java Web知识我推荐你按“用户认证 - 商品查询 - 购物车 - 下单支付”这条主线去读先理解一条完整业务链路的走向再去抠每个模块的实现细节。最后提醒一下项目里import的依赖尽量保持原样不要盲目升级版本尤其注意MyBatis-Plus、SpringBoot和Node这三者的版本匹配关系——我在调试过程中踩过的坑大多都藏在版本不一致里。
返回列表