news 2026/10/21 22:27:56

SpringBoot+Vue手机商城系统源码详解:前后端分离与订单库存设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue手机商城系统源码详解:前后端分离与订单库存设计

第一次拿到这套“欢迪迈手机商城”源码的时候,我其实没抱太大期望。毕竟SpringBoot+Vue的商城类项目,市面上多到泛滥,很多就是把开源后台改个Logo就拿出来卖。但翻了半小时之后,我承认它的完成度确实比一般毕设要高:用户端、管理端前后端分离,购物车、订单、库存、分类这些电商核心链路都有,MyBatis的SQL写得也规整,不是靠ORM硬凑出来的烂活。这篇文章就把这套系统的技术选型、表结构设计、后端实现、前端联调,还有我实际运行过程中踩到的坑,从头到尾拆一遍。不管你是拿它做毕业设计参考,还是想找一个可以二次开发的电商脚手架,或者是刚接触前后端分离项目、想搞懂完整业务闭环,这篇应该都能给你省不少时间。

这套系统适合谁?第一类是高校计算机相关专业做毕设或课程设计的学生,需要一套结构清晰、能演示、答辩时讲得出技术点的完整项目;第二类是刚入行一两年、想研究前后端分离工程结构的开发者;第三类是接小项目或做产品Demo演示的人,需要快速搭出一个像模像样的商城业务模型。下面我直接按自己的阅读和调试顺序来讲,尽量把关键细节都摊开,代码和配置能直接抄的就直接抄。

1. 这个商城系统到底做了什么

1.1 项目定位:一个能跑通全流程的B2C手机商城

“欢迪迈手机商城”并不是一个只有商品展示的静态页面,而是一个典型的B2C电商系统。用户端包含注册登录、分类筛选、商品详情、购物车、订单提交和模拟支付、订单列表、个人中心;管理端包含商品管理、分类管理、库存管理、订单管理、用户管理以及简单的数据概览。整个业务链路是完整的:用户从浏览商品到下单支付,管理员在后端处理发货和商品上下架,所有数据最终落到MySQL里。

这种完整闭环恰恰是多数毕设项目最欠缺的部分。很多初学者写商城只写了一半,商品列表能用,但一点“加入购物车”就报错;或者订单表建了,却没有和商品SKU、库存表建立起关联。这套源码至少把主链路都跑通了,所以拿来做改造和参考,比从零开始要省力得多。

1.2 核心功能模块拆解

从源码的Controller层可以清晰地看到模块划分:

  • 用户模块:注册、登录、个人信息、收货地址维护。
  • 商品模块:商品列表、商品详情、分类树、关键字搜索、价格区间筛选。
  • 购物车模块:加入购物车、修改数量、删除、选中状态管理。
  • 订单模块:创建订单、订单列表、订单详情、取消订单、模拟支付、确认收货。
  • 管理模块:商品CRUD、分类CRUD、订单状态修改、用户列表查看、统计报表。

模块之间不是孤立的,比如商品模块会关联分类和SKU,订单模块会关联购物车、地址快照和库存。这里最值得关注的是“订单与库存”的联动,这是商城系统最核心的难点之一,后面我会单独用一节展开。

1.3 为什么“手机商城”是个合适的业务载体

手机类目有明确的属性维度,比如品牌、价格区间、内存版本、颜色等,适合做分类筛选和规格设计。相比图书和服装,电子产品的SKU复杂度刚刚好:不会简单到没有逻辑,也不会复杂到难以落地。如果你想练手“商品规格+多级分类+库存扣减”,手机商城几乎是完美的业务场景。

2. 技术栈选型的幕后逻辑

2.1 后端框架:为什么是SpringBoot

这个项目选择SpringBoot可以说既是主流,也是最优解。SpringBoot的自动配置和起步依赖极大降低了搭建成本,一个spring-boot-starter-web就能把内置Tomcat、Spring MVC、Jackson这些全部带进来。更重要的是,商城系统涉及事务控制、权限校验、文件上传、邮件通知等场景,Spring生态的IOC和声明式事务管理能非常优雅地处理这些问题。

