恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
SpringBoot3+Vue3前后端分离商城系统从零搭建完整实战
首页
资讯中心
/
SpringBoot3+Vue3前后端分离商城系统从零搭建完整实战
SpringBoot3+Vue3前后端分离商城系统从零搭建完整实战
发布时间:2026/9/9 11:43:50
带过不少学生把这个项目从零搭到答辩通过今天索性把整个思路和实操过程完整写出来不论你是准备拿它当毕业设计还是纯粹想学SpringBoot3和Vue3的前后端分离开发这篇内容都能帮你少走很多弯路。项目本身不复杂核心就是一套标准的前后端分离商城前端用Vue3 Vite Element Plus后端用SpringBoot3 MyBatis-Plus MySQL功能覆盖用户登录注册、商品分类展示、购物车管理、订单流程以及后台的简单管理。整套流程走下来你对前后端交互、接口设计、状态管理、路由权限这些关键知识点的理解会扎实很多而且每个模块都能单独拿出来在面试里讲清楚。1. 项目整体设计与技术选型思路1.1 这套商城系统到底解决了什么问题很多零基础的朋友一开始都会问商城项目为什么这么受欢迎。说白了商城系统是一个“麻雀虽小、五脏俱全”的典型业务场景。用户端你要处理商品信息的展示、购物车的增删改查、订单的下单和状态流转管理端你要做商品的上架下架、分类的维护、订单的审核处理。这些功能几乎涵盖了Web开发中最常用到的CRUD操作而且业务逻辑层层递进从简单的单表查询到复杂的事务操作都能在一个项目里练习到。对于SpringBoot3和Vue3这两个技术栈来说商城系统的契合度也特别高。SpringBoot3基于JDK17引入了新的编程模型比如Spring AOT编译、GraalVM支持、更好的响应式编程体验你在项目中能感受到它启动更快、配置更简洁。Vue3则带来了Composition API、Teleport、Fragment等新特性配合Vite的开发体验热更新几乎秒级完成写页面的时候相当舒服。另外商城系统正好把前后端分离开发的完整流程串起来了。你会有清晰的接口文档有统一的响应结构有JWT之类的鉴权机制有跨域处理有状态管理等。这些内容在纯后端项目或者纯前端项目里很难接触到但是商城系统逼着你去面对做一遍之后印象会非常深刻。1.2 为什么选SpringBoot3 Vue3而不是其他组合很多学生在动手之前都会纠结到底用SpringBoot2还是SpringBoot3用Vue2还是Vue3甚至要不要直接用若依这种脚手架。我的建议是既然做项目是为了学习和应对毕业答辩就用最新稳定的版本也就是SpringBoot3搭配Vue3。SpringBoot3相比2.x版本最大的变化是强制要求JDK17起跳并且基于Jakarta EE 9命名空间原来javax开头的包名全部换成了jakarta。这个变化带来的好处是长期的技术支持和生态兼容性毕竟新项目再用旧版本后面维护起来会越来越麻烦。你可能担心网上资料少实际上SpringBoot3的核心用法和2.x差不多遇到问题知道是多版本带来的兼容性即可。Vue3也一样现在新项目再用Vue2技术上等于给自己挖坑。Vue3的Composition API配合script setup语法糖写业务代码比Options API舒服太多逻辑复用也方便。而且现在组件库全面支持Vue3Element Plus就是最典型的一个。如果你用Vue2很多组件库已经停止维护遇到BUG都没人管。再说为什么不直接用若依或vue-element-admin这类脚手架。脚手架适合真正做项目的人快速起步但是对学习者和毕设党是致命的因为里面的代码很多是自动生成的你今天删掉两行代码可能就触发了一个隐藏的坑而且问你某个功能怎么实现的时候你根本答不上来。自己从零手写一套架构上参考了这些开源项目的设计但是每一行都能讲清楚这才是项目经历应有的价值。1.3 前后端分离架构的核心理解说过很多次前后端分离的核心不是两个项目分开写而是“接口契约”的建立。前端只管页面显示和交互后端只管数据逻辑和存储两者通过JSON格式的数据进行通信。这个模式听起来简单但实际操作中很多人会在这里翻车。最常见的翻车方式有两种。第一种是前端页面写完了发现后端接口还没写好自己随便Mock了一些假数据等联调的时候发现字段不对又大改一遍。第二种是后端接口写完了但是返回的JSON结构和前端期望的不一致比如后端返回了{code:200,data:{name:xxx}}前端却拿着res.name去接页面当然什么都不显示。所以在这个项目中我强烈建议你先定义好统一的返回结构。比如后端所有接口都返回ResultT包含三个字段code表示状态码msg表示提示信息data表示具体数据。前端在axios的响应拦截器里统一处理这个结构拿到res.code 200才继续执行业务逻辑。这样一个简单的约定可以让前后端联调少掉一半的坑。另外一个核心概念是接口文档。这里不要求你专门去搭建Swagger或者Knife4j但是至少要在项目的接口定义上保持清晰类名、方法名、参数名要见名知义。我习惯在Controller层直接把注释写好包括接口用途、请求参数说明、返回结果说明。后面写前端的时候对着后端代码就能把接口调对不需要反复问别人。2. 起步前的环境准备与工程初始化2.1 后端工程搭建的全过程后端我选择用Spring Initializr来创建工程你可以直接访问start.spring.io也可以在自己IDE里操作。这里有几个关键选项需要注意。Java版本选择17这是SpringBoot3的最低要求。如果本机还没装JDK17先去Oracle官网下载安装安装完记得在环境变量里把JAVA_HOME指到新版本。有时候你之前装过JDK8即使JAVA_HOME改好了项目里还是识别不出来多半是IDE里的配置引用了旧路径检查一下Eclipse或IDEA的Project Structure。Spring Boot版本选择3.x的最新稳定版。在Dependencies里勾选这几个模块Spring Web、Spring Security如果你打算做JWT鉴权、MyBatis框架的依赖需要手动加、MySQL Driver、Lombok、Validation。另外我习惯加上一个AOP依赖因为后面要写统一的日志切面虽然不是必需但加上没坏处。创建完成之后先做三件事。第一件事是改配置文件把application.properties改成application.yml后面所有的配置都用YAML格式写更清晰也更好维护。第二件事是写一个基础的ResultT返回类放在common包下。第三件事是配置全局的跨域在SpringBoot3里可以通过实现WebMvcConfigurer接口重写addCorsMappings方法来完成。这里提醒一下SpringBoot3的跨域配置和SpringBoot2差别不大但如果你开启了Spring Security跨域配置的位置要特别注意过滤器链里也要放行OPTIONS请求否则前端联调时会遇到Access-Control-Allow-Origin报错这个问题我在后面章节会详细讲。2.2 前端工程搭建与Vite使用心得前端这块直接用Vite创建命令是npm create vitelatest然后在交互式命令行里选择Vue框架、JavaScript语法如果你的基础够好可以试TypeScript但零基础的同学先用JS把流程跑通TS后面再迭代替换。Vite比Webpack快非常多原因是它基于ESModule开发时只对修改后的文件做编译而不是像Webpack那样打包整个项目。这个速度体验在写大项目时特别明显基本上保存后浏览器里马上就能看到效果。创建好之后我需要你额外安装几个依赖。vue-router是必须的做路由管理。pinia负责状态管理轻量而且和Vue3的Composition API配合得很舒服。axios用来发HTTP请求。UI组件库我推荐Element Plus它的组件丰富度在Vue3生态里是数一数二的而且一直在维护更新。安装命令一并写出npm install vue-router4 pinia axios element-plus element-plus/icons-vue在main.js里完成全局注册不要每个组件单独引入效率太低。Element Plus的中文语言包也要在入口处配置好否则日期组件等显示的是英文。2.3 数据库表设计表设计是整个项目的基石我见过太多人一上来就写代码最后发现表结构不合理代码写了一半推倒重来。这套酒类商城系统的数据表设计我是按这个思路来的。用户表sys_user是最基础的字段包括主键id、用户名username、密码password这里我强烈建议用BCrypt加密存储、昵称nickname、手机号phone、头像avatar、创建时间create_time这几个字段。如果有多个角色还要加一个role字段区分管理员和普通用户。商品分类表category和商品表product是核心业务表。分类表有id、name、sort排序权重。商品表字段相对多id、category_id、name、description、price价格注意用DECIMAL类型而不是DOUBLE避免精度丢失、stock库存、image商品主图、status上下架状态、sales销量、create_time。这里有一个重要的设计细节商品表通过category_id和分类表关联查询某个分类下的商品时用where category_id ?即可不需要冗余存分类名称。购物车表cart字段有id、user_id、product_id、quantity、create_time。订单相关表稍微复杂一点我拆成了主表orders和明细表order_item。orders表记录一笔订单的整体信息包括id、order_no订单编号、user_id、total_amount、status、create_time。order_item表记录这笔订单里的每项商品信息包括id、order_id、product_id、product_name、product_image、price、quantity。为什么要冗余product_name和product_image因为订单生成后商品信息可能会修改或下架订单详情里保留下单时刻的快照才能确保历史订单始终呈现正确信息。首页轮播图表banner和系统配置表sys_config这种锦上添花的表建议学有余力再加初期不用追求全先把主干跑通。3. 后端核心功能实现3.1 统一返回结果与全局异常处理这套代码设计是你整个项目的门面也是面试时最容易突出亮点的地方。先定义一个ResultT类放在common包下内部维护三个字段code、msg、data并提供两个静态方法Result.success(data)和Result.error(msg)。这样Controller层的代码就变成了GetMapping(/list) public ResultListProduct list() { ListProduct list productService.list(); return Result.success(list); }你想想如果没有这个封装每个接口都要单独处理ResponseBody的格式代码重复率极高而且前端对接时也没有一个统一的标准可循。全局异常处理用RestControllerAdvice来统一拦截异常。先定义一个业务异常类BizException继承RuntimeException构造函数里接收错误信息和状态码。然后在RestControllerAdvice标注的类里写两个方法一个处理BizException一个兜底处理Exception。这样做的目的是业务逻辑里遇到参数校验失败、库存不足等情况直接throw new BizException(库存不足)前端就能收到格式统一的错误JSON而不是一堆丑陋的异常堆栈。最后一个细节是参数校验。SpringBoot3里可以用Validated加NotBlank这类注解来校验请求参数省去手写一大堆if判断的代码。3.2 用户登录注册与JWT鉴权用户模块是第一个完整的业务闭环。注册逻辑很简单前端提交用户名和密码后端先检查用户名是否已存在如果不存在就创建一个用户密码用BCrypt加密后存入数据库。BCrypt的加密方式是单向的每次生成的结果都不同和原来的密码做比对用matches方法完成。登录逻辑稍微复杂一些。验证密码通过后需要生成一个Token返回给前端。Token方案我推荐JWTJSON Web Token它天然适合前后端分离的鉴权场景服务端不需要存储会话状态Token自包含用户信息校验时只需要在服务端用密钥解开签名即可。JWT的工具类建议自己封装。引入jjwt依赖后核心方法就两个一个是生成Token的方法public String generateToken(Long userId, String username, boolean isAdmin) { Date now new Date(); Date expireDate new Date(now.getTime() 7 * 24 * 3600 * 1000); return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(username, username) .claim(isAdmin, isAdmin) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }这里注意不要往Token里放敏感信息比如手机号、身份证号因为JWT本身Base64编码后可以被解码只有签名部分才是加密的。鉴权过滤器是用户模块的重头戏。写一个JwtAuthenticationFilter继承OncePerRequestFilter在doFilterInternal方法里从Header里取出Authorization字段判断有没有Bearer前缀如果有就解析Token解析成功则把用户信息放入SecurityContextHolder。Spring Security的配置类里放行/api/user/login、/api/user/register以及/api/product/**的公开查询接口其他接口全部开启鉴权。这块代码我是按Spring Security 6的写法来的注意SpringBoot3对应的是Security6.x里面不少配置方法已经废弃比如旧的WebSecurityConfigurerAdapter已经被移除需要改用SecurityFilterChain的Bean定义。3.3 商品分类与商品列表商品模块看起来是最简单的CRUD但里面有很多值得注意的设计细节。分类接口提供一个树形结构会很加分。数据库里只有一级分类和二级分类用parent_id字段标识隶属关系。查询时一次性把所有分类查出来在内存中组装成树形结构返回给前端。这个逻辑虽然简单但是能展示你对数据处理有自己的思考。商品列表最关键的是分页和条件查询。前端请求时传pageNum、pageSize、keyword、categoryId、sortType这几个参数后端用MyBatis-Plus的Page对象配合LambdaQueryWrapper做条件构造。LambdaQueryWrapper是MyBatis-Plus的精华不用把SQL写在注解里用Java方法安全的方式构建查询条件改起来也方便。价格排序和销量排序是最常用的两个场景。价格排序用orderByAsc(price)或orderByDesc(price)销量排序用orderByDesc(sales)。前端下拉框选择排序方式后把sortType传给后端后端用switch判断后添加对应的排序条件。商品详情接口就是根据id查出商品信息同时把分类名称一并返回。你可以用当前表关联查询也可以单独查一次分类表两种方式性能差异很小看个人习惯。3.4 购物车加购与订单提交购物车是一个典型的关联业务模块加购接口接收productId和quantity先判断购物车里是否已有该商品有就累加数量没有就新建。这个过程要小心库存校验加购数量不能超过商品库存。订单提交是整个项目里事务最复杂的模块。因为它涉及多张表的变更新增订单主表记录、批量插入订单明细表、扣减商品库存、清空购物车对应商品。任何一个环节失败都需要回滚所以要在Transactional注解保护下完成。这个注解是Spring事务管理的经典用法面试时大概率会问你要能讲清楚它底层用的是AOP动态代理机制默认遇到RuntimeException回滚。订单编号是我个人觉得容易忽略的地方。用自增Id做订单号虽然简单但是太丑了而且容易暴露业务量。建议用时间戳加随机数的组合格式类似202501121030001234前面14位是年月日时分秒后面4位随机数。如果担心并发重复可以再加一个用户Id的尾号。下单时还需要确认收货地址。正常的电商项目会单独有地址管理表但作为学习项目我建议用户在订单里直接填写收货人和手机号、收货地址。这样省去一张表打交道的复杂度也能满足毕设需求。4. 前端核心页面实现4.1 路由设计与Pinia状态管理前端路由用Vue Router的History模式需要注意的一点是部署时后端要做路径重写否则刷新页面会404。开发环境中不存在这个问题。路由设计上我建议区分三个层面。第一层是公共路由包括首页、商品列表、商品详情。第二层是用户路由需要登录后才能访问比如购物车、订单页、个人中心。第三层是管理员路由后台管理的产品列表、订单管理、用户管理等页面。这几类路由要通过路由守卫实现访问控制。路由守卫的代码思路是在router.beforeEach里判断目标路由的meta信息。如果meta.requiresAuth为true检查Pinia里有没有用户信息没有就跳转到登录页并带上redirect参数。如果meta.requiresAdmin为true则额外检查用户角色是否为管理员。Pinia的状态管理在这里派上大用场。我建议创建两个Store一个useUserStore管理用户信息和登录状态一个useCartStore管理购物车数量和悬浮窗显示。用户登录成功后把Token存到localStorage里同时把用户信息存到Store中。刷新页面时通过初始化的方法来从本地恢复状态保证页面刷新后不会掉登录态。4.2 商品列表页与详情页的交互体验商品列表页是从后端数据到前端展示的第一个完整场景。核心逻辑就是页面加载时调用GET /api/product/list接口获取第一页数据搜索框输入关键字时触发防抖查询左侧分类菜单切换时重置分页并重新请求排序下拉框变化时重新请求在这里防抖是必须处理的细节。如果不做防抖用户每敲一个字母就触发一次请求不仅后端压力大前端页面也会频繁刷新体验很差。防抖的实现有两种一种是自己在watch里配合setTimeout实现另一种是用lodash-es的debounce函数。个人推荐后者代码更简洁。商品详情页除了展示基本信息外主要交互是数量选择、加购和立即购买。加购成功后最好弹出一个确认框让用户选择继续购物还是去购物车结算这是一个非常符合真实电商场景的细节设计答辩时讲出来会很加分。4.3 购物车与订单提交的联动购物车页面展示当前用户的所有购物项每行包括商品图、名称、价格、数量、小计金额以及一个删除按钮。数量的增减要绑定后端接口每改变一次就调用一次PUT /api/cart/{id}接口更新数量。这里有一个很容易踩的坑就是页面上的数量和数据库不同步的问题。比如用户连续快速点击加号此时多个请求同时发出后发先至就会把前一个请求的结果覆盖掉导致数量最终不对。解决办法是加一个loading状态在请求未完成时禁用按钮或者在请求成功后再更新本地数据让数据轴以后端为准。订单结算页要展示订单的商品列表以及合计金额。确认提交后调用下单接口成功后清空购物车已下单的商品同时跳转到订单详情页展示订单状态。整个流程的顺序是提交订单接口返回订单编号前端根据订单编号查询订单详情再渲染到新页面。这里前端代码要注意接口的调用顺序避免使用success回调后再调下一个success导致回调地狱合理做法是用async/await。5. 项目联调高频报错与排查实录5.1 跨域问题的三种解法前后端分离项目里跨域报错是出现频率最高的问题。前端的开发服务器跑在5173端口后端的接口跑在8080端口浏览器的同源策略会把两者之间的请求拦截下来。解决方式有三种按推荐程度排序。第一种是后端开启CORS这也是最推荐的方案。在SpringBoot里就是写一个配置类实现WebMvcConfigurer接口重写addCorsMappings方法允许的前端地址配置在配置文件里。好处是可以精确控制允许访问的来源。第二种是前端Vite配置代理。打开vite.config.js文件修改server.proxy配置把/api前缀的请求代理到后端地址。这种方案开发时最常用因为浏览器看到的是同源localhost:5173下的请求根本不触发跨域。但是部署到生产环境时代理就不起作用了需要后端或Nginx配合。第三种是Nginx反向代理。生产环境里这基本是标配把所有前端路由交给Nginx处理后/api开头的请求转发到后端服务地址。我的建议是开发阶段两种都配置后端CORS打开兜底前端Vite代理设置好生产环境由Nginx负责。实际联调时这三种方案能覆盖绝大部分场景。5.2 前端渲染数据为空的排查思路前端页面白屏或者表格里没数据这是新手最容易懵的情况。我的排查路径一般是这样。第一步看浏览器Network面板。打开F12开发者工具刷新页面看对应接口的状态码。如果状态码是404说明后端的接口路径写错了或者前端请求的URL不对。如果状态码是200但Response返回的JSON是空数组或null那问题可能在后端SQL或数据结构继续往下查。如果状态码是403或401说明鉴权机制拦截了检查Token是否带上了。第二步看Console面板的报错信息。Vue3开发模式下常见的两个报错是TypeError: Cannot read properties of undefined和Cannot find module。前者通常是取了不存在的字段比如后端返回的字段叫productName前端却取了name后者多半是组件路径写错了或依赖没安装完。第三步检查Store和组件的响应式绑定。有时候接口返回的数据正常但页面没有更新原因可能是声明变量时没有用响应式API包起来。记住Vue3里用ref和reactive创建的变量才是响应式的普通变量改了不会触发页面重新渲染。5.3 前后端常见错误速查表报错信息或表现可能原因解决办法Access-Control-Allow-Origin 缺失后端没开CORS或前端代理没配置参考5.1三种方案选一种配置401 UnauthorizedToken缺失或过期检查请求拦截器是否注入了Authorization头403 Forbidden登录了但权限不够检查路由守卫和用户角色判断逻辑Whitelabel Error Page后端接口路径错误或异常未处理检查后端控制台堆栈日志Cannot find module xxxnpm依赖未安装完整执行npm install前端表格数据不显示字段名不匹配或数据类型不对打开Network面板对比返回JSON和前端代码字段Refused to display in a frame被X-Frame-Options拦截后端在安全性允许的范围内配置frameOptions购物车数量不对并发请求未加锁请求前禁用按钮或请求成功后刷新数据订单重复提交前端未加锁或接口未做幂等提交按钮加loading状态后端用订单号去重刷新页面后登录态丢失未从localStorage恢复状态在Store初始化时读取localStorage5.4 几个容易忽略的小细节第一个是时区问题。数据库连接串上一定要加serverTimezoneAsia/Shanghai否则用默认时区取出来的时间和本地差8小时。这个问题在部署到Linux服务器上时最常见。第二个是Python脚本类问题不对这里应该说的是Node版本问题。Vite5要求Node版本18如果你安装依赖时报错提示版本不支持先用node -v检查一下当前版本。很多同学下载了最新的Vite模板却报错最后发现是Node版本太老。第三个是配置文件编码问题。在Windows上用记事本编辑过application.yml再放回IDE里运行偶尔会遇到配置文件乱码导致的启动失败。编码问题很隐蔽排查时会浪费不少时间。建议统一使用UTF-8编码并在IDE里设置默认编码为UTF-8。6. 给毕设、实训与自学人群的额外建议6.1 如何把这个项目从“能跑”变成“能答辩”很多学生拿到项目源码后第一反应是把代码跑起来跑通了就以为万事大吉。结果到了答辩现场老师随便问一个问题就答不上来。为了让项目真正能扛住答辩你需要做几件事。第一件事是重新梳理业务流程。把用户从注册、登录、浏览、加购、下单、支付的完整链路在纸上画出来理解每一个环节涉及哪些表、哪些接口、哪些页面。能做到闭着眼讲清楚流程答辩时就不会慌。第二件事是理解每个模块的“为什么”。比如为什么密码要用BCrypt加密而不是MD5为什么订单表要冗余商品名称快照为什么要用JWT而不是Session。这些问题不要求你回答得多深入但至少要有自己的理解。平时在写代码时多想一步答辩时就能多说三句。第三件事是准备一个亮点功能。商城的核心功能大家都差不多你需要在细节上做出差异化。比如在后台上传商品时支持图片预览并回显订单列表支持按状态筛选购物车支持批量删除。这些功能看似不起眼但和“只会简单CRUD”的学生一比立刻拉开差距。6.2 项目扩展还能往哪些方向做如果时间充裕这个项目可以往三个方向扩展每个方向对能力的提升点不同。第一个方向是支付功能。接入支付宝沙箱或者微信支付沙箱代码里需要处理支付回调、签名验证、订单状态同步。这能让你接触真实支付场景中的安全性设计逻辑是电商类项目里含金量最高的部分。第二个方向是增加管理后台的权限系统。当前项目里只有管理员和普通用户的简单区别可以改成RBAC模型引入角色表和菜单表做动态路由。前端根据用户的角色来动态加载路由表这也是企业级项目里非常常见的设计。第三个方向是引入Redis。把首页轮播图、热门商品排行等热点数据缓存到Redis减少数据库压力。还能用Redis做分布式Session存储这部分的代码量不大但讲起来非常加分。6.3 关于版本迭代和踩坑的总结最后再分享一个实际经历。我最早带学生做这个项目时用的还是SpringBoot2和Vue2版本升级到SpringBoot3和Vue3之后确实踩了一些兼容性的坑比如Spring Security的配置方式变了比如MyBatis-Plus的starter包名调整了比如Element Plus的组件用法和Element UI有很多差异。遇到这些问题时我建议你先看清楚报错信息再根据关键字搜索解决方案尤其是官方文档优先看。很多报错信息贴到搜索引擎里第一条可能就是官方GitHub的Issue点进去就能找到权威回答。写代码过程中还有一个小技巧就是每一阶段跑通后再进入下一阶段。比如先把后端用户模块的所有接口写完并测试通过再写前端用户页面。不要后端还没写完整就急着写前端那样联调时会出现一堆问题排查起来非常痛苦。这套项目对我个人来说其实也是一次技术更新过程中的完整实践记录。前前后后做了多次版本的迭代从单一的用户登录到完整的订单流程从简单的页面展示到后台的权限管理积累了非常多可以复用的代码设计思路。如果你正在做类似的项目希望在阅读完这篇文章后能直接用这份实操记录把系统跑起来并且在使用过程中形成自己的理解。多动手多调试遇到报错不害怕——这才是编程学习中最重要的能力。