news 2026/10/13 15:28:29

基于SpringBoot+Vue3的电商系统设计与实践——前后端分离架构解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot+Vue3的电商系统设计与实践——前后端分离架构解析

最近一直在折腾一个网购平台的项目,从数据库设计到前后端联调,踩了不少坑,也积累了不少经验。这套系统用的是 Java SpringBoot + Vue3 + MyBatis,典型的前后端分离架构,数据库选的 MySQL,覆盖了用户、商品、购物车、订单、支付这些电商核心链路。今天就把整个项目的源码结构和核心实现思路整理一遍,给正在做类似课设、毕设,或者刚入行想了解电商系统怎么搭的同学一个参考。

先说一下这套系统能干什么。用户端可以注册登录、浏览商品分类、搜索商品、加购物车、提交订单、模拟支付、查看订单状态;后台管理端可以做商品上下架、库存管理、订单发货、分类维护、轮播图设置这些操作。说白了就是一套能跑起来的完整电商闭环,不是那种只有 CRUD 的 demo,而是每个环节都有业务逻辑兜底,能真实感受到前后端分离项目在工程层面是怎么组织的。

如果你是第一次接触这类项目,建议按我的思路先整体过一遍架构,再看数据库设计,然后顺着后端接口走到前端页面,最后处理部署和排查问题。这样比直接盲扒源码要高效得多。

1. 项目整体思路与架构拆解

1.1 为什么坚持做前后端分离

我见过很多同学做电商项目时习惯用模板引擎,后端一个 SpringBoot 应用把页面和控制层全包了。这种方案在简单场景下确实省事,但一旦涉及移动端适配、多端复用、前后端并行开发,就会很痛苦。这套网购平台我一开始就定了前后端分离的路子,后端只提供 RESTful API,前端用 Vue3 独立构建,两边通过 JSON 数据交互。

拆开之后的好处很明显。第一个是后端接口可以单独测试,用接口测试工具直接验证业务逻辑,不用每次改完代码都要启动一个浏览器去点页面。第二个是前端团队和后端团队可以并行推进,后端写完用户模块的接口,前端同步开发登录页面,互不阻塞。第三个是将来如果要做小程序或者移动端 App,后端 API 可以直接复用,不需要重写。

当然,分离也带来了一些额外成本,比如跨域处理、接口文档维护、前后端联调时字段对齐问题。这些在项目里我都做了对应处理:跨域通过配置拦截器统一解决,接口返回统一封装成 Result 对象,前端有独立的 request 工具类处理响应。这些细节等会儿会展开讲。

1.2 技术栈选型背后的逻辑

SpringBoot + MyBatis + MySQL 这套组合在中小型项目里非常成熟。SpringBoot 的核心价值在于自动配置,省掉了一大堆 XML 配置,一个启动类就能把应用跑起来,这对快速搭建原型很有帮助。MyBatis 虽然要自己写 SQL,但电商系统里经常会出现复杂的多表关联查询、动态条件拼接,用 MyBatis 的<where>、<if>标签反而比 JPA 那种全自动 ORM 更可控。性能上,手写 SQL 对索引的利用也更直接,查出什么问题一眼就能看到完整语句。

前端选 Vue3 而不是 Vue2,主要是看中了组合式 API 带来的逻辑复用能力。购物车、订单这类业务,代码量都不小,用 setup 语法可以把相关的响应式状态和方法聚在一起,比 Options API 那种把所有逻辑拆散在 data、methods、watch 里的方式清晰得多。配合 Pinia 做全局状态管理,多组件之间的数据同步问题也顺手解决了。

再就是 MySQL。电商系统的核心是事务,下单这个操作必须保证扣库存、生成订单、更新状态这三个动作要么全成功,要么全回滚。这个项目在订单生成方法上加了@Transactional注解,底层就是靠 MySQL 的 InnoDB 引擎支持事务。选型的时候没考虑用其他数据库,主要是因为 MySQL 的资料多、部署简单、生态成熟,出了问题好排查。

2. 数据库设计与核心表结构

2.1 核心业务表与字段规划

