ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot+Vue前后端分离商城系统实战:甘肃特产线上平台解析

Spring Boot+Vue前后端分离商城系统实战:甘肃特产线上平台解析 这个项目做完前后大概花了三周时间名字看着很直白——Vue基于Java的甘肃特产商城销售系统本质上就是一个标准的 Spring Boot Vue 前后端分离商城项目。做这个系统的初衷是给甘肃本地的农特产品做一个线上销售展示平台像兰州百合、苦水玫瑰、临泽小枣、岷县当归这类有地域标签的商品通过商城的形式统一管理、在线交易同时给商家留一个独立的运营后台。标题里的“商家_d3wdv0e7”可以理解成这套系统的商家端实例标识它在项目里对应着 seller 这个角色也对应一套独立的部署环境配置。如果你正在找毕业设计、课程设计或者想拿一个完整的全栈项目练手这篇文章应该能让你少走不少弯路。我不会只贴代码而是把整个系统的设计思路、数据库关系、关键接口、前端交互以及我在实际开发中踩过的坑都拆开讲一遍。内容偏实战适合已经掌握了 Java 和 Vue 基础语法、但还缺一个完整项目串联经验的读者。1. 项目概述与方案选型过程1.1 这套系统的核心业务到底长什么样甘肃特产商城业务上最核心的不是“卖货”这两个字而是需要同时处理三类角色的诉求普通用户要能浏览商品、加购物车、下单支付、查看物流商家要能维护自己的商品、处理订单、管理库存平台管理员要能审核商家、管理分类、查看全站数据。这意味着系统从一开始就不能只做用户端必须把后台管理和商家端一起设计进去。用户端主要是商品展示和交易流程商品列表需要支持分类筛选和关键词搜索商品详情页要有轮播图、规格选择、库存展示。订单流程是整个商城的灵魂从购物车到结算页再到提交订单、支付、发货、收货每一步的状态都要能被追踪。商家端侧重商品上下架、库存修改、订单发货平台管理端则负责系统全局配置。1.2 为什么选 Vue Java 这个组合这个选择其实不复杂。市面上能搭商城的方案很多有电商 SaaS 平台直接买有用 PHP 或 Node 快速实现的也有 Spring Cloud 微服务全家桶。但作为个人项目或者毕设Spring Boot Vue是性价比最高的组合。Java 生态在高校和企业里普及率最高网上关于权限、支付、部署的资料也最多遇到问题随便一搜都有答案Vue 上手曲线平缓组件化开发方式很适合商城这种多模块页面Element Plus 这类组件库能快速做出合格的界面。前后端分离是这套架构的必然选择。前端独立打包成静态资源后端只提供 JSON 接口开发时两边可以并行推进部署时也能各自扩容。比起传统的 JSP 或 Thymeleaf 模板渲染前后端分离的调试体验和后期可维护性都要好不少这也是现在企业里最主流的开发模式。1.3 技术栈清单和版本选型参考后端我用的 Spring Boot 2.7.x这个版本相对稳定网上资料也最多。ORM 框架选了 MyBatis-Plus因为它内置了分页插件和简单的 CRUD 方法能省去大量重复的 SQL 编写。数据库用 MySQL 8.x缓存用 Redis主要用于验证码存储、Token 管理和热点商品缓存。登录鉴权采用 JWT无状态、好扩展。前端用 Vue 3 Vite状态管理用的 Pinia路由用的 Vue Router 4UI 组件库用的是 Element Plus。HTTP 请求统一封装 Axios配合拦截器处理 Token 注入和 401 跳转。这里想提醒一点很多人在版本上纠结其实没必要追新稳定能用才是第一位。比如 Spring Boot 3 配合 JDK 17 虽然性能更好但很多老资料不兼容出了问题查起来很麻烦。我的选择是 JDK 8 Spring Boot 2.7稳妥。2. 数据库设计与核心业务模型拆解2.1 从零设计一张覆盖商城全流程的表结构数据库设计是这套系统里最考验功力的部分它决定了后面能做什么功能、不能做什么功能。我的核心表一共九张用户表、商家表、商品分类表、商品表、库存表SKU、购物车表、订单表、订单明细表、收货地址表。用户表和商家表分开设计是因为用户和商家虽然都是账号体系但属性和权限边界完全不同。用户表主要存联系方式、积分、会员等级商家表则要有店铺名称、店铺简介、审核状态、营业执照信息。平台管理员不单独建表直接通过角色字段标识。商品表和库存表是最容易被人忽略、但也是最重要的关系。比如同一款“兰州百合”有 300g 装和 500g 装两个规格价格和库存都不一样。如果直接在商品表里存价格和库存那每一次增加规格都要改表结构后期维护成本极高。所以我把商品基本信息放在 product 表把规格、价格、库存、SKU编码放在 sku 表通过 product_id 关联。2.2 购物车和订单的数据流转逻辑购物车表相对简单关联字段有 user_id、sku_id、数量、勾选状态。真正复杂的是订单表因为它需要冗余一份商品快照信息。为什么必须冗余因为商品的价格和名称随时可能被商家修改订单一旦生成用户看到的下单价格必须锁定。如果订单明细表直接关联商品表的实时数据商家改价之后用户的历史订单就全乱套了。订单表的核心字段包括订单号、用户ID、商家ID、订单状态、商品总金额、运费、实付金额、收货地址快照、支付时间、发货时间、完成时间。订单号我采用“时间戳 用户ID后四位 随机数”的生成策略比如 20250108153012 后拼接四位随机数这样既保证唯一性又能从订单号直接看出下单时间。订单状态我用一个枚举类管理包含 PENDING_PAYMENT 待支付、PENDING_SHIPMENT 待发货、SHIPPED 已发货、COMPLETED 已完成、CANCELLED 已取消、REFUNDING 退款中。状态流转是单向的每个状态能执行什么操作都有严格限制比如待支付状态下用户能取消商家不能发货已发货状态下用户可以申请退款不能直接取消。这个状态机设计的合理与否直接决定了订单模块的代码复杂度。2.3 一套值得参考的表字段设计示例以商品表为例我用表格把核心字段列出来方便你对照设计自己的表字段名类型说明idbigint主键自增category_idbigint商品分类IDseller_idbigint所属商家IDproduct_namevarchar(128)商品名称main_imagevarchar(255)商品主图地址detailtext商品详情富文本statustinyint上下架状态0下架1上架origin_placevarchar(64)产地如“甘肃兰州”shelf_lifevarchar(32)保质期说明create_timedatetime创建时间update_timedatetime更新时间这里加上 origin_place 和 shelf_life 是考虑到甘肃特产有很多生鲜和农副产品产地和保质期是用户下单时非常关注的信息也方便以后做产地溯源这类扩展功能。设计表之初就为业务特性预留字段比我一开始只按通用商城模板建表后期再改动要省心得多。3. 后端服务搭建与核心接口实现3.1 工程目录结构和分层思想后端我按经典的四层结构来组织controller 接收请求、service 写业务逻辑、mapper 做数据库操作、entity 放实体类。另外加了 config 包放配置类common 包放统一返回结果和异常处理utils 包放 JWT、订单号生成等工具类。一个简单但清晰的包结构如下com.gansu.mall ├── controller // 接口层 ├── service // 业务层 │ └── impl ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 请求和响应对象 ├── config // 配置类 ├── common // 公共响应、异常处理 └── utils // 工具类为什么强调分层因为商城业务逻辑比较复杂拿下单来说它涉及到校验库存、计算金额、生成订单、更新库存、清空购物车、生成支付单等多个步骤。如果这些逻辑全写在 controller 里一个接口几百行代码后期根本没法维护。分层之后controller 只负责参数校验和结果返回service 里串联具体业务代码可读性和复用性都大幅提升。3.2 JWT 登录鉴权与拦截器配置登录这块我采用手机号 密码的方式注册登录密码通过 BCrypt 加密存储登录成功之后生成 JWT 返回前端。JWT 的有效期设置是两小时前端在 axios 拦截器里取出 Token放到请求头的 Authorization 字段。后端需要写一个拦截器统一校验所有需要登录的接口。拦截器里对白名单放行比如用户的注册登录接口、商城的商品查询接口都不需要登录。校验时从请求头解析出 Token解析成功把用户ID放入 ThreadLocal解析失败直接返回 401 状态码。核心代码如下public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BusinessException(401, 未登录或登录已过期); } Claims claims JwtUtil.parseToken(token.replace(Bearer , )); UserContext.set(claims.get(userId).toString()); return true; } }UserContext 本质是一个 ThreadLocal 包装类在请求结束后要及时 clear避免线程复用导致数据串号。这个坑我印象很深刚开始没清理 ThreadLocal高并发下偶尔会出现一个用户查到另一个用户购物车的情况排查了很久才发现是这个原因。3.3 商品模块和库存扣减的并发处理商品模块的查询接口要支持分页、分类筛选、价格排序、关键词搜索。数据量不大的时候直接用 MyBatis-Plus 的分页插件加条件构造器就够用了。但如果商品数量增多我建议在商品表的 name 字段上加全文索引或者引入 Elasticsearch这也是后续可以扩展的方向。库存扣减是商城系统里最典型的并发问题。用户下单时不能只做“查询库存 → 判断充足 → 扣减”这三步因为在高并发情况下会超卖。我采用的方案是 SQL 层面的原子操作一次性完成条件判断和扣减UPDATE sku SET stock stock - #{num} WHERE id #{skuId} AND stock #{num}这条 SQL 的执行效果是只有当当前库存大于等于购买数量时才执行扣减并且数据库行锁保证了同一时刻只有一个事务能改这条记录。受影响的行数是 1 说明扣减成功是 0 说明库存不足直接给用户返回“库存不足”的提示。这个方法比在 Java 代码里加锁性能好得多比乐观锁版本号方案也更简洁。3.4 订单生成接口的完整流程订单接口是整个后端最核心、也最容易写乱的一个接口。正常的流程是接收下单请求取出购物车中选中的 SKU 列表逐个校验商品是否处于上架状态、库存是否充足然后计算总金额生成主订单和子订单批量扣减库存清空购物车返回支付所需参数。这里我遇到一个实际的问题一个订单里可能包含不同商家的商品如果 A 商家的商品已经发货了B 商家的还在备货订单状态怎么处理我的做法是同一用户同一批商品生成一个主订单再按商家维度拆分成多个子订单。每个子订单有自己独立的订单状态和物流信息主订单的状态根据子订单的状态汇总计算。用户在“我的订单”页面看到的是主订单点进去能查看每个商家的发货进度。这个拆单逻辑虽然增加了一些编码量但更符合真实电商的业务形态。4. 前端页面设计与核心交互实现4.1 Vue 工程创建与路由权限控制前端我用 Vite 创建 Vue 3 项目安装了 vue-router、pinia、axios、element-plus 这几个核心依赖。创建项目以后第一件事是配置路由这里我用的是动态路由的思路所有页面组件按模块放在 views 目录下在路由配置里区分“不需要登录的页面”和“需要登录的页面”。用户端页面包括首页、商品分类页、商品详情页、购物车页、结算页、个人中心页。商家端单独用一套布局挂在 /seller 路径下包含商家商品管理、订单管理、数据概览等页面。路由守卫在每次跳转前检查用户信息和 Token没有登录一律跳转到登录页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })这里的坑在于前端判断有没有登录只能看本地有没有 Token但 Token 是否过期需要后端接口验证。如果只在前端判断会出现 Token 已经过期但页面还能打开、接口全部报 401 的情况。所以真正的登录状态校验是在 axios 响应拦截器里捕获 401然后统一跳转登录页并清除本地缓存。4.2 axios 封装与用户状态共享axios 封装是前端的一个基础工程所有请求都要走统一的实例。我在 request.js 里做了三件事设置 baseURL在请求拦截器里自动携带 Token在响应拦截器里统一处理业务错误码和 HTTP 状态码。后端接口返回格式统一为 { code: 200, data: {}, msg: success }前端在拦截器里判断 code非 200 的 code 直接弹出错误提示。用户登录状态用 Pinia 管理登录成功之后把用户信息、Token 存到 Pinia 和 localStorage。刷新页面时从 localStorage 恢复用户状态并调用一次“获取当前用户信息”的接口确保数据是最新的。购物车数量也可以在用户信息里冗余一个 totalCartCount 字段登录后显示在导航栏的购物车图标上。4.3 商品列表、购物车、结算页的实现细节商品列表页是用户进店之后看到的第一屏性能直接影响转化率。列表页一次加载 12 条商品使用 el-card 展示商品图、名称、价格、销量支持分类切换和关键词搜索。价格显示需要注意商品口径的价格应该读取当前 SKU 的最低售价但列表页如果要拿最低价SQL 查询会比较费劲。我这里的做法是在 product 表冗余一个 min_price 字段SKU 表价格变动时同步更新列表页直接读取冗余字段避免子查询。购物车页交互上有几个容易被忽略的细节选中状态要单独存不能和购物车记录混在一起数量修改要防抖处理不然用户连续点加减会发出大量请求金额总计要前端实时算但最终下单金额以后端计算为准。结算页是用户路径的最后一站。页面上要展示收货地址支持新增和切换、商品清单、金额明细商品总额、运费、优惠金额、实付金额、支付方式。提交订单按钮要做防重复提交处理我用了提交中禁用按钮加后端幂等校验的双保险方案。前端禁用按钮只是体验上的优化真正防止重复下单要靠后端——生成订单前先查询是否存在相同用户和相同业务流水号的订单有就直接返回没有才创建。4.4 商家端页面的快速搭建思路商家端页面相比用户端要简单得多核心是商品管理和订单管理。商品管理页包含商品列表、新增商品、编辑商品三个部分新增和编辑共用一个表单组件。表单里的规格录入是动态的商家可以点“添加规格”按钮实时增加一行 SKU 输入框提交时组装成数组传给后端。这个交互用 Element Plus 的 el-form 和动态表单组件就可以实现代码量不大但实用性很强。订单管理页面更直接商家看到的是分配给自己的、状态为待发货的订单点击发货按钮后填写物流公司和物流单号订单状态变成已发货。我特意在商家订单列表页加了数据统计卡片展示“今日新增订单、待发货数、待处理退款数”让商家一进后台就能掌握当天经营概况。5. 部署上线全过程与常见问题排查实录5.1 前端打包与 Nginx 部署配置项目开发完以后要正式部署这一步很多第一次做前后端分离项目的人会卡住。前端打包用 npm run build输出一个 dist 静态目录。后端用 Maven 执行 package 命令生成一个可执行的 jar 包。部署时我在服务器上装了 Nginx把前端静态文件放到 /usr/share/nginx/html然后配置反向代理把所有以 /api 开头的请求转发到后端的 8080 端口。这样一个 Nginx 同时承担了静态文件服务和接口代理两个职责避免了前端直接请求后端接口带来的跨域问题。Nginx 配置片段如下server { listen 80; server_name yourdomain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }location / 里的 try_files 配置必不可少它的作用是让 vue-router 的 history 模式在刷新页面时不报 404。第一次部署时我没加这行配置用户停留在商品详情页按 F5 刷新Nginx 找不到对应的物理路径直接返回 404排查了半天才发现是这个原因。5.2 常见报错与排查方法速查表开发过程中遇到的报错我整理成了一张速查表很多问题具有一定的普适性问题现象可能原因解决办法Element Plus 的 ElMessage 提示未定义组件库按需引入时未注册 ElMessage 相关样式在 main.js 里全局引入完整样式或单独引入 message 样式前端请求接口报跨域前后端域名或端口不一致后端配置跨域过滤器或使用 Nginx 反向代理上传商品图片报文件大小超限Spring Boot 默认单文件最大 1MB在 application.yml 中调整 spring.servlet.multipart.max-file-size刷新页面后登录状态丢失没有持久化 Token 或刷新时未重新读取使用 localStorage 持久化路由守卫里读取后恢复库存偶尔出现负数并发场景下扣减库存不是原子操作使用 UPDATE 条件判断扣减保证原子性订单创建成功但购物车没清空清空操作失败后事务未回滚下单和清空购物车放在同一个事务里异常整体回滚5.3 项目上线后要注意的几点安全配置项目上线前我做了几项关键配置确保系统不会被轻易打穿。一是数据库账号不使用默认的 root而是单独创建只具备单库权限的账号二是 JWT 密钥不要写在代码里通过环境变量注入三是后端的全局异常处理器要做好兜底任何未捕获的异常都返回统一格式的错误响应不要把堆栈信息直接暴露给前端四是商家上传的图片文件要做类型校验只允许 jpg、png、webp 等白名单格式防止上传恶意脚本文件。还有一个容易被忽略的点后端接口要做好参数校验尤其是订单金额、购买数量这类关键参数。虽然前端已经做了校验但接口是公开的直接拿 Postman 调接口完全可以绕过前端限制。我在下单接口里强制要求传用户ID服务端从登录态中取用户ID而不是信任前端传入的用户ID这样才能防止越权操作。5.4 个人总结做完整套系统后的几点真实感受这个项目做完之后我对前后端分离开发有了更深的体会。以前学框架的时候每个知识点都是孤立的Spring Boot 会用了、Vue 会用了但不知道它们之间怎么配合。真正做完一个完整的商城系统才会理解接口设计为什么要统一返回格式、状态码为什么要约定好、跨域问题为什么会在前后端联调时集中爆发。这些都是教科书上学不到、只有踩过坑才能记住的经验。如果再给我一次机会从头做我会在动手编码之前先花更多时间把数据库表关系和状态流转搞清楚。表结构设计合理后面写业务的效率会提升一倍表结构设计不合理后期每次加需求都像在修补一座地基歪了的房子。另外我强烈建议做完核心功能之后尽早部署一版到云服务器上不要等全部功能做完再部署。因为很多问题只有在真实的部署环境中才会暴露比如静态资源路径、上传文件存储位置、数据库连接池配置等早部署早发现问题早解决。做这套系统的过程虽然辛苦但收获非常大。它让我完整地走了一遍从需求分析、数据库设计、接口开发、前端实现到服务器部署的全流程也让我对电商系统的业务逻辑有了体系化的认知。如果你也准备上手类似的商城项目我建议你参考这个思路先理清角色和业务流程再动编码过程中遇到的问题逐个记录下来做完你会发现你的收获远不止一套代码那么简单。
返回列表