相比SSH那套老古董,SpringBoot不需要写一大堆XML配置;相比一些轻量级框架,它又能通过起步依赖随时扩展Redis、MQ等组件,适合作为学习电商业务的载体。

2.2 持久层:MyBatis比JPA更适合商城业务

选MyBatis而不是JPA,原因其实很实在。商城里有大量多表联查和动态条件查询,比如按分类、品牌、价格区间筛选商品,还要做模糊搜索。这类查询如果把逻辑放在ORM的Entity关联关系里,生成出来的SQL经常又慢又难排查。MyBatis允许你直接控制SQL,把查询写成动态SQL,性能和可读性都在自己手里。

另一个原因是MyBatis的XML Mapper天然适合团队协作。SQL语句可以单独维护,出了问题直接复制SQL到客户端跑一遍就能定位,不像JPA那样需要去猜框架生成的语句。不过这里有个关键点:Mapper接口和XML文件必须同名且在同一个包路径下。源码里这一点做得很规范,如果后续你自己新建Mapper,一定别忽略这个约定。

2.3 前端框架:Vue加组件库的经典组合

用户端和管理端都基于Vue构建,配合Element UI组件库。为什么不用React?对这类以表单、表格、弹窗为主的管理后台来说,Vue的模板语法更直观,Element UI提供了现成的el-table、el-form、el-pagination,半天就能把页面框架搭好。而且Vue的响应式数据模型,在处理购物车数量增减、表单联动这类需求时非常顺手。

需要注意,如果源码用的Vue 2,配套的Element UI版本不要太新;如果是Vue 3,就得换成Element Plus。这个版本匹配问题,几乎是新手第一次跑前端项目时必踩的坑。

2.4 MySQL版本和连接参数

建议使用MySQL 5.7或8.0。数据库连接串里常见的坑有两个。一个是时区问题,高版本的MySQL驱动要求写清楚serverTimezone=Asia/Shanghai,否则启动后跑查询会报时区错误。另一个是MySQL 8.0默认的认证方式是caching_sha2_password,如果连接池版本太老,还需要在JDBC URL后面加上allowPublicKeyRetrieval=true。源码里如果用的是root账号,最好确认一下你的MySQL root密码策略,避免密码加密方式不兼容。

3. 数据库设计:商城业务的地基

3.1 核心表清单

我整理了一张核心表的用途说明,参考源码里的SQL脚本,整体结构如下:

表名用途关键字段示例
sys_user系统用户id, username, password, nickname, role, status
user_address用户收货地址id, user_id, receiver, phone, province, city, detail
category商品分类id, name, parent_id, sort_order
product商品主表id, title, subtitle, main_image, category_id, status, create_time
product_sku商品规格库存id, product_id, color, version, price, stock, sales
cart购物车id, user_id, sku_id, quantity, checked, create_time
ord_order订单主表id, order_no, user_id, total_amount, status, address_snapshot
ord_order_item订单明细表id, order_id, sku_id, product_name, sku_info, price, quantity

这张表结构基本符合三范式,但又没有为了贴近理论而过度拆表。像订单主表里放address_snapshot快照字段,看似冗余,实际是电商设计里的常规操作,因为收货地址后续可能被用户修改,订单必须保留下单那一刻的快照。

3.2 商品与SKU分离的设计

这是整个库表设计里最值得学习的地方。手机商品会区分“颜色”“内存版本”,如果直接在这些属性上建列,扩展性极差。正确做法是拆分出product_sku表,把规格组合和对应价格、库存、销量放进SKU记录。商品表只保存标题、封面、详情、分类等公共信息。

前端展示商品列表时,可以通过联查SKU获取最低价和总库存,展示成“128GB版 3299元起”这种效果;点击具体SKU后,再查对应库存和价格。这套设计虽然基础,但它非常完整地演示了电商产品的核心概念,值得好好理解。

3.3 订单表设计里的快照思想

订单主表和订单明细表是一对多关系。主表记录订单号、用户、总金额、状态、地址快照;明细表记录每一件商品的名称、规格、单价、数量。为什么明细表里要存商品名称和单价快照?因为商品表和价格表随时可能变化,如果只存product_id,用户翻历史订单时看到的可能是改价后的数据,就失去了凭证意义。

这里有个细节,订单号不是简单的自增ID,而是用order_no字段保存类似“前缀+时间戳+随机数”的字符串,避免暴露业务量,也方便后续对接物流和支付。我个人很喜欢这种设计,虽然只是多几行代码,但对系统的专业度提升很明显。

3.4 购物车表的唯一约束设计

购物车表用了user_id + sku_id作为唯一逻辑组合。加入购物车时,先尝试更新数量;如果更新受影响行数为0,再执行插入。这样既避免了同一用户反复加入相同SKU造成多条脏数据,也让前端的“加入购物车”按钮逻辑变得很简洁。很多人会把“判断购物车里有没有这个商品”的逻辑写在Java代码里,先select再insert,这样不仅多一次查询,在高并发场景下还容易出现重复数据。用数据库唯一约束加ON DUPLICATE KEY UPDATE或“先update后insert”的做法,才是更稳的。

4. 后端核心实现细节精讲

4.1 统一返回体与全局异常处理

先说一个很多初学者不在意、但工程上非常影响体验的东西:统一返回体。源码里定义了一个Result类,包含code、message、data三个字段。正常请求返回200,业务异常返回500,未登录返回401。这个设计虽然简单,却让前端能够用一套统一的逻辑处理所有接口响应。

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.setCode(200); r.setMessage("success"); r.setData(data); return r; } public static <T> Result<T> error(String message) { Result<T> r = new Result<>(); r.setCode(500); r.setMessage(message); return r; } }

配合@RestControllerAdvice做全局异常处理,@ControllerAdvice拦截业务异常、参数校验异常、数据库异常,转成Result返回。这样Controller层就可以非常干净,只写业务流转,不必每个方法都try-catch。我看到这套源码里对“用户未登录”“库存不足”“参数非法”都做了分别处理,这点值得点赞。

4.2 JWT登录鉴权与角色权限控制

登录态用的是JWT,而不是传统Session。流程是:用户提交用户名密码,后端校验通过后生成一个带用户ID和角色的token返回给前端;前端存在localStorage里,每次请求在Authorization请求头中携带;后端用一个拦截器统一解析token,并把当前用户信息放进ThreadLocal,供本次请求后续使用。

核心拦截器里要做三件事:

  • 放行白名单接口:登录、注册、商品列表、商品详情、分类树等公开接口。
  • 校验token是否存在且有效,无效直接返回401。
  • 对/admin/**这类管理接口,再校验用户角色是否为管理员。

JWT的好处是服务端无状态,适合前后端分离部署。但要提醒一句,token的过期时间不要设置得太长,一般2到4小时比较合理;同时密钥不要硬编码在代码里,应该放进配置文件中。源码里如果直接写在Java类里,建议你改成配置项。

4.3 MyBatis动态SQL与多条件商品筛选

商品列表页往往有多个筛选条件:分类ID、品牌、价格区间、关键词、上下架状态。如果每个条件都写一个SQL方法,那方法数量会爆炸。源码里利用MyBatis的<if>标签,把可选条件拼进同一条SQL,非常干净。

下面这段是商品筛选Mapper的典型写法:

<select id="selectProductList" resultType="com.hdmall.pojo.Product"> select * from product where status = 1 <if test="categoryId != null"> and category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> and (title like concat('%', #{keyword}, '%') or subtitle like concat('%', #{keyword}, '%')) </if> <if test="minPrice != null"> and price &gt;= #{minPrice} </if> <if test="maxPrice != null"> and price &lt;= #{maxPrice} </if> order by create_time desc </select>

注意两个细节:一是like查询要使用concat函数拼接百分号,而不是直接在参数里写%,这样可以避免SQL注入风险;二是>=在XML里要写成&gt;=,这是XML语法转义,新手经常忘记,然后怎么调都报错。

4.4 订单创建与库存扣减的事务控制

订单创建是整个系统最需要谨慎的部分。用户点击“提交订单”后,后端要做三件事:插入订单主表、批量插入订单明细、扣减对应SKU的库存。这三步必须在一个数据库事务里完成,否则一旦中途报错,就会出现“订单生成了但库存没扣”或“库存扣了但订单没创建”的严重问题。

源码里使用@Transactional注解包住整个方法,同时扣减库存的SQL写得非常关键:

update product_sku set stock = stock - #{quantity} where id = #{skuId} and stock >= #{quantity}

这条SQL的后半句stock >= #{quantity}是防超卖的核心。当库存不足时,受影响行数为0,代码判断受影响行数如果为0就直接抛出业务异常,触发事务回滚。这种“条件更新”的方式比先select查库存再update要安全得多,能够避免两个人同时下最后一台手机时都把库存当作充足的并发问题。虽然对毕设项目来说并发压力不大,但这个设计思想是需要掌握的。

4.5 模拟支付与订单状态机

系统里的支付是模拟的,不会真正对接支付接口。订单状态一般包含:待支付、已支付/待发货、已发货、已完成、已取消。源码里的状态机流转顺序很清晰:用户创建订单后是待支付,点击“模拟支付”后变成待发货,管理员发货后变已发货,用户确认收货后变已完成;在待支付状态下用户也可以取消订单。

这里默认支付前不锁定库存,而是创建订单时就把库存扣减了。这样用户如果不支付,订单超时后需要“取消订单+释放库存”的补偿逻辑。源码里如果在取消订单时也做了库存回补,那说明业务闭环已经很完整了;如果没做,这块可以作为你的二次开发练习题。

5. 前端Vue实现思路与接口对接

5.1 前端项目结构与路由设计

源码的前端目录结构很适合学习,一眼能看出业务模块:

src/ api/ # 接口请求封装 router/ # 路由配置 store/ # 状态管理(Vuex/Pinia) views/ home/ # 首页 goods/ # 商品列表和详情 cart/ # 购物车 order/ # 订单确认与列表 user/ # 用户中心和地址 admin/ # 管理后台 login/ # 登录注册 utils/ # 工具函数

路由设计上,用户端和管理端做了分离。管理端路由统一挂在AdminLayout下,并在路由meta里标记requiresAdmin,进入时做权限判断。这种按角色拆路由的做法,比把所有页面都堆在同一个布局里要清晰很多,也方便后续扩展更多角色。

5.2 Axios封装:请求拦截和统一错误处理

前端所有接口请求不可能每次手动写fetch或axios,源码里对Axios做了一层封装。核心逻辑如下:

import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截:自动携带token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) // 响应拦截:统一处理返回码 service.interceptors.response.use( response => { const res = response.data if (res.code === 401) { localStorage.removeItem('token') router.push('/login') return Promise.reject(new Error('未登录或登录已过期')) } if (res.code !== 200) { Message.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error => { Message.error('网络异常,请稍后重试') return Promise.reject(error) } ) export default service

这段封装解决了两个大问题:一是所有请求自动带token,不用每个接口单独写;二是后端返回的业务错误由统一拦截器弹出提示,页面里不用重复写catch。如果后续对接真实后端,只需要调整baseURL。

5.3 购物车与结算流程的前端配合

购物车页面的难点不在展示,而在“选中状态”的跨页面传递。用户可能在购物车勾选了几件商品,然后点击结算进入确认订单页。这里的选中状态不能只存在购物车页面的局部变量里,需要通过Vuex/Pinia或路由参数传递。

源码里的做法通常是在状态管理中维护一个selectedSkuIds数组,结算页根据这个数组去购物车列表里过滤出被选中的条目,再请求“创建订单”接口。创建订单时,前端提交的是SKU ID列表、数量以及收货地址ID,后端再根据这些信息计算金额并创建订单。有一点要提醒:前端计算出的金额只用于展示,后端必须重新计算一遍总价,否则用户可以篡改请求参数把价格改低。如果源码里没有做强校验,建议你加上。

5.4 管理后台的表格分页与表单交互

管理后台是Element UI的高频使用场景。商品管理页通常包含搜索表单、工具栏、表格、分页组件。分页组件的currentPage和pageSize要和后端接口参数一一对应,搜索表单提交时要重置页码到第一页。这部分逻辑看起来简单,但很多人踩过“搜索后页码超出总页数,表格显示空白”的坑。

表单交互上,新增和编辑通常共用同一个弹窗组件,通过editId是否存在来判断是新增还是修改。提交前用rules做校验,校验规则要和后端参数校验保持一致。比如手机价格不能为空、库存必须是正整数,这些规则如果前端不校验,后端就有义务拦截。

6. 环境搭建、启动部署与踩坑实录

6.1 运行环境准备

在开始之前,确认你的环境满足下面条件:

  • JDK 1.8或17(SpringBoot 2.x用JDK8最稳,SpringBoot 3.x必须JDK17)
  • Maven 3.6+
  • MySQL 5.7或8.0
  • Node.js 16及以上
  • IDE:后端用IDEA,前端用VSCode或WebStorm

启动前把MySQL的SQL脚本执行一遍,注意字符集选择utf8mb4。如果你用的是8.0,数据库连接串里记得带上useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true。

6.2 从零启动的完整步骤

以下是我实际跑通这套系统的操作顺序:

  1. 新建数据库,导入源码中的hb_mall.sql脚本,确认表已全部创建。
  2. 打开后端项目,修改application.yml中的数据库账号密码、JWT密钥和文件上传绝对路径。
  3. 在IDEA中运行MallApplication主类,等待启动完毕。
  4. 若有Swagger或接口文档,浏览/swagger-ui/index.html验证接口。
  5. 打开前端项目,执行npm install安装依赖,注意如果依赖安装失败,优先检查Node版本。
  6. 修改前端vue.config.js中的代理配置,把/api代理到http://localhost:8080。
  7. 执行npm run serve启动开发服务器,浏览器访问用户端首页。
  8. 使用管理员账号登录管理后台,普通用户注册测试完整下单流程。

初次启动如果发现前端页面有样式错乱,通常是因为Element UI的样式没有全局引入;如果接口能通但页面没数据,先打开浏览器开发者工具的Network面板,看请求是否真正发出、状态码是什么。

6.3 实际踩过的三个典型问题

问题一:跨域请求被拦截。页面访问的是localhost:8081,接口却监听localhost:8080,浏览器直接报跨域。我在调试时先把后端CORS打开,再加了前端代理,属于双重保险。如果是开发环境,我更推荐只做前端代理;生产环境用Nginx反代,让前后端看似同源,避免后端CORS配置暴露接口。

问题二:Mapper接口和XML文件绑定失败。这个问题很经典,症状是启动时报Invalid bound statement (not found)。检查后发现XML文件放在src/main/java的Mapper包下面,但Maven默认不会把src/main/java下的非Java文件编译到target/classes。解决办法是在pom.xml里增加资源配置,把**/*.xml也包含进去;或者把XML统一放在src/main/resources/mapper目录下,并在application.yml里配置mybatis.mapper-locations。

问题三:登录后刷新页面token丢失,接口全部401。原因是没有把token持久化到本地,只存在Vuex内存里刷新就没了。解决方式是把token写入localStorage,并在main.js或路由守卫启动时重新读取登录状态。如果接口返回401,还要清除本地token并跳回登录页,避免无限报错。

6.4 常见问题速查表

问题现象可能原因解决办法
启动时报时区错误JDBC URL缺少serverTimezone连接串加serverTimezone=Asia/Shanghai
前端请求跨域代理未配置或后端CORS未开配置vue.config.js代理;或后端增加CorsFilter
Invalid bound statementMapper XML未被编译pom.xml配置resources包含**/*.xml
登录后刷新失效token未持久化token存localStorage,路由守卫读取
库存扣成负数扣库存SQL缺少库存判断使用stock >= #{quantity}条件更新
图片上传无法预览文件上传路径问题配置静态资源映射到上传目录

6.5 给二次开发者的三点建议

这套系统我实际运行了几天,最大的体会是商城类项目真正的难度不是某一个技术点,而是把商品、购物车、订单、库存这些模块串成一条能自洽的业务链路。如果你正在拿它做毕设或练手,我建议不要只是启动起来截几张图,而是动手改一个真实功能。

几个适合练手的方向:

  • 增加优惠券模块,涉及用户领券、下单抵扣、券状态变更,能练到异步状态流转。
  • 把模拟支付替换成真实支付回调,理解第三方通知和本地订单状态同步。
  • 给商品列表加Redis缓存,并考虑缓存击穿问题,这是一个很好的面试加分点。

我在实际使用中还有一个感受:源码的SQL注释和表字段命名都比较规范,哪怕不启动项目,单看建表脚本和Mapper文件,就能学到很多电商业务建模的思路。如果你准备答辩或面试,把“为什么字段要加快照”“为什么库存扣减要放在SQL里做条件判断”讲清楚,比背一百道八股文都更有说服力。最后拿它做扩展时,别忘了给自己留一个环境变量,把数据库密码和JWT密钥从代码里挪到配置文件里,这种小细节在真正部署时能帮你省掉很多不必要的麻烦。

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

知识管理插件化实战:从采集到输出打通效率链路

做知识管理这些年&#xff0c;我最大的感受是&#xff1a;工具不是不够多&#xff0c;而是插件生态让人眼花缭乱。早期我一度迷恋各种“全家桶”式知识软件&#xff0c;装上几十个插件&#xff0c;光配置就折腾了一周&#xff0c;最后发现真正的产出没增加多少&#xff0c;反而…

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

插件化个人知识工作流:采集、整理、检索与输出系统实践

我这两年一直在维护一套插件集合&#xff0c;项目代号就叫 knowledge-work-plugins。它不是什么大平台&#xff0c;就是一组能够互相协作的小插件&#xff0c;专门处理知识工作里最烦人的几件事&#xff1a;把散落的信息收进来&#xff0c;把杂乱的内容整理好&#xff0c;在需要…

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

微服务增强网关架构设计与插件化实践

1. 为什么标准网关总是不够用做后端的人对网关这个东西的感情很复杂。一开始觉得它就是个反向代理&#xff0c;把请求转发到后端服务就完事了&#xff0c;Nginx 配一配&#xff0c;或者 Spring Cloud Gateway 写几个 Filter&#xff0c;好像什么都能搞定。但真正在生产环境跑起…

作者头像 李华
网站建设 2026/10/21 22:24:14

lingbot-map:轻量级智能体语义建图与定位工具链实战拆解

lingbot-map 这个名字我相信很多人第一次看到都会有点摸不着头脑&#xff1a;ling 是语言的意思&#xff0c;bot 看起来和机器人或智能体有关&#xff0c;map 又指向地图和映射。我最初拿到这个项目时&#xff0c;第一反应是"这到底是一套建图工具&#xff0c;还是一个把语…

作者头像 李华
网站建设 2026/10/21 22:22:16

YOLOv8自定义数据增强:继承BaseTransform接入MixUp与Copy-Paste实战

如果你想让 YOLOv8 的训练 pipeline 加入自定义增强方法&#xff0c;最忌一上来就改训练主循环。我在一次做小样本目标检测的内部实验时&#xff0c;也走过这个弯路&#xff1a;想加一个自定义 MixUp&#xff0c;顺手把训练脚本里的 dataloader 改了又改&#xff0c;结果不是增…

作者头像 李华
网站建设 2026/10/21 22:22:11

飞机表面缺陷识别实战:从YOLO数据集构建到工业质检部署全解析

做工业AI质检一年多&#xff0c;我越来越觉得飞机表面缺陷识别是计算机视觉里最考验工程能力的落地场景之一。这几天在整理第10618期飞机表面裂痕识别yolo数据集时&#xff0c;我一边跑着训练&#xff0c;一边在想&#xff1a;为什么这类工业零部件自动化质检项目在论文里总显得…

作者头像 李华