数据库是一个电商系统的地基,表结构设计不好,后面写接口的时候会处处难受。这个项目我规划了用户表、商品表、分类表、轮播图表、购物车表、订单表、订单明细表、收货地址表,一共 8 张核心表。初期没有引入过于复杂的营销表、优惠券表,避免把范围撑得太大,先把主干跑通。

用户表的设计要注意密码不能存明文,项目里用了 MD5 加盐的方式处理。用户名做了唯一索引,这是登录时的查询条件,没有索引的情况下用户多了以后会很慢。商品表里最关键的是价格字段,记住能用DECIMAL(10,2)就不要用FLOAT,浮点数在电商金额场景下会出现精度丢失,比如 0.1 加 0.2 可能等于 0.30000000000000004,这在结算时是致命的。

订单表是整个系统的核心,我设计了几个重要的状态字段:订单编号、用户 ID、订单金额、收货信息快照、订单状态、支付时间、发货时间、创建时间。这里有个容易忽略的细节,收货地址不能只存地址 ID,因为用户改地址后历史订单要保留下单时的收货信息,所以订单表里冗余存储了一份收货人信息快照。这在实际电商系统里是标准做法。

订单明细表则是把一次订单里的多个商品拆成单独记录,每条包含商品 ID、商品名称快照、商品图片、购买单价、数量、小计。商品名称也做了快照,因为商家改商品名后,历史订单应该展示当时的名称,而不是改完后的名称。

2.2 表关系与查询路径设计

表之间的关系其实很直观。用户表跟购物车表是一对多,用户表跟订单表是一对多,订单表跟订单明细表是一对多,商品表跟分类表是多对一。在设计外键时,我并没有在物理层建外键约束,而是保留逻辑关联。原因是外键在插入和更新时会带来额外的锁开销,而且这个项目里订单明细和商品表之间是弱关联——商品删了,历史订单记录还在,如果建了物理外键,删商品会被限制住。

典型的查询路径是这样的:用户在前端选好商品后,前端把购物车 ID 列表传给后端,后端查购物车表关联商品表,算出总价;用户下单后,后端往订单表和订单明细表插入数据,同时扣减商品库存;用户查询订单列表时,后端查订单表按创建时间倒序返回,点进详情页再查对应的订单明细。

分页这块用 MyBatis 的 PageHelper 插件,商品列表、订单列表、用户列表都走分页接口。这里要注意一个细节,pageNum和pageSize一定要做参数校验和上限控制,不然用户可以传一个极大值把全表数据一次拉走,在生产环境这是一个严重的隐患。我在前端也做了限制,pageSize最大 100。

3. 后端核心功能实现与业务逻辑

3.1 用户认证与 JWT 登录方案

登录模块看起来简单,但方案选型很关键。这个项目用了 JWT 做无状态认证,流程是用户登录成功后,后端生成一个包含用户 ID 和用户名的 token 返回给前端,前端把 token 存在本地存储里,之后每次请求都在请求头上带上Authorization: Bearer token字段,后端拦截器解析 token 拿到当前用户。

写 JWT 的时候有个容易踩的坑:token 里的过期时间不能设置太长,项目里设置了 24 小时。过期后前端要能感知到 401 状态码,自动跳转登录页。我在前端的 axios 拦截器里做了统一处理,响应状态是 401 时清除本地登录状态并跳转/login。而后端拦截器也做了一层白名单,/api/user/register、/api/user/login、/api/product/list这类不需要登录就能访问的接口要放行,否则用户连登录页面都进不去。

密码加密这里我推荐再加一层盐值,单纯 MD5 面对彩虹表攻击是无能为力的。实现思路是用户注册时生成一个随机盐值,把盐值和密码拼接后做 MD5,数据库里同时存储盐值和加密结果。登录校验时取出盐值重新计算比对,这样即使用户的密码散列泄露,攻击者也无法通过预计算表直接反推出明文密码,安全性好很多。

我在项目中还做了一个用户注册时的唯一性校验。注册接口里先按用户名查一次库,存在则直接返回错误。这里要注意并发场景下可能同时注册同一个用户名,单纯的先查再插会有风险,严谨的做法是在 user 表的 username 字段上建唯一索引,让数据库兜底。

