资讯详情

资讯详情

Spring Boot + Vue 全栈电商项目实战:从环境搭建到业务改造踩坑指南

简介一套基于SpringbootVue的在线购物平台完整项目适用于计算机专业毕业设计、课程设计与期末大作业等场景也适合正在准备毕设的学生和需要项目实战的Java学习者。项目以高分通过并获得导师指导运行环境为JDK1.8、MySQL5.7与Maven3.3可在Eclipse或IDEA中直接部署。压缩包共607个文件主要包含124个Java后端源码、94个Vue前端页面、63个JavaScript脚本另附数据库脚本、开发说明文档、部署视频、代码讲解视频及图标图片素材等整体约20.79MB目录划分清晰便于按需查阅。目前已有58人学习浏览。除可直接运行的完整代码外还提供了多段操作录屏和代码讲解可帮助理解前后端数据交互、数据库设计思路与项目部署细节适合直接作为毕设交付或在此基础上继续扩展。1. 在线购物平台项目Spring Boot Vue 全栈样板跑到能改才是你的解压 .rar 的人通常分两种一种想快速拿到一个能演示的在线购物平台另一种想让这套 Spring Boot Vue 的代码变成自己能改、能讲、能部署的东西。这个标题里藏的是一个典型的前后端分离电商项目后端用 Spring Boot 提供商品、购物车、订单、登录等接口前端用 Vue 渲染页面并调用接口MySQL 负责业务数据持久化。它不算生产级系统但覆盖了电商业务最核心的完整链路。先说结论把它跑起来只需要半小时真正拉开差距的是你能不能从“能跑”走到“能改”。接下来按拆结构、启动、读业务、避坑、进阶的顺序展开每一步都能落到实处。2. 技术选型拆解Spring Boot 后端与 Vue 前端各自的职责边界2.1 后端分层Controller / Service / Mapper 为什么这样拆几乎每个基于 Spring Boot 的在线购物平台后端都会采用 Controller、Service、Mapper 三层结构。这个习惯不是为了代码好看而是让职责可控Controller 只负责参数接收和结果返回Service 负责业务规则与事务管理Mapper 只操作数据库。就拿“下单”这个高频动作来说校验库存、扣减库存、生成订单、清空购物车四个操作必须在一个事务里完成这个事务边界只能放在 Service 层Controller 放不下也不该放。RestController RequestMapping(/api/order) public class OrderController { Autowired private OrderService orderService; PostMapping(/create) public ResultOrderVO create(RequestBody OrderDTO dto) { return Result.success(orderService.createOrder(dto)); } }这段代码里没有任何业务判断它只负责把 JSON 转成 OrderDTO然后调用 Service。Controller 越薄代码越容易测试业务规则集中在 Service 层出问题时也只需要在一个地方排查。Mapper 层通常配合 MyBatis-Plus 使用因为它内置了 selectById、selectPage 这些基础操作购物平台八成以上的查询都是单表或简单联表完全不需要手写 XML 映射。还要注意 Spring Boot 版本带来的差异2.x 用的是 javax.* 包3.x 全部换成 jakarta.* 包。很多课程项目基于 2.x如果你本地装的是 3.x 脚手架直接把代码拷贝过来会出现大量 import 报错。判断项目版本最快的方法是看 pom.xml 里 spring-boot-starter-parent 的版本号而不是看代码。2.2 前端工程Vue Router 管理页面Axios 封装请求前端部分老项目用 Vue 2 Element UI新项目用 Vue 3 Element Plus但工程骨架高度一致src/router 放页面路由src/api 放接口请求封装src/views 放页面组件。购物平台的页面集比较固定商品列表、商品详情、购物车、订单确认、个人中心正好由一套路由撑起来。前端最值得先读的文件不是某个页面而是封装请求的公共模块因为所有接口的鉴权头都靠它统一添加。// src/utils/request.js —— 前端统一请求封装 import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } ) export default request这个文件里有两个容易被忽略的细节。baseURL 写成/api而不是后端完整地址是为了配合开发服务器的代理token 从 localStorage 里取是为了让刷新页面后登录状态还在。这两个点前后端必须对齐否则就会出现后面要专门讲的“登录成功但其他接口全部 401”现象。前端组件组织上商品列表页通常由搜索框、商品卡片列表、分页器三部分组成购物车页只需要一张表格加一个结算按钮。用 Vue Router 时要注意路由的懒加载写法component 里用 () import(/views/GoodsList.vue)否则打包后首屏加载会明显变慢。这一点在课程项目里经常被忽略但并不难改。2.3 接口约定统一返回结构、状态码与鉴权方式前后端能不能顺畅协作取决于接口约定是否稳定。购物平台项目里一般会有一个统一的 Result 包装类把状态码、提示信息和业务数据包在一起返回。常见约定code含义前端处理200请求成功直接读取 data400参数不合法弹出 msg 提示401未登录或 token 失效清除本地 token 并跳登录页500服务端异常弹出 msg 并保留当前页前端拿到任何非 200 的 code都应该统一交给拦截器处理而不是在每个页面里各自判断。鉴权部分登录成功后后端返回一个 token前端存进 localStorage后续每个请求都从本地取出来放到请求头里。后端用拦截器统一校验白名单放行登录、注册和商品浏览等公开接口其余路径全部要求有效 token// 后端拦截器放行规则公开接口不进鉴权 String[] allowUrls { /api/user/login, /api/user/register, /api/goods/**, /api/category/** };白名单配置是经验活少放一个接口前端就会莫名报 401多放一个安全边界就出现缺口。拿到项目后先读这一处基本就能判断作者对鉴权流程理解到哪一层。还有一个常见细节是登录接口本身如果也被拦截器拦了就会出现“登录接口返回 401”的自锁现象这种问题在项目里屡见不鲜。3. 把项目拉起来从 .rar 解压到本地全栈启动的最小步骤3.1 解压后的目录结构先分清后端、前端和数据库脚本拿到 .rar 之后第一件事不是双击打开 IDE而是先把压缩包里的东西认清楚。典型的 Spring Boot Vue 项目包含三块一个 Maven 后端工程、一个 Vue 前端工程、一份 SQL 初始化脚本有时还带个说明文档。很多新手一上来就只把后端目录拖进 IDE数据库没建就点运行然后在报错里绕圈子。先用目录树确认结构# 解压后常见的工程布局 online-shopping/ ├── springboot-server/ # 后端pom.xml 所在目录 ├── vue-web/ # 前端package.json 所在目录 └── db/ └── shop.sql # 数据库初始化脚本如果解压出来只有一个后端工程而没找到前端目录检查是不是嵌套了一层文件夹如果连 SQL 脚本都没有说明原项目依赖 ORM 自动建表这时要核对实体字段与数据库类型避免字段对不上。把三块内容定好位后再按数据库、后端、前端的顺序启动。顺序即成功率数据库没就绪就启动后端日志会一直报连接错误容易让人误判成代码问题。3.2 数据库初始化SQL 脚本导入与连接配置数据库是第一个最容易翻车的环节。SQL 脚本可能是从 MySQL 8.0 导出的也可能是用 5.7 写的直接导入轻则报排序规则错误重则建表一半中断。建议先看脚本头部有没有 CREATE DATABASE没有就先手动建库再导入避免库名与连接配置不一致。我用命令行演示一遍最稳妥的导入流程mysql -uroot -p --default-character-setutf8mb4进入客户端后执行CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8mb4; USE shop; SOURCE /path/to/shop.sql;导入完成后后端连接配置要和库名、账号、密码完全对齐。打开 application.yml确认这一处和本地环境匹配spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码如果启动时提示时区错误多半是 url 里少了 serverTimezone连接失败则优先检查端口是 3306 还是被改成了别的。数据库没就绪之前不要动任何后端代码这是启动阶段最重要的顺序约定。3.3 启动后端Maven 构建与启动日志定位数据库就绪后再用 IDE 打开后端工程等 Maven 把依赖拉完。也可以完全用命令行跑两条路线效果一样区别只是 IDE 把日志做了可视化命令行更贴近线上操作方式cd springboot-server mvn clean package -DskipTests java -jar target/online-shopping-0.0.1-SNAPSHOT.jar看到 Tomcat started on port(s): 8080 这行输出后端才算真正起来。失败的话先看日志里的第一段异常大多数情况下那是根因后面跟着的十几行堆栈经常是同一个问题的连锁反应。常见的启动失败场景就集中在三处端口被占用、数据库连接参数不对、pom.xml 依赖版本冲突。拿到日志先搜 Caused by 能省很多时间。补充一个判断如果 pom.xml 里的 packaging 是 war启动命令要换成 mvn spring-boot:run 或在 IDE 里直接运行主类默认 jar 就用 java -jar 方式。还有就是第一次用 Maven 构建时依赖下载可能花很长时间这不是卡死是中央仓库连接慢耐心等或换镜像源都可以。3.4 启动前端npm install 与开发代理配置后端起来后前端启动也有固定工序先装依赖再起开发服务器。有些压缩包里故意不携带 node_modules就是为了跨机器重装但重装有重装的坑Node 版本对不上就会卡在依赖编译上。标准操作是cd vue-web npm install npm run servenpm install 后看到类似 vue-cli-service serve 的启动日志就说明编译成功。然后访问前端端口比如 8081 或 5173能看到首页才叫前端起来了。但页面能开只是第一步接口能不能通是另一回事前端开发服务器的 /api 请求需要被代理到后端 8080配置在 vue.config.js对应 Vue CLI或 vite.config.js对应 Vite里module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }如果页面能开但接口全部 404检查代理 target 的后端端口是否写错如果接口返回 502说明代理生效了但后端没起来。这三类现象分别对应前端问题、代理问题、后端问题排查路径完全不同先分清再动手。还有一个容易踩到的点是后端改了端口但前端代理没改这时候页面和接口各自都正常放在一起就不通非常迷惑人。4. 核心业务实现商品、购物车、订单的三段代码走读4.1 商品列表与分页MyBatis-Plus 的 Lambda 查询商品是购物平台的起点列表页和详情页几乎覆盖了所有流量入口。绝大多数课程项目都用 MyBatis-Plus 操作数据库因为它自带分页插件把“查第几页、每页几条、按什么条件筛选”封装得非常简洁。商品模块的 Service 实现类里核心方法大概长这样public PageGoods pageGoods(Integer pageNum, Integer pageSize, String keyword) { PageGoods page new Page(pageNum, pageSize); LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); wrapper.eq(Goods::getStatus, 1) .like(StringUtils.hasText(keyword), Goods::getName, keyword) .orderByDesc(Goods::getCreateTime); return goodsMapper.selectPage(page, wrapper); }这里 eq 表示精确匹配like 表示模糊查询StringUtils.hasText 这个条件决定了 keyword 为空时整段 like 不生效。orderByDesc 控制列表默认按创建时间倒序。如果商品表里有销量字段想改成按销量排序把排序字段换掉即可Mapper 和 SQL 都不用动。这段代码看懂后分类筛选、价格区间筛选都是同样的套路往 LambdaQueryWrapper 里加条件就行。MyBatis-Plus 还有一个需要注意的地方分页要配置 PaginationInterceptor 或 MybatisPlusInterceptor 才真正生效否则 selectPage 返回的数据是全量查询后在内存里切的假分页。项目里如果只看到 MP 依赖而没有配置拦截器列表接口在数据量大时会越跑越慢这一点值得专门检查。4.2 购物车数据结构表设计比业务逻辑更值得先想购物车逻辑本身不难加购、减数量、删商品、清空列表。难点在于设计放哪里。常见做法是设计一张数据库表以 user_id 和 goods_id 为联合唯一键保证同一个用户对同一个商品只存在一条记录。这样加购时不需要先查一次再决定 insert 还是 update数据库自己就能处理重复插入CREATE TABLE cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 用户ID, goods_id INT NOT NULL COMMENT 商品ID, count INT DEFAULT 1 COMMENT 数量, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_goods (user_id, goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;配合唯一键后端在“加入购物车”时只需要一条 SQL 完成累加或新增不需要先 select 再决定操作类型INSERT INTO cart (user_id, goods_id, count) VALUES (1, 10, 1) ON DUPLICATE KEY UPDATE count count 1;购物车表里为什么不存商品名称和价格因为它们属于商品表购物车只保存 user_id 和 goods_id每次展示时再联表或查询商品表获取最新价格。这样做的好处是商品改价后购物车会自动展示最新价格不用做数据同步。提示如果项目把购物车放到 Redis 里通常是用 Hash 结构以 user_id 为 key 存商品 ID 与数量好处是读写快坏处是要自己解决持久化和过期策略。对于学习和课程设计数据库表方案更直观也更容易排查。4.3 订单流程事务边界与扣库存的时序问题订单是电商项目里最能体现水平的部分。一个下单动作涉及四件事校验库存、扣减库存、生成订单、清空购物车它们必须同时成功或同时失败。Spring Boot 的做法是在 Service 方法上加 Transactional让方法内的所有数据库操作处于同一个事务中Transactional public OrderVO createOrder(OrderDTO dto) { Goods goods goodsMapper.selectById(dto.getGoodsId()); if (goods.getStock() dto.getCount()) { throw new BusinessException(库存不足); } goodsMapper.reduceStock(dto.getGoodsId(), dto.getCount()); Order order new Order(); order.setUserId(dto.getUserId()); order.setGoodsId(dto.getGoodsId()); order.setCount(dto.getCount()); order.setStatus(0); orderMapper.insert(order); return OrderVO.from(order, goods); }这段代码能跑通课程设计级别的流程但有一个资深工程师一眼就能看到的并发隐患先查库存再扣库存之间存在时间差两个请求同时通过校验时库存就会被扣成负数。把它往生产方向改的第一步是把“校验库存 扣减”合并成一次原子更新UPDATE goods SET stock stock - #{count} WHERE id #{goodsId} AND stock #{count}这条 SQL 的影响行数为 0 时说明库存不足直接抛异常把原来的判断和扣减两步缩成一步就把并发窗口期堵上了。订单状态用整型字段表示0 待付款、1 已付款、2 已发货、3 已完成、4 已取消状态流转在 Service 层集中判断避免前后端各写一套状态逻辑的隐患。看订单模块的顺序建议是表结构 → 事务注解 → 扣库存 SQL → 状态流转。5. 从启动到登录5 个实战翻车点与排查思路5.1 数据库脚本导入报错Unknown collation现象Navicat 或命令行导入 SQL 时弹出 Unknown collation: utf8mb4_0900_ai_ci导入中断。原因脚本是从 MySQL 8.0 导出的本地装的是 5.7后者不支持 0900 系列排序规则。解决先确认本地 MySQL 版本本地是 8.0 就检查是不是连错了实例是 5.7 就做一次文本替换把排序规则降级sed -i s/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g shop.sql另外检查脚本开头有没有 CREATE DATABASE没有就先手动建库。导入后务必用 SELECT 抽查关键表数据确认不是空库——空表会让后端启动正常但页面拿不到任何商品这种“假成功”比报错更难排查。5.2 Maven 依赖下载缓慢或构建失败现象执行 mvn clean package 时长时间停在 downloading最后报 Could not resolve dependencies。原因连接默认中央仓库不稳定或本地 Maven 仓库里缺了项目依赖的某个构件。解决在 Maven 的 settings.xml 中配置国内镜像源再用 -U 强刷快照mvn clean package -DskipTests -U如果构建失败信息里出现某个具体依赖坐标先确认 pom.xml 里有没有版本号缺失。用 IDE 的话顺手把 Maven 本地仓库路径改到非系统盘能避免权限问题引起的神秘失败。这里的老经验是不要只盯着最后一个报错看往上翻几行真正缺失的依赖名称通常写得很明确。5.3 后端启动时端口被占用现象日志里出现 Port 8080 was already in useSpring Boot 启动线程直接退出。原因本机有其他 Java 进程占用了 8080或者上一次调试的后端进程没有完全停止。解决先找进程再决定杀还是改netstat -ano | findstr :8080 # Windows 找 PID lsof -i :8080 # macOS/Linux 找 PID杀进程后重新启动如果该端口被其他重要服务占用就改 application.yml 里的 server.port同时同步改前端代理的 target。建议开发环境统一端口减少联调时出现“前端连到别人的服务上”这种玄学问题。还有一种隐蔽情况是 IDE 里旧实例还在运行新实例启动时端口冲突日志一闪而过实际上旧进程没被终止。5.4 前端 npm install 报 node-sass 编译错误现象npm install 执行到 node-sass 时报 gyp ERR或者长时间卡在编译环节不动。原因项目锁定的 node-sass 版本与本地 Node 版本不匹配node-sass 在老版本依赖下对 Node 版本非常挑剔。解决把 package.json 里的 node-sass 替换为 sass然后重装依赖rm -rf node_modules package-lock.json npm install替换成 sass 后代码里如果出现 import ~xxx 这种前缀写法需要微调。如果不想改代码就换一个与项目年份匹配的 Node 版本但用 sass 方案更省事也能让以后的依赖安装不被版本绑架。吃过这个亏的人都明白node-sass 的编译问题不是靠耐心能解决的它是版本组合硬约束。5.5 登录成功但其他接口全部 401现象前端登录页能正常登录并跳转但每调用一个业务接口就返回 401 或“未登录”。原因token 没有成功存进 localStorage或 axios 请求拦截器没生效也可能是后端拦截器白名单没把公开接口放行。解决按顺序排查。先打开浏览器 Network 面板看业务请求的请求头里有没有 Authorization。看不到 token 就是前端问题能看到 token 但后端仍报 401就是后端校验问题。前端检查登录成功后是否执行了 localStorage.setItem(token, ...)后端检查拦截器白名单确认 /api/user/login 和 /api/goods/** 在放行列表里。另一个隐蔽原因是登录接口本身也要求带 token导致登录请求拿不到正常响应这种自锁现象在项目里并不少见排查时可以把登录接口临时加进白名单验证。6. 进阶给平台加 Redis 缓存与库存锁再用 JMeter 压测验证跑通并不是终点把项目改成能扛一点压力的样子才算真正吸收。先挑改动最小、收益最明显的接口商品详情。这个接口请求频率高、数据更新不频繁是最适合加缓存的场景。key 按商品 ID 设计先读 Redis没有就回源数据库写入时给一个过期时间防止数据长期不刷新public Goods getDetail(Long id) { String key goods: id; Goods goods redisTemplate.opsForValue().get(key); if (goods null) { goods goodsMapper.selectById(id); redisTemplate.opsForValue().set(key, goods, 30, TimeUnit.MINUTES); } return goods; }商品更新或删除时记得主动删除对应 key否则用户看到的还是旧数据。这个细节比缓存本身更容易被忽略也是排查“为什么改了价格页面不变”时要优先想到的方向。扣库存那一步的并发问题用 Redis 分布式锁来堵锁的 key 用商品 ID抢到锁才执行库存校验和扣减执行完再释放并设置合理的过期时间防止线程崩溃导致死锁。锁的粒度按商品维度拆不会互相阻塞对购物平台这种多商品并发的场景最合适。改造后用 JMeter 建一个下单接口的压测场景线程数设 10循环次数设 50监听器里看聚合报告。重点看三个指标吞吐量TPS、平均响应时间、错误率。改造前如果错误率里混着库存超卖导致的异常改造后这类错误会明显归零TPS 和响应时间的变化则能看出缓存和锁有没有拖慢接口。压测时把后端日志级别调成 WARN否则控制台刷日志会严重拖慢 JVM。我每次拿到购物平台项目都会先翻登录鉴权和白名单配置那里最暴露代码的成熟度再追一遍订单事务和库存扣减基本就能判断出这套代码值不值得继续加深。这套流程没有捷径但压测是验证一切改动是否真的有效的最实在方式。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →