ARTICLE DETAIL

资讯详情

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

Spring Boot+Vue体育商城实战:前后端分离架构与核心模块设计

Spring Boot+Vue体育商城实战:前后端分离架构与核心模块设计 独立开发一个基于 Spring Boot Vue 的体育购物商城从立项到落地我攒了不少实打实的经验。这个项目不只是一个普通的电商练手作品在商品体系、订单流转、会员营销和前后端分离架构上都有不少值得掰开揉碎讲一讲的地方。如果你正准备做类似的毕业设计或者想系统掌握前后端分离开发套路这篇总结应该能帮你少走很多弯路。1. 项目整体设计与技术选型思路先说说为什么选 Spring Boot Vue 这个组合。市面上电商项目模板虽然多但这个组合在真实企业环境里覆盖面最广。Spring Boot 负责后端接口和业务逻辑Vue 负责页面交互和用户界面两者通过 RESTful API 通信天然就是前后端分离的成熟形态。退一步说就算以后想扩展到小程序端或者 App 端后端接口完全可以复用改动成本极低。1.1 从需求出发的模块划分体育购物商城听起来只是“卖货”但仔细拆解下来核心业务域其实很清晰。第一块是商品中心。体育用品有鲜明特点分类层级深比如运动鞋下面要按篮球鞋、跑步鞋、休闲鞋继续分属性维度多鞋码、颜色、材质、适用场地都得能筛选。所以商品表我拆成了商品基本信息表、SKU库存量单位表、分类表、品牌表和图片表五张核心表而不是把一堆字段全堆在一张表里。这样以后加促销活动或者做推荐系统扩展起来都不费劲。第二块是交易中心。包含购物车、订单、支付和售后。这里我特别把订单状态设计成枚举待付款、待发货、待收货、已完成、已取消。每个状态间的流转都有对应的接口校验防止用户跳过前置环节乱提交。比如未付款的订单不能随意取消发货超时未付款的订单由定时任务自动关闭并释放库存。第三块是会员与营销中心。注册登录用 JWT 做无状态认证支持手机号密码登录。营销这块做了优惠券模块和积分模块下单选券、下单返积分这两条链路我都跑通了。虽然这个模块业务量不大但把这套逻辑捋顺之后电商系统的核心玩法基本就掌握了。第四块是后台管理系统。基于 Vue 做了一套包含商品管理、订单处理、用户管理、数据看板的运营后台。管理员可以上下架商品、修改库存、处理订单退款请求还能看到每日订单量和热门商品排行。1.2 为什么选择前后端分离架构我最早也犹豫过用传统的 Thymeleaf 模板渲染一套代码全搞定部署也简单。但想清楚这几个点之后我果断切到了前后端分离。团队协作是首要原因。前后端分离之后前端工程师专心写页面调接口后端工程师专注业务逻辑和 SQL 优化两边只需要约定好接口文档开发节奏完全解耦。我自己一个人开发可能感受不明显但如果以后去公司做项目这是标配。第二个原因是多端复用。商城前端的接口设计成标准 JSON 格式以后要出小程序端或者移动 H5 端后端代码一行都不用改。做管理后台也是同一套接口只是页面端不同而已。第三个原因是部署和维护更灵活。前端打包成纯静态文件扔到 Nginx 里就行后端打 jar 包跑在服务器上两者可以各自扩容。哪怕前端挂了也不影响后端接口用户体验上可维护性高不少。1.3 技术栈版本选型与理由版本选型这里吃过不少亏。Spring Boot 我现在用的是 2.7.x 版本而不是 3.x。Spring Boot 3.x 虽然性能更好、底层是 Jakarta EE 规范但我查企业实际项目的主流版本分布2.7.x 的占用率依然非常高。很多中间件、工具包的自动配置都是基于 2.x 做的比如我用的 MyBatis-Plus、Hutool 这些虽然新版本已经适配 3.x但老项目迁移时踩坑概率还是偏高的。对新手来说2.7.x 的资料多、问题答案多遇到报错搜一下就能解决。配套的版本我很早就固定下来了JDK 1.8与 Spring Boot 2.7.x 搭配最稳定MySQL 5.7这个版本在电商场景里验证最充分MyBatis-Plus 3.5.x查询性能和开发效率兼得Redis 2.6.x用来存缓存、JWT 黑名单、购物车临时数据Vue 2.6.x Element UI生态成熟组件够用版本锁定这个习惯特别重要我见过太多人因为依赖版本冲突光调 Maven 就调了整整两天。2. 后端 Spring Boot 核心模块实操要点后端是整个商城的发动机。我按照“分层架构 模块化”的思路来组织代码每个业务模块独立成一个包包里放 controller、service、mapper、entity、dto 五层。这样以后要抽离单独的服务直接把包带走就行耦合度非常低。2.1 统一的接口返回格式与异常处理前后端联调最烦的事情就是接口返回格式不统一。前端一会儿收到正常数据一会儿收到异常堆栈页面根本没法做统一处理。我第一时间就把返回值封装成了统一结构。public class ResultT implements Serializable { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }code 用 200 表示正常400 表示参数错误401 表示未登录403 表示无权限500 表示服务器内部异常前端拿到 code 不等于 200 就直接弹错误提示并跳转对应页面。全局异常处理器也是必备的。Spring Boot 提供了RestControllerAdvice注解我在这里统一捕获业务异常、参数校验异常和兜底异常把抛给前端的错误信息统一成中文提示而不是一堆英文堆栈。2.2 用户鉴权与 JWT 的完整实现商城系统里用户登录后要能在多个接口中识别身份。Session 方案需要服务端存储会话前后端分离之后还得处理跨域携带 Cookie 的问题比较麻烦所以我用的是 JWT 方案。实现步骤大致是这样用户登录成功后根据 userId、用户名、过期时间生成 token并存进 Rediskey 是login_token:userIdvalue 是 token过期时间和 JWT 的过期时间保持一致。前端拿到 token 后存到 localStorage每次请求在请求头里带上Authorization: Bearer token。后端拦截器从请求头中解析 token校验签名和过期时间。校验通过后把 userId 放到 ThreadLocal 里方便后续业务代码直接调用。用户退出登录时把 Redis 里的 token 删掉相当于服务端主动让 token 失效。拦截器核心代码public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } token token.substring(7); // 校验 token 签名和过期时间 Claims claims JwtUtil.parseToken(token); if (claims null) { response.setStatus(401); return false; } // 从 Redis 中校验 token 是否存在避免注销后的 token 还能继续使用 String redisToken (String) redisTemplate.opsForValue().get(login_token: claims.get(userId)); if (!token.equals(redisToken)) { response.setStatus(401); return false; } request.setAttribute(userId, claims.get(userId)); return true; } }JWT 最值得注意的一点是服务端无法主动让 token 失效签发的 token 在过期前始终有效。所以我用了 Redis 做二次校验这相当于给 JWT 加了一层“主动注销”的能力安全性提升了一个档次。2.3 商品SKU与库存扣减的设计细节商品模块的难点在 SKU 设计。我拿“篮球鞋”举例同样一款鞋有黑色和白色两种颜色每种颜色有 40、41、42、43 四码这就组合出八种 SKU。每个 SKU 有自己的条形码、库存和价格用户下单时选的是具体某个 SKU不是笼统的那个商品。表结构设计上spu 表存商品公共信息比如标题、描述、主图sku 表存规格组合、价格、库存。前端页面展示时通过 spuId 查出所有 sku再通过规格值分组渲染出选择按钮。库存扣减是整个商城最容易出错的地方。我最早用的是先查库存再更新的写法后来在压测中发现了超卖问题。// 错误示例先查后改并发下会超卖 int stock skuMapper.selectStock(skuId); if (stock quantity) { skuMapper.reduceStock(skuId, quantity); }改成原子操作之后问题就消失了// 正确示例UPDATE 语句自带行锁一次操作保证原子性 int rows skuMapper.reduceStock(skuId, quantity); if (rows 0) { throw new BusinessException(库存不足); }reduceStock的 SQL 是update sku set stock stock - #{quantity} where id #{skuId} and stock #{quantity}只要更新影响行数为 0说明库存不够直接事务回滚。这个方案在秒杀场景下也够用如果流量再大再引入 Redis 预扣库存和消息队列异步落库。2.4 集成支付与回调处理体育商城要打通真实支付链路我接入的是支付宝沙箱环境。沙箱环境和真实环境的接入逻辑完全一样只是用测试账号和测试金额非常适合做学习和联调。支付的核心流程是用户点击支付后端创建支付请求调用支付宝接口生成支付表单。用户在支付宝页面完成支付操作。支付宝异步通知商城后端服务告知支付结果。后端校验通知签名、金额、订单号确认无误后更新订单状态。提醒用户支付成功前端轮询订单状态接口刷新页面。异步通知的处理有几个关键点验签必须放在第一位防止伪造通知幂等处理要做好同样的回调可能收到多次不能导致订单状态混乱回调处理里更新订单状态和减少库存要放在同一个事务里保证数据一致性。我遇到过最典型的坑是回调地址在本地开发时无法接收支付宝的异步通知。支付宝服务器根本访问不到我的 localhost。后来我用内网穿透工具把本地服务映射到公网地址回调地址填映射后的公网地址这才跑通完整链路。3. 前端 Vue 框架与商城页面实战前端这部分我花的时间其实比后端多。页面多、交互繁、联调细每一个环节都会出各种意想不到的问题。Vue 的核心知识点从组件通信到路由守卫在商城这种多页面项目中基本都能用到。3.1 项目初始化与常用目录结构规划我用 Vue CLI 创建项目选型时直接选了 Vue 2 Router Vuex Axios 的组合。src/ ├── api/ // 接口请求封装按模块拆文件 ├── assets/ // 静态资源 ├── components/ // 通用组件比如商品卡片、分页、上传组件 ├── router/ // 路由配置 ├── store/ // Vuex 状态管理 ├── utils/ // 工具函数比如请求封装、日期格式化 ├── views/ // 页面组件按功能模块分目录 │ ├── home/ // 首页 │ ├── goods/ // 商品列表、商品详情 │ ├── cart/ // 购物车 │ ├── order/ // 订单确认、订单列表 │ └── user/ // 个人中心、登录注册 └── App.vueaxios 请求封装我做得比较早。utils/request.js里统一设置了 baseURL、超时时间并在请求拦截器中添加 tokenimport axios from axios const request axios.create({ baseURL: /api, timeout: 15000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { // 统一处理业务错误 Message.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { // 未登录或token过期跳转登录页 router.push(/login) } Message.error(网络异常) return Promise.reject(error) } ) export default request这个封装带来的好处是业务代码里只需要写request.get(/goods/list)然后拿数据不需要关心 token 是怎么带的、错误是怎么弹的。接口复用率高维护也轻松。3.2 商品多条件筛选与搜索功能实现商品列表页是前端最复杂的页面之一。筛选条件包括分类、品牌、价格区间、鞋码、颜色、排序方式。我采用了“筛选条件 查询参数联动 URL”的方案。用户点击筛选项触发 handleFilter 方法把选中的条件更新到 data然后调用后端接口拉取商品列表。同时用this.$router.replace({ query: params })把筛选参数同步到 URL 上这样用户刷新页面筛选状态也不会丢直接能够通过链接分享给好友。后端搜索接口我用 MyBatis-Plus 的 QueryWrapper 做动态条件拼接参数可以为空为空时不加筛选条件QueryWrapperGoods wrapper new QueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(name, keyword); } if (categoryId ! null) { wrapper.eq(category_id, categoryId); } if (minPrice ! null) { wrapper.ge(price, minPrice); } if (maxPrice ! null) { wrapper.le(price, maxPrice); }排序用wrapper.orderByDesc(sales)或wrapper.orderByAsc(price)动态切换。数据量大之后这里需要加索引优化否则慢查询会直接把数据库拖垮。3.3 购物车数据状态管理与结算逻辑购物车我分了两套未登录用户购物车数据存 localStorage登录用户的购物车数据存后端数据库。这样既保证了游客体验又保证了登录后数据不丢失。购物车页面里用户可以直接修改数量、勾选商品、删除商品底部的合计金额要实时变化。为了防止每次操作都请求后端我把购物车状态放进了 Vuexstate: { cartList: [], selectedItems: [] }, mutations: { updateCartList(state, list) { state.cartList list }, toggleSelect(state, skuId) { // 更新选中状态 } }, getters: { totalPrice(state) { return state.selectedItems.reduce((sum, item) sum item.price * item.quantity, 0) } }进入结算页面时把选中的商品信息、收货地址、优惠券信息一起提交给后端后端生成订单并返回订单号。这里要注意的是前端展示的价格只是一个参考真正下单时的价格结算必须以后端计算为准防止用户通过修改请求参数来改价。3.4 处理视频播放 M3U8 流媒体的踩坑总结体育商城的商品详情页有时候需要展示运动鞋的试穿视频或者护具产品的使用教程。我接到的视频源有一部分是 M3U8 格式的流媒体地址。Vue 里直接放video标签播放 M3U8 是不支持的需要用 hls.js 这个库来做兼容。解决方法很直接在商品详情页安装 hls.js视频地址判断为 M3U8 时用 Hls 初始化npm install hls.jsimport Hls from hls.js if (Hls.isSupported() videoSrc.endsWith(.m3u8)) { const hls new Hls() hls.loadSource(videoSrc) hls.attachMedia(videoElement) hls.on(Hls.Events.MANIFEST_PARSED, () { videoElement.play() }) }这里有个容易忽略的点M3U8 视频地址往往涉及跨域问题需要在 Nginx 层配置Access-Control-Allow-Origin头否则资源请求会被浏览器拦截。另外视频目录的防盗链设置不能做得太死否则用户直接无法播放。我在测试时就因为防盗链 Referer 白名单没配好整整排查了一天最后才发现是请求被服务器拒了。4. 管理后台、部署上线与核心问题排查商城的管理后台是运营人员每天都要用的系统功能密度和复杂度不比用户端低。前端这一侧我基于 Vue Element UI 快速搭了一套后台布局左侧是菜单右侧是内容区顶部是管理员信息和退出按钮。菜单定义在路由配置里权限控制和菜单生成是联动的。4.1 后台权限控制与角色管理管理员角色分超级管理员和普通运营。超级管理员可以管理管理员账号、查看全部数据普通运营只能操作商品模块不能动用户和订单。这个权限控制我在后端通过拦截器拦截请求并校验操作权限。具体做法是菜单表存菜单和对应的权限标识符比如goods:add、goods:edit、order:list。管理员表关联角色表角色表关联权限表。登录后用户拿到的权限标识符集合存到 Redis每次请求接口时拦截器比对当前请求所需的权限标识符是否在集合里。这个方案适用于中小型后台不用引入 Spring Security 和 Shiro 这种重型安全框架自己手写拦截器也完全够用。等以后权限粒度细到部门层级或者数据范围层级再考虑升级方案。4.2 商品图片上传及回显的完整处理流程商品图片上传是后台管理的常用功能我前后踩了三个坑这里重点说说。第一是上传接口的大小限制。Spring Boot 默认上传文件大小是 1MB商品图一般 2-3MB 很正常所以必须修改配置spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB第二是文件存储路径问题。开发环境下我直接把文件保存在项目的upload目录生产环境上传到服务器后我把路径做了统一配置通过配置文件区分环境不让代码写死路径。Nginx 配置一个虚拟目录映射到上传目录前端直接通过 URL 访问图片资源。第三是图片回显的路径拼接问题。数据库中存的是相对路径前端展示时需要拼接上 Nginx 地址。我在结果对象里增加了一个imgUrl字段由后端拼接完整地址返回给前端前端直接用。4.3 前端打包部署遇到的坑与 Nginx 配置详解前后端联调本地没问题部署到服务器上之后就冒出各种问题这里把踩坑最深的几个记录下来。第一个坑刷新页面 404。Vue 路由默认是 history 模式路由路径在服务器上不存在物理文件刷新时 Nginx 找不到对应路径直接返回 404。解决办法是 Nginx 配置try_fileslocation / { root /usr/share/nginx/html/; index index.html; try_files $uri $uri/ /index.html; }第二个坑接口请求跨域。虽然开发环境用了 Vue 的 devServer 代理解决跨域但部署后前后端是两个不同的域名或端口浏览器还是拦截跨域请求。我在 Nginx 反向代理里做了配置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; }这样前端请求/api/goods/listNginx 会把请求转发给后端服务的/goods/list接口跨域问题彻底消失。第三个坑图片加载不出来。原因是上传的图片文件在 Nginx 的静态资源目录里没有映射我重新配置了一个静态资源映射 location。第四个坑前端资源缓存导致的样式错乱。每次打包后文件指纹变化但浏览器缓存的旧文件没失效。Nginx 里给静态资源设置了协商缓存并打开 gzip 压缩网站加载速度提升了不少。4.4 后端安全漏洞排查与修复经验做这类电商项目别人往往关注功能是否齐全但我花了不少时间排查安全问题虽然踩了坑但这部分经验非常值得记录下来。最典型的例子是 Spring Boot Actuator 端点暴露问题。项目刚上线时我为了看系统状态把 actuator 所有端点都打开了结果被别人扫描到通过 heapdump 端点下载了 JVM 堆内存快照把里面临时存储的用户信息直接拿到了。这次教训非常深刻后续采取了这些措施限制 Actuator 端点的暴露范围management: endpoints: web: exposure: include: health,info同时给 Actuator 接口加上访问鉴权不在内网环境直接裸奔暴露。还检查了其他安全项管理员密码强制复杂度校验、登录接口加验证码防暴力破解、商品订单接口加参数校验防止超卖和负数订单、文件上传接口限制文件类型和大小防止上传恶意脚本、不在日志中打印用户敏感信息。做电商项目安全不是可有可无的功能而是必须认真对待的基础要求。5. 常见问题速查表与排错经验开发过程中遇到的很多问题我都记录在案了这里把最常遇到的整理成表方便你能直接对照排查。5.1 后端高频报错与解决方案报错信息原因分析解决方案Caused by: java.sql.SQLSyntaxErrorException: Unknown column实体字段和表字段映射不一致检查驼峰命名和数据库下划线命名的映射配置Field xxx doesnt have a default value插入数据时必填字段为空检查 insert 语句字段是否齐全或给数据库字段设置默认值401 Unauthorized请求未带 token 或 token 已过期检查前端拦截器是否配置了请求头Redis 中的 token 是否被误删Connection refused: connect服务端口被占用或数据库未启动检查端口占用情况netstat -ano查看监听状态Invalid bound statement (not found)Mapper XML 文件没扫描到或 namespace 写错检查 XML 文件的 namespace 是否和 Mapper 接口一致BeanCreationException: Error creating bean依赖注入的 Bean 不存在或配置错误检查Autowired的类是否加了 Spring 管理注解甲方项目无法启动端口被占用上一次启动的进程没有关闭Windows 用 netstat -ano支付宝回调验签失败公钥配置错误或参数顺序错误确认配置的支付宝公钥是“应用公钥”对应的“支付宝公钥”验签代码重新检查5.2 前端高频报错与解决方案报错信息原因分析解决方案Cannot read property xxx of undefined接口返回的数据里没有该属性给初始 data 设置默认值或者做好空值判断SyntaxError: Unexpected token in JSON后端返回了 HTML 而不是 JSON检查请求 URL 是否被 Nginx 拦截接口路径是否拼错Failed to load resource: 404请求的资源不存在或路径错误检查 Nginx 的 root 路径和 try_files 配置[Vue warn]: Property or method xxx is not defined模板中调用了未定义的对象或方法检查 data、methods 和 computed 中是否都定义好了Request failed with status code 401登录状态失效拦截器统一跳转登录页并清空本地 token一刷新就 404history 模式路由没有配置 try_filesNginx 的 location 中添加try_files $uri $uri/ /index.html;样式错乱或没有样式CSS 被缓存或资源路径错误清浏览器缓存检查打包后的 css/js 路径是否用绝对路径跨域请求被阻止前后端域名不一致开发环境用 Vue devServer 代理生产环境用 Nginx 反向代理5.3 前人踩坑高频点预警有一些问题出现的频率特别高但解决方式又比较隐蔽这里集中预警一下。数据库连接配置问题。很多人刚跑 Spring Boot 项目时报数据库连不上最常见的坑是 MySQL 和 Spring Boot 的驱动不匹配。我用的是mysql-connector-java5.1.47连接 URL 里必须加useSSLfalseserverTimezoneAsia/Shanghai。另外MySQL 8.0 要更详细地配置时区参数不然也会报时区错误。Redis 缓存穿透问题。商城首页的轮播图、热门商品这些高频访问的数据我一开始直接用缓存但总有用户频繁请求不存在的商品 ID导致请求打穿缓存直接怼到数据库。后来做了空值缓存和布隆过滤器穿透问题才真正缓解。对小型商城来说空值缓存加超时时间是最简单有效的方案。Maven 依赖下载慢或失败。国内访问 Maven 中央仓库经常抽风我去阿里云 Maven 镜像改配置之后依赖下载速度快了一倍不止。这个改镜像的步骤在 pom.xml 配置 mirror 就可以了省时省力。前后端联调时网络请求状态 200 但业务报错。遇到这种情况不要慌先看 response body 里的 code 和 message问题大概率是参数不对或者后端业务逻辑做了校验不一定是 HTTP 层面的问题。6. 项目扩展方向与我的个人心得体会项目做到这一步核心功能已经全部落地但商城系统永远有迭代空间。我的扩展思路有三个方向不仅能完善功能也能为以后求职作品集或者实际商用增加亮点。6.1 可扩展的进阶功能方向搜索功能升级。现在的商品搜索还是 MySQL 的 LIKE 模糊查询数据量到十万级之后性能明显下降。可以引入 Elasticsearch 做全文搜索支持分词、拼音搜索、搜索建议、搜索纠错用户体验会提升很多。秒杀活动改造。现在的下单流程是用户点击立即购买然后锁定库存生成订单。高并发秒杀场景需要预先加载商品库存到 Redis再用 Lua 脚本保证减库存的原子性之后把订单消息推到消息队列再由消费者异步落库。这样能抗住瞬时并发冲击不会把数据库直接打挂。数据分析模块。在现有订单数据和用户数据基础上加入用户行为埋点统计商品的浏览量、加购量、成交量按品类分析销售趋势按用户分析复购率。这些数据对运营决策非常关键。6.2 我对整个项目的一些心得体会如果让我重新做一次这个项目我会把优先级调整得更合理先做核心交易链路再拓展营销玩法最后完善页面细节。我最想强调的是数据库设计。这个项目做到后期修改数据表结构的成本非常高因为新增字段会牵扯到实体类、接口参数、前端表单一改就是全链路。一开始就把表结构设计得足够细、足够扩展后面能省一半以上的返工时间。其次是日志的重要性。我在代码里加了请求日志和错误日志用 Slf4j 记录请求参数、用户 ID、耗时信息和异常堆栈。排查问题的时候日志就是你在暗夜里唯一的探照灯。没有日志线上出了问题只能靠猜效率极低。最后再说回技术选型。Spring Boot 和 Vue 这一套组合无论是做毕业设计、个人作品还是企业小项目性价比都非常高。学习路线清晰从入门到能单独接项目的周期短招人也好招维护也方便。如果你还在犹豫用什么技术栈不用纠结就选它。
返回列表