3.2 商品模块与订单状态机

商品模块的核心是分类管理和商品列表。分类采用两级结构,一级分类下面挂二级分类,商品表里存的是二级分类的 ID。查询商品时通过分类 ID 关联查询,也可以直接按商品名模糊搜索。这个模块用到最多的就是 MyBatis 的<if>标签,比如按分类查、按关键字查、按价格区间查,三个条件任意组合,写一个动态 SQL 就够了,不用为每种组合单独写方法。

订单模块是整个项目里业务最重的地方。我实现了一个状态机思路,订单状态用整数表示:0 待付款,1 待发货,2 待收货,3 已完成,4 已取消。每个状态的流转都有明确前提:待付款只能通过支付接口转到待发货或已取消,待发货只能通过发货接口转到待收货,待收货只能通过确认收货转到已完成。在前端页面,按钮的显示逻辑跟状态一一对应,后端接口在状态变更前也做校验,防止跳过中间状态,比如直接对已取消的订单执行确认收货,这种操作必须被拒绝。

下单流程的代码顺序是先查询商品信息并计算总价,然后扣库存,再生成订单主记录,接着批量插入订单明细,最后清空该用户的购物车中已下单的商品。这一串操作全部放在一个事务方法里,任何一步出错都会整体回滚。我在调试时验证过,故意把商品库存扣成负数抛异常,订单数据不会残留,购物车也不会被清掉。

还有一点关于库存:扣减库存的 SQL 建议在 WHERE 条件中加入库存大于当前购买数量的判断,例如UPDATE product SET stock = stock - #{count} WHERE id = #{productId} AND stock >= #{count},返回受影响行数判断是否扣减成功。这样能防止并发超卖,比先查库存再判断要可靠得多。

4. 前端 Vue3 工程化实践

4.1 项目目录结构与状态管理方案

前端项目的目录结构直接影响代码的可维护性。这套系统的前端分了src/api、src/router、src/stores、src/views、src/components、src/utils这几个核心目录。api目录下按业务模块拆分文件,比如product.js、order.js、cart.js,每个文件导出一个函数对应一个后端接口。这样页面组件里只调用函数,不直接写 axios 请求,接口地址集中管理,后改起来方便。

状态管理这块我用了 Pinia。购物车数量、用户登录信息、订单提交后的支付参数,这些都是跨页面共享的数据,如果靠组件 props 一层层传,代码会很混乱。Pinia 的 store 像是一个全局的响应式仓库,任何组件里都可以读写。比如用户登录后把用户信息存到userStore,个人中心页面和订单结算页面都能直接取,不需要重新请求接口。

路由设计上,项目区分了前端路由和权限控制。用户端路由有首页、商品分类、购物车、订单列表、订单详情、个人中心,管理端路由有商品管理、订单管理、分类管理、用户管理、轮播图管理。前端在路由守卫里判断登录状态和角色角色权限,后端接口同样有拦截器校验权限,两边都做一层,不是多余的,因为前端控制只是体验优化,真正的安全边界必须放在后端。

4.2 关键页面逻辑与状态同步

购物车页面是前后端配合的典型场景。购物车的加购、数量修改、勾选商品、删除商品,每一步操作都会调用后端接口。前端拿到返回的新购物车数据后,更新 Pinia 里保存的购物车商品总数。这里要注意的就是状态同步,如果只有本地加减而不同步后端,刷新页面数据就会回退,用户会以为系统出 bug 了。我处理的方式是每次增删改之后都重新拉取购物车列表,虽然多打一个请求,但数据绝对准确。

订单结算页面稍微复杂一点。用户从购物车勾选商品后点击去结算,前端把选中的购物车 ID 列表和收货地址 ID 传给后端,后端返回商品的实时价格和库存。这里为什么必须查实时价格?因为购物车里的价格是加购时的快照,这段时间内商品可能调价了,下单必须用最新价格,否则商家会亏。前端拿到实时价格后再显示应付金额,同时要求用户选择地址,确认后才能提交订单。

提交订单成功后,前端跳到收银台风格的支付页面,展示订单编号、金额和支付方式选择。这个项目里支付是模拟实现,用户点击支付按钮,前端调支付接口,后端延迟一秒后把订单状态从待付款改成待发货。整个交互流程和真实电商平台差别不大,只是省去了接入第三方支付网关的步骤。

5. 部署配置与高频故障排查

5.1 本地环境搭建三个容易出错的配置文件

一个前后端分离项目跑通本地环境,基本上是四个步骤:导入 SQL、改后端配置、启动 SpringBoot、启动 Vue 开发服务器。听起来简单,实际操作中的报错往往集中在配置细节上。我把自己常用的配置框架整理出来,照着改基本不会跑偏。

后端application.yml里最核心的是数据源和 MyBatis 配置。数据源要确认 MySQL 的地址、库名、账号、密码全都匹配。MyBatis 配置要注意mapper-locations指向classpath:mapper/*.xml,如果路径写错,启动时会报找不到 Statement 的错误。另外要设置map-underscore-to-camel-case: true,否则数据库的下划线字段名映射不到 Java 的驼峰属性,查询结果全是 null。

前端的.env.development文件里要配置 Vite 的代理转发。开发模式下前端跑在 5173 端口,后端跑在 8080 端口,两者不同源,浏览器会拦截后端返回的跨域响应。我在 Vite 配置里加了代理,把所有/api开头的请求转发到http://localhost:8080,这样前端代码里写的请求地址都是相对路径,不需要关心跨域。生产环境部署时,前端构建出的静态文件由 Nginx 托管,再把/api反代到后端服务即可。

还有一个很多人忽略的文件是 MySQL 的初始化 SQL。如果数据库字符集没设置成 utf8mb4,商品描述里有 emoji 或特殊符号时会报字符集相关的错误。导入 SQL 之前最好确认数据库的默认字符集,或者在初始化 SQL 开头统一指定DEFAULT CHARSET=utf8mb4。

5.2 实战中遇到的高频报错与解决记录

我把这套系统从零开发到联调完成,遇到过的报错类型基本是五类,整理成一份速查表,几乎每个做类似项目的人都会碰到。

第一类是端口冲突。后端端口 8080 被其他程序占用,启动时日志直接报Port already in use。排查方法是在命令行里查端口占用,找到对应的进程号之后结束掉,或者直接改server.port避开。如果你本机同时启动过多个 SpringBoot 项目,这种问题会反复出现。

第二类是数据库连接失败。报错信息常见的是Access denied for user或者Communications link failure。前者检查账号密码和授权,后者基本就是 MySQL 服务没启动,或者spring.datasource.url里的端口、主机地址写错了。还有一个隐蔽的点:MySQL 8 和之前的版本驱动类名不一样,8.x 要写com.mysql.cj.jdbc.Driver,而且 URL 里加上serverTimezone=Asia/Shanghai,否则时间字段会有 8 小时的偏移。

第三类是 MyBatis 的绑定异常,报Invalid bound statement (not found)。出现这个错先看 XML 文件的 namespace 是否和 Mapper 接口全限定名一致,再看 mapper-locations 路径是否扫到了 XML 文件,最后检查 XML 里的 id 是否和接口方法名一致。三处有一处不匹配就会报这个错。

第四类是前端跨域问题。浏览器 Console 报CORS policy错误,排查时先确认请求是否走了 Vite 代理,再看后端拦截器是否放行了 OPTIONS 预检请求。写一个Filter或者实现WebMvcConfigurer的addCorsMappings,统一处理跨域,避免每个 Controller 都加@CrossOrigin。

第五类是 Vue3 响应式丢失。最常见的是从接口拿到的数据直接赋值给 reactive 数组后,页面不更新。原因是用let list = reactive([])定义后,用list = res.data整体替换了引用,响应式丢失了。正确做法是list.splice(0, list.length, ...res.data)或者改用ref([]),赋值时给list.value整体赋值,就不会有这种问题。

6. 源码结构笔记与后续扩展方向

这套系统的源码整体上可以分成两个工程,后端server目录和前端web目录。后端按三层架构组织,controller 只接收参数和返回结果,service 里写业务逻辑,mapper 负责数据库操作。DTO 和 VO 要跟实体类分开,前端需要什么字段就封装成对应的 VO 返回,不要把实体类直接暴露给前端,这是保护后端数据结构的关键习惯。

前端工程按照页面维度组织,每个页面的代码控制在几百行以内。如果某个页面的逻辑超了,就把公共部分抽到 hooks 里。比如订单列表和订单详情都要处理状态文字映射,就可以把订单状态和描述、标签颜色封装到一个独立的模块里,两个页面共用。

后续想扩展的话,优先级我先排好。第一是增加 Redis,做验证码存储和购物车缓存,Redis 处理这类高频读写比数据库快一个量级。第二是接入真实的第三方支付,替换掉模拟支付逻辑,同时完善支付回调处理和订单超时关单机制。第三是增加商品规格参数,支持多规格选择和对应库存,这需要重新设计商品规格表。第四是做一个简单的推荐系统,基于用户浏览记录和购买历史做协同过滤,这属于锦上添花,排在最后就好。

我实际做完这套系统后最大的体会是,电商项目的难点不在单点技术上,而在业务链路的衔接和状态管理上。一个下单操作可能要改十几张表、涉及多个服务的协作,任何一环没想清楚,联调的时候都要返工。所以给我的话给正在做类似项目的同学一个建议:开工之前先拿一天时间画好数据库表关系图,把订单状态流转图和购物车交互图理清楚,你觉得是浪费时间,实际能帮你省掉后面很大一部分调试成本。代码写错了可以改,数据模型设计错了,返工的代价才叫人头疼。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 15:28:19

基于Simulink的钒液流电池储能系统变换模型搭建与仿真避坑指南

简介&#xff1a;该资源面向电力电子、储能系统与新能源方向的Matlab/Simulink学习者及科研人员&#xff0c;提供一套可运行的储能系统仿真工程&#xff0c;重点解决钒液流电池本体建模与储能变换器控制策略的验证问题。包内共5个文件&#xff0c;以2张jpg与1张png波形截图、1个…

作者头像 李华
网站建设 2026/10/13 15:28:08

SolidWorks+Simscape Multibody:四旋翼从CAD建模到物理仿真控制全链路实战

做四旋翼仿真的朋友都知道&#xff0c;最烦人的不是控制器调不出来&#xff0c;而是模型离真实物理太远。几年前我刚开始做这个课题的时候&#xff0c;MATLAB里到处是简化的点质量模型&#xff0c;电机用一个一阶惯性环节代替&#xff0c;机架恨不得用三根线段画完。仿真跑得倒…

作者头像 李华
网站建设 2026/10/13 15:27:24

labelImg安装与标注实战:目标检测数据集避坑指南

如果你准备开始做目标检测&#xff0c;那么“给图片画框”这个活儿迟早会找上你。我自己的经历是&#xff1a;模型代码随便跑&#xff0c;数据集却没人管&#xff0c;最后只能自己一张一张画标注框&#xff0c;这时候 labelImg 就成了绕不开的第一个工具。它界面简洁、操作门槛…

作者头像 李华
网站建设 2026/10/13 15:25:42

开断潮流与N-1校核:支路潮流修正的快速计算方法

做电力系统分析的人&#xff0c;大概都过过这样的日子&#xff1a;把一套电网数据整理好&#xff0c;让潮流计算程序跑完基准工况&#xff0c;正要出报告&#xff0c;调度那边来一句"看看西郊那条线检修退出后有没有问题"&#xff0c;于是又得改拓扑、换数据、重算一…

作者头像 李华
网站建设 2026/10/13 15:24:29

Visual C++光标管理:高DPI适配与GDI资源安全实践

1. 项目概述&#xff1a;光标控制不是“小功能”&#xff0c;而是人机交互的底层触点在Visual C开发中&#xff0c;“设置光标Cursor”这件事&#xff0c;表面看只是调用一个SetCursor()函数、加载一个.cur文件&#xff0c;但实际深入下去&#xff0c;它牵扯的是Windows消息循环…

作者头像 李华