☰
SpringBoot+Vue+MySQL在线商城系统源码解析与实战部署指南
2026/9/26 18:17:43 网站建设 项目流程

1. 项目概览与架构设计思路

这套在线商城系统,我用了一段时间,也带着几个同事一起调过、跑通、部署过,整体给我的感觉是:结构标准、业务覆盖完整、入手门槛低,非常适合做课程设计、毕业设计,或者是刚接触前后端分离项目的人用来理解商用系统的基本盘。

先说这个项目到底是什么。它的全称是"ONLY在线商城系统信息管理系统源码",技术栈非常明确:SpringBoot 负责后端接口和业务逻辑,Vue 负责前端页面和交互渲染,MySQL 负责数据存储。三个部分各自独立又能互相协作,典型的前后端分离开发模式。而且它自称"可直接运行",这意味着项目里已经内置了初始化数据、前后端联调配置,甚至可能包含了数据库脚本,省去了从零搭建的基础工作量。

1.1 这套系统是做什么的

在线商城系统,说白了就是把线下商场的商品浏览、下单购买、订单管理、后台维护这些环节搬到线上。常见的用户端功能比如商品列表、商品详情、加入购物车、提交订单、模拟支付、个人中心、收货地址管理;管理端功能包括商品发布、商品上下架、库存管理、订单处理、用户管理、分类管理、数据概览等。这套系统基本把这些模块都覆盖到了,虽然不像淘宝那样有完整的分布式推荐系统、秒杀系统、搜索引擎,但作为一套中小型商用平台或教学项目,功能性是足够完整的。

从我实际跑到的情况看,这套系统比较适合这几类人:

  • 在校学生,尤其是Java方向或软件工程方向,需要课程设计、毕业设计源码的;
  • 初级程序员,想通过完整的全栈项目来提升SpringBoot、Vue、MySQL综合能力的;
  • 小团队创业验证,需要快速搭一个商城不过度定制,拿源码改改就能用的;
  • 想从前端转全栈,但找不到合适练手项目的,这个项目规模刚好。

1.2 技术组合选型的思考

为什么要用SpringBoot + Vue + MySQL,而不是SSH(Spring+Struts+Hibernate)或者更重的微服务架构?

这里是我自己的理解。SpringBoot现在已经是Java后端开发的事实标准之一,它最大的价值就是"约定优于配置",把原来Spring MVC、Spring配置各种XML文件的繁琐工作全部干掉,内置Tomcat,注解驱动开发,启动一个项目就是main方法的事情。而且它的自动配置机制让开发者只需要关心业务代码,不用关心组件的装配细节。

Vue在前端框架里火了很多年,它相比React上手曲线更平缓,模板语法直观,双向绑定特性在表单类业务场景(比如商城里的购物车数量修改、后台商品编辑)里特别好用。Vue 2.x或Vue 3.x都有成熟的Element UI组件库,表格、弹窗、表单验证这些后台系统高频操作都有现成组件。

MySQL就更不用说了,开源、免费、稳定、生态好,中小规模并发场景下性能完全够用,最重要的是会的人多,遇到问题百度就能找到答案。商城系统对事务的要求比较高(库存扣减、订单生成),MySQL的InnoDB引擎提供了很好的事务支持,这也是它作为这个项目存储层的核心原因。

这套组合选型的逻辑很简单:不求最新最炫,但求稳定好用、生态成熟、学习成本可控。这一点在项目里体现得很清楚,所有模块都是标准实现思路,不搞花活,这对阅读源码和理解业务非常有帮助。

1.3 安全合规说明

在展开技术细节之前,还是要先交代一句:我自己拿到源码之后,第一件事不是急着启动,而是先花点时间通读了一遍项目结构、数据库脚本和关键配置项。因为任何网络上流传的源码,即使再"可直接运行",也要自己做一次安全确认。这既是职业习惯,也是对团队负责。后面我会单独用一节讲清楚这种安全确认的流程。

2. 系统整体架构与功能模块拆解

2.1 前后端分离架构下的请求流转

这个项目的前后端分离,体现在目录结构上——前端工程和后端工程是两个独立的代码仓库。前端通过HTTP请求(本质上是JSON格式的RESTful API)调用后端接口,后端处理完业务逻辑后返回JSON数据,前端拿到数据后渲染页面。

整个请求流转过程大致是:

  1. 用户在浏览器里访问Vue项目页面;
  2. 用户在页面上的操作(比如点击"加入购物车")触发Vue组件中的方法;
  3. 方法通过axios或fetch发起HTTP请求到SpringBoot接口;
  4. SpringBoot的Controller层接收请求,并将参数传给Service层处理业务逻辑;
  5. Service层调用Mapper层(操作数据库的接口)读写MySQL数据;
  6. 执行结果逐层返回,最终以JSON形式回传给前端;
  7. Vue根据返回的数据更新页面DOM,用户看到反馈结果。

这个架构的好处在于前后端开发可以并行推进、互不干扰,前端只需要知道接口的入参和出参。同时,如果未来有App端、小程序端的接入需求,后端接口可以直接复用,不需要重写。

2.2 用户端功能模块详解

从实际使用的角度来说,用户端是绝大多数人打开这个系统后第一眼看到的界面。它包含的核心模块有:

  • 用户注册与登录:基于Token的认证方式,用户注册后密码使用加盐哈希存储(Spring Security或简单的JWT实现),登录成功后获得一个有效的访问令牌。这个模块是所有业务的前提,因为后续的下单、查看订单等操作都需要校验用户身份。

  • 商品浏览:支持商品按分类浏览、关键词搜索、分页展示。商品卡片上会显示缩略图、价格、销量、库存等信息。这里背后涉及商品表的联表查询、图片二进制存储或静态资源路径映射等。

  • 商品详情页:展示商品大图、详细介绍、价格变化、库存状态,并提供"加入购物车"和"立即购买"按钮。这个页面是转化率的核心,它的数据接口往往还需要附带评论或销量统计。

  • 购物车管理:用户可以将多个商品加入购物车,在购物车页面可以修改购买数量、删除商品、计算总价。购物车数据是存储在服务端的(关联用户ID),而不是仅仅存在浏览器localStorage里,这样换设备数据不丢。

  • 订单确认与提交:从购物车或商品详情页进入订单确认页面,填写收货地址、选择配送方式,确认金额后提交订单。这个步骤涉及多张表的联动:生成订单主表记录、生成订单子表记录(每个商品一条)、扣减库存。

  • 个人中心:查看个人资料、修改密码、查看订单列表、查看订单详情、确认收货。这个模块更多是状态查询和展示,逻辑相对简单。

2.3 管理端功能模块详解

管理端是给商城的运营人员或管理员使用的,包含的功能比用户端更偏向数据操作:

  • 分类管理:新增、修改、删除商品分类,分类通常支持两级结构(父分类和子分类)。

  • 商品管理:核心是商品表CRUD操作。单品模型里最重要的字段是名称、价格、库存、分类ID、封面图、描述、状态(上架/下架)。管理员的日常操作基本都集中在这里。

  • 订单管理:查看所有用户的订单,按状态筛选(待付款、待发货、已发货、已完成、已取消),进行发货操作,这对应着订单状态的流转。

  • 用户管理:查看注册用户列表、禁用/启用账号。商城类系统通常还会做用户等级或积分体系,这套系统功能相对基础,但基础CRUD是齐全的。

2.4 权限控制的设计逻辑

在商城系统里,用户和管理员必须被分开。普通用户只能访问和自己相关的业务功能,管理员才能访问后台管理模块。

从代码层面来看,这个项目在SpringBoot里通过拦截器或Spring Security过滤器来对请求进行鉴权。处理逻辑是这样的:前端在登录成功后会获得一个token,之后的每次请求在HTTP Header中带上这个token;后端拦截器对请求路径进行匹配,如果请求的是后台管理接口(比如 /admin/ 开头的接口),就会校验当前token对应的用户角色是否为管理员,如果不是则直接返回401或403错误码。

Vue前端那边也有配套的路由守卫机制。在路由配置里,会给后台管理相关的路由添加一个meta标记,全局前置守卫检查用户状态和角色,如果没有权限就直接跳转到登录页或404页面。这种"前端拦截 + 后端校验"的双重机制,才是完整的权限控制方案。光做前端拦截是防不住绕过访问的,后端才是最后一道安全闸门。

3. 后端SpringBoot核心模块实现思路

3.1 项目分层架构解析

打开SpringBoot后端工程,包结构一般是按照Controller → Service → Mapper的经典三层架构组织的。比如:

com.only.shop ├── controller # 请求入口,接收HTTP参数,返回JSON结果 ├── service # 业务逻辑层,处理核心业务规则 │ └── impl # Service接口的实现类 ├── mapper # 数据访问层,一般搭配MyBatis的Mapper接口 ├── entity # 实体类,对应数据库表结构的Java对象 ├── vo # 视图对象,用于向前端返回定制化的数据结构 ├── dto # 数据传输对象,用于接收复杂的请求参数 ├── config # 配置类,比如跨域配置、拦截器注册 ├── common # 通用工具类、统一返回结果封装、异常处理 └── ... # 其他辅助模块(如JWT工具类、常量定义)

这种分层的核心好处是职责单一、可维护性强。Controller层只做参数接收和结果包装,不写业务;Service层专注业务逻辑;Mapper层专注SQL语句。如果将来要换数据库,只需要修改Mapper层的SQL实现;如果要把业务逻辑抽成微服务,Service层也可以方便地分离出去。

3.2 统一返回结果封装

前后端分离的项目里,接口返回的数据格式必须统一。这个项目的common包下通常会有一个Result类,里面定义了三个核心字段:code(状态码)、message(提示信息)、data(业务数据)。

接口返回格式大致是:

{ "code": 200, "message": "操作成功", "data": { "token": "eyJhbGciOiJIUzI1NiJ9...", "userInfo": { "id": 1, "username": "onlyuser" } } }

这样做的好处非常直观:前端在axios响应拦截器里统一处理返回结果,根据code判断是进入正常流程还是弹出错误提示。如果每个Controller都自己拼返回格式,迟早会出现字段名不一致、状态码含义混乱的问题。

这也是我在阅读这个项目时比较看重的一点,统一返回结果是工程化规范的基础。在我自己的团队里,这个规范是写进代码评审清单里的硬指标。

3.3 热门接口的业务逻辑拆解

这里我挑几个核心接口,把它们的业务处理逻辑拆开讲讲。

登录接口:接收用户名和密码,先校验验证码(如果有的话),再根据用户名查出用户记录,比对密码哈希,比对成功后生成JWT Token返回给前端。这里有几个细节关卡:用户是否存在;账号是否被禁用;密码是否匹配;是否需要在Token里携带到期时间和角色信息。

添加购物车接口:接收商品ID和数量,先检查商品ID是否存在且处于上架状态,再查当前购物车是否已存在该商品记录——如果存在,就累加数量,否则新增一条记录。这里还要做库存校验,不能超过库存上限。

提交订单接口:这是系统中最复杂的单机事务。主要步骤是:

  1. 接收订单参数(商品ID列表、数量列表、收货地址ID);
  2. 校验商品状态和库存是否充足;
  3. 计算订单总金额(单价 × 数量,也可以支持优惠减免逻辑);
  4. 生成订单主记录,状态为"待付款";
  5. 生成订单明细记录,每个商品一条;
  6. 扣减库存(更新商品表的库存字段);
  7. 清空对应的购物车记录;
  8. 返回订单ID和应付金额给前端。

这六步操作必须在一个事务里完成,任何一个步骤失败都要全部回滚。SpringBoot里的实现通常是在Service方法上加上@Transactional注解,一旦方法内抛出运行时异常,Spring会自动进行事务回滚。这个接口是整个系统里最考验后端功力的一部分,如果对事务传播机制和异常处理理解不透彻,很容易出现库存扣了订单没生成这种脏数据。

给个简单的库存扣减SQL示例,如果你拿到的源码用的是MyBatis的XML方式,核心语句一般长这样:

<update id="deductStock"> UPDATE product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity} </update>

这里用stock >= quantity作为条件,可以在数据库层面防止扣成负数,属于一种乐观锁的简化版本。这一步在这个业务里是必须加的防护。

支付回调接口:因为是商城系统,支付环节通常走模拟支付,或者通过支付宝/微信沙箱环境联调。调用流程是前端调起支付页面,用户在沙箱环境输入支付密码,支付平台向系统后台发送异步通知回调接口,后端验证签名后修改订单状态为"已付款"。如果使用的是RabbitMQ或Redis的延迟定时任务,还可以实现自动取消超时未付款订单。

3.4 数据库连接与事务配置

SpringBoot的项目配置文件application.yml里,数据库相关配置是最核心的一块。一般会长这样:

spring: datasource: url: jdbc:mysql://localhost:3306/only_mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true

几个容易踩坑的配置点:

  • serverTimezone=Asia/Shanghai必须明确设置,不然MySQL连接时容易报时区错误;
  • map-underscore-to-camel-case这个配置可以把数据库的user_name自动映射到Java类的userName字段,不用写大量的resultMap映射规则;
  • mapper XML文件的路径要跟实际目录一致,否则会启动报错找不到Mapper绑定。

事务配置方面,SpringBoot默认就开启了事务管理,无需额外XML配置,只要在类上或方法上使用@Transactional即可。但需要注意一点——事务只在Spring容器管理的Bean里才能生效,如果你自己在Service里用new创建了一个业务对象去调用带事务注解的方法,那事务是不会生效的。这是我调试时发现不少新手会忽略的细节。

4. 前端Vue核心实现与页面交互设计

4.1 前端工程结构与目录规划

Vue工程是前端的骨架。通过Vite或者Vue CLI创建项目后,目录结构大致是:

src ├── api # 所有接口请求封装,每个模块一个文件 ├── assets # 静态资源,图片、样式文件 ├── components # 公共组件,比如商品卡片、订单状态标签 ├── router # 前端路由配置 ├── store # 全局状态管理(Vuex/Pinia) ├── views # 页面组件 │ ├── home # 首页相关 │ ├── product # 商品相关 │ ├── cart # 购物车页面 │ ├── order # 订单页面 │ ├── user # 个人中心 │ └── admin # 后台管理相关页面 ├── utils # 工具函数,比如localStorage封装、时间格式化 ├── App.vue # 根组件 ├── main.js # 入口文件 └── ...

前端项目非常讲究组件化复用。比如商品卡片,在首页、搜索结果页、猜你喜欢列表里都会出现,那就应该抽成一个通用组件,通过props传入商品数据。如果你拿到的源码里面是一个页面复制多套商品卡片代码,那说明作者前期图快没做好复用,你可以把它作为自己的一个优化练手点。

4.2 路由守卫与登录状态管理

商城系统涉及到用户信息展示和购物车数量展示,这些信息在很多页面都需要用到。如果每个页面都自己请求一次后端接口,又慢又重复,所以要用Vuex或Pinia把用户信息、购物车数量作为全局状态存储起来。

在Vuex里会有类似这样的结构:

// store/modules/user.js const state = { token: localStorage.getItem('token') || '', userInfo: JSON.parse(localStorage.getItem('userInfo')) || null } const mutations = { SET_TOKEN(state, token) { state.token = token localStorage.setItem('token', token) }, SET_USERINFO(state, info) { state.userInfo = info localStorage.setItem('userInfo', JSON.stringify(info)) }, CLEAR_USER(state) { state.token = '' state.userInfo = null localStorage.removeItem('token') localStorage.removeItem('userInfo') } }

路由守卫的作用就是判断用户是否登录。比如在router.beforeEach里,判断用户要访问的页面是否需要登录权限,如果需要且token为空,就直接重定向到登录页,并带上redirect参数,登录成功后跳回原页面。

这种体验细节很影响系统可用性。比如用户浏览商品详情页时想下单,点击"立即购买"后如果没登录,会被带到登录页,登录完成后自动跳回商品详情页,整个流程就非常顺滑。如果一个系统登录完跳到首页,用户还得自己重新找商品,体验就差很多。

4.3 商品搜索与分页展示的前端处理

商品列表页的前端逻辑,核心是搜索条件和分页参数的请求拼接。搜索条件一般有:关键字、分类ID、排序方式(价格升序、销量优先)、当前页码、每页条数。这些参数会在Vue组件的data中维护,当用户点击搜索或调整排序时,组件重新调用API,携带着新的参数向后端发起请求。

分页通常是这样的请求参数:

// 请求商品列表 async function fetchProducts() { const params = { page: currentPage, pageSize: pageSize, keyword: keyword, categoryId: categoryId, sort: sortType } const res = await getProductList(params) total = res.data.total productList = res.data.records }

前端拿到total总数后,用分页组件的current-page和total属性绑定,切换到下一页时触发翻页事件重新加载数据。这套逻辑在几乎所有后台系统的列表页面里都是一样的套路,掌握了它,任何一个管理系统的列表页你都能上手。

4.4 页面间状态共享:购物车数据流

购物车在各个页面如何保持一致性,是前端设计里比较有趣的一个点。常规思路是:页面初始化时,通过getCartData()接口加载当前用户的购物车列表,存储到Vuex或Pinia里。当用户在当前页点击"加入购物车"成功时,本地购物车数量更新,然后立即调用一次获取购物车的接口,确保后续页面显示同步。

如果项目不想用Vuex,也有一些替代方案,比如在父组件统一加载数据,用event bus或事件广播的形式通知子组件。但从维护性角度看,我用Vuex/Pinia管理购物车状态会舒服得多,因为购物车在至少三个页面(商品详情页、购物车页、全局header图标)都有展示,集中治理是长远之选。

下单成功后,还需要从购物车数据里移除已下单的商品,这一步如果漏掉,就会出现用户已经付款了但购物车里还挂着那些商品的情况。在代码里要明确:下单成功接口返回后,前端重新拉取一次购物车数据,别偷懒靠前端filter过滤完事,以接口返回的最新状态为准。

4.5 后台管理页面的表格交互

后台管理系统的前端页面,基本都是Element UI的表格组件加弹窗表单组合。商品管理页面的大致逻辑是:

  • 页面加载后,调用/admin/products接口获取商品分页数据,渲染在el-table里;
  • 点击"新增"按钮,打开el-dialog弹窗,里面是动态表单(分类选择、名称输入、价格输入、库存输入、图片上传);
  • 提交时把表单数据封装为JSON发给后端接口;
  • 成功后会刷新表格、提示成功。

这个交互模式覆盖了后台系统90%以上的CRUD场景。图片上传部分,如果项目用的是将图片base64编码后存进数据库,这个方法在数据量小的时候没问题,但数据库会快速膨胀,生产环境不推荐。比较合理的做法是:把图片上传到本地静态资源目录或对象存储服务,数据库里只存图片路径,这样接口返回轻、数据库小、页面加载也快。

5. 数据库设计分析与MySQL关键操作

5.1 核心数据表结构梳理

这个商城系统的数据库,核心表一般包括:user(用户表)、category(分类表)、product(商品表)、cart(购物车表)、order(订单主表)、order_item(订单明细表)、address(收货地址表)、admin(管理员表)。

我挑几张核心表的结构来梳理。

product商品表的核心字段:

字段名类型说明
idBIGINT主键
nameVARCHAR(100)商品名称
category_idBIGINT所属分类ID
priceDECIMAL(10,2)单价
stockINT库存数量
cover_imgVARCHAR(255)封面图片路径
descriptionTEXT商品详情描述
salesINT销量
statusTINYINT上架状态(0下架,1上架)
create_timeDATETIME创建时间

order订单主表的核心字段:

字段名类型说明
idBIGINT主键
order_noVARCHAR(32)订单编号,尽量唯一
user_idBIGINT下单用户ID
total_amountDECIMAL(10,2)订单总额
statusTINYINT状态(0待付款、1已付款、2已发货、3已完成、4已取消)
address_idBIGINT收货地址ID
create_timeDATETIME下单时间
pay_timeDATETIME支付时间

order_item订单明细表的核心字段:

字段名类型说明
idBIGINT主键
order_idBIGINT关联订单主表ID
product_idBIGINT商品ID
product_nameVARCHAR(100)商品快照名称
product_imageVARCHAR(255)商品快照图片
priceDECIMAL(10,2)成交单价快照
quantityINT购买数量

这里有个值得注意的细节:为什么在明细表里要冗余product_name和product_image这些字段?因为商品名称和图片是可能被后台修改的,一旦修改,历史订单里的信息不应该跟着变。快照机制保证了订单数据的不可变性。这种设计思路在电商系统里是一种经典实践,可以说是必会的要点。

5.2 外键与索引设计

先说外键,很多基于SpringBoot + MyBatis的实战项目在表设计时已经不再优先使用数据库外键约束。原因不是外键不好,而是在高并发写入场景下,外键约束会造成额外的数据库开销,而且查询时如果需要跨表联查,通常通过业务层的关联逻辑完成。这个项目里的表关系,更多是通过字段命名来实现逻辑上的外键关联(比如order_item.order_id指向order.id)。

索引设计方面,数据库脚本里至少应该能看到的常用索引:

  • user表:唯一索引uk_username(用户名为登录名,必须唯一);
  • product表:普通索引idx_category_id(分类筛选)、普通索引idx_status(上下架筛选);
  • order表:普通索引idx_user_id(用户查自己的订单)、唯一索引uk_order_no(订单号必须唯一)。

索引不是越多越好,每增加一个索引都会降低写入性能、占用存储空间。对一个小型商城系统来说,上述几组索引已经够用了,几个高频查询点都能覆盖到。

5.3 数据库初始化与数据脚本

这个项目"可直接运行"的重要保障,就是它提供了完整的SQL初始化脚本。通常脚本文件会放在项目根目录或/db/sql目录下面,文件名类似only_mall.sql或init.sql。

拿到脚本后,操作流程是:

  1. 在MySQL里创建一个数据库,比如CREATE DATABASE only_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
  2. 切换到该数据库:USE only_mall;
  3. 导入脚本:source /path/to/only_mall.sql;

脚本本身会自动建表,并往里插入一些演示数据,比如十几个商品、几个分类、一个管理员账号、一个普通用户账号。这就解释了为什么项目能"直接运行"——不需要你再手工造数据,启动后就有内容可看。

这里还要特别提醒一点:拿到脚本后先自己执行一遍,确认无误后再对接项目,而不要一上来就跑到项目配置里去改数据库密码。因为脚本里的字符集、引擎类型、数据格式如果和本地环境不匹配,后续排查起来会凭空多出很多麻烦。

5.4 事务在订单业务中的实际应用

前面提到过提交订单操作需要多表操作,这里结合MySQL的InnoDB引擎展开说说。

使用MyBatis时,在SpringBoot的Service实现类上加了@Transactional之后,Spring容器会把这一层的方法包在一个事务里。也就是说,从方法开始,所有Mapper的写操作都在同一个数据库连接上执行,直到方法返回成功后统一提交。

我根据实际经验给出一个更严谨的写法思路。扣除库存时,如果单纯使用:

UPDATE product SET stock = stock - 1 WHERE id = ?

在高并发场景下,两个请求同时读到了stock=1,然后同时执行减1,最后stock会变成-1,这就不对了。但如果在WHERE条件中加上stock > 0,只有一个请求能成功更新,另一个请求affected rows为0,业务层再抛异常回滚,这样可以有效防止超卖。

这种细节,是区分一个项目是"能跑"还是"能上线"的分水岭。多数课程设计项目只关注跑通,我认为一套好源码至少要在这种并发边界上有防护意识。

6. 环境准备与项目启动完整指南

6.1 本地环境要求

想把这个项目跑起来,需要准备的软件环境如下:

软件版本建议说明
JDK1.8或11SpringBoot 2.x版本对应JDK8,较新版本可能要求17,看项目pom文件
Maven3.6+后端构建工具,管理依赖
Node.js16或18前端构建工具,Vue项目需要npm进行依赖安装
MySQL5.7或8.0数据库,8.0要注意驱动版本兼容
IDEIDEA或VSCode后端用IDEA最舒服,前端VSCode轻量
Navicat/Workbench可选数据库管理工具,可视化管理更方便

环境准备环节最容易出问题的是版本匹配。比如JDK版本过高、单体或SpringBoot版本过低,可能出现编译不通过的情况。建议先看一眼后端pom.xml里的spring-boot-starter-parent版本,再决定安装哪个版本的JDK。

6.2 后端项目启动步骤

第一个阶段是数据库初始化。按照上面提到的SQL导入方法,把初始化脚本导入本地MySQL。注意MySQL的字符集要使用utf8mb4,避免中文字符乱码。

第二个阶段是修改配置。在后端项目的src/main/resources/application.yml或application.properties文件中,修改数据库账号密码为本地环境值。如果端口被占用,可以修改server.port。

第三个阶段是编译启动。在项目根目录下打开命令行,执行:

mvn clean install -DskipTests

接着去target目录查看打包出来的jar文件,或者直接在IDEA里启动主类。通常主类在com.only.shop包下,类名是OnlyMallApplication之类的。

启动成功后,控制台会打印SpringBoot banner,并输出一句类似"Started OnlyMallApplication in 5.2 seconds"的日志。保持这个窗口开着,后端服务就在运行了。

6.3 前端项目启动步骤

前端启动需要先在命令行进入前端工程目录,执行:

npm install

如果package-lock.json存在,推荐用npm ci代替npm install,这样可以锁定依赖版本,安装速度也更快、不会因为意外升级导致接口不兼容。网络不佳或镜像源不稳定的情况下,可以预设使用国内镜像:

npm config set registry https://registry.npmmirror.com

依赖安装完成后,执行:

npm run serve

如果项目是基于Vue CLI的,控制台会输出"App running at: http://localhost:8080",用浏览器打开这个地址就能访问前端首页。这里要额外核对一件事——前端配置的后端接口地址是否正确。在很多Vue项目的src/utils/request.js或src/api/index.js里,axios的baseURL可能是http://localhost:8088,要确保端口跟后端实际启动的端口一致。

6.4 跨域问题处理

前后端分离部署,最容易遇到的问题就是跨域(CORS)。前端服务器运行在8080端口,后端API运行在8088端口,两个端口不同,浏览器会拦截跨域请求。

解决方式通常有三种:

  1. 后端开发环境配置CORS(最常见):在SpringBoot里写一个WebMvcConfigurer配置类,重写addCorsMappings方法,允许指定来源或所有来源访问。
  2. 前端代理转发(也常见):在Vue的vue.config.js里配置devServer.proxy,把/api前缀的请求转发到后端地址。
  3. 生产环境中用Nginx做反向代理,前端和后端共用同一个域名。

我在调试这个项目的时候,直接用方式2,原因很简单:改了后端CORS配置等于允许所有来源访问,不太安全;前端代理只在开发环境生效,不影响生产部署。配置起来也很简单:

module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8088', changeOrigin: true } } } }

6.5 部署到服务器的建议

如果想让项目在局域网或服务器上长期运行,我的建议是这样的:

  • 前端执行npm run build,构建出dist/静态目录;
  • 用Nginx托管dist/目录,配好root和index;
  • 后端执行mvn package,把项目打成可执行的jar包,通过java -jar方式启动,或者用Systemd配置成服务;
  • Nginx里做一个反向代理:把/api路径的请求转发到SpringBoot的8088端口;
  • 数据库生产环境不要用root账号,新建一个最小权限的专有账号;
  • 敏感配置项(数据库密码、密钥)不要硬编码在配置文件里,可以用环境变量或配置中心管理。

有一点很值得注意:jar包方式和IDE启动方式对资源占用不同,如果服务器内存紧张,可以通过调整JVM参数来控制占用。比如:

java -Xms128m -Xmx256m -jar only-mall.jar

这样可以显著降低对小内存云主机的压力。

6.6 安全排查与项目接管建议

回到我前面提到的安全确认环节。任何从一个未知来源获得的源码在真正运行之前,我都建议执行这样几个动作:

  1. 检查配置文件:application.yml、.env等文件中是否包含可疑的服务器地址、奇怪的网络回调地址;
  2. 检查依赖清单:pom.xml和package.json中是否有不常见或拼写可疑的依赖坐标;
  3. 检查代码中的"彩蛋":搜索全局关键词,比如"curl"、 "exec"、 "Runtime"、 "URLConnection"、"eval",确认没有隐藏的外部请求或控制逻辑;
  4. 数据库脚本:确认初始化脚本里只有建表和插入数据语句,没有可疑的存储过程或计划任务。

这不是不信任他人的代码,而是这一行的基本安全意识。尤其是数据库连接信息,如果原代码内置的是一个互联网地址,务必要改成自己的本地地址。

7. 常见问题与排查技巧实录

7.1 启动类报错:端口被占用

现象:后端启动时抛出Web server failed to start. Port 8088 was already in use.

排查:用命令行查看端口占用情况:

netstat -ano | findstr 8088

找到占用端口的进程,用任务管理器结束对应进程即可。或者更省事的方式是直接修改application.yml里的server.port,换一个不冲突的端口,同时要记得同步修改前端request.js中的baseURL或本地代理配置。

7.2 数据库连接失败:Access denied for user

现象:java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)

排查:99%的原因是application.yml里填写的密码与实际数据库密码不一致,或者MySQL用户本身不允许本地或远程登录。先在命令行手动连一下数据库验证账号密码:

mysql -u root -p

确认密码没错后,再检查MySQL是否开启了远程访问权限(如果SpringBoot项目跑在另一台机器上):SELECT host, user FROM mysql.user;,确保当前用户和host匹配。

7.3 前端安装依赖失败

现象:npm install的时候一直报错,或者卡在某个依赖上。

排查:最常见的是网络问题导致部分registry请求超时。解决方案大致是这样:

npm config set registry https://registry.npmmirror.com npm install

如果还不行,试试清缓存:

npm cache clean --force

还有一个容易忽略的点是Node.js版本问题。旧版本Vue CLI可能不支持Node 18以上的版本,建议安装nvm(node version manager)来切换Node版本,而不要硬着头皮往下跑。

7.4 登录功能正常但商品图片不显示

现象:页面能打开,但商品图片都是裂开的。

排查:这是比较典型一个案例。如果项目使用本地文件存储上传的图片,而数据库中保存的图片路径是一个静态资源映射,比如/upload/xxx.jpg,那么需要在SpringBoot的配置里添加静态资源映射规则,或者在Nginx里配上对应的location路径。如果图片是从外链读取的,那可能是网络或IP限制问题。

处理方式通常是这样的:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:/真实路径/upload/"); } }

7.5 前端请求405或404错误

现象:登录时点击按钮,抓包发现接口返回404或405。

排查:这种情况往往是两个问题混在一起:

  • URL路径不匹配:确认前端调用的接口路径和后端Controller里的mapping路径是否完全一致,包括大小写、正斜杠;
  • 请求方法不匹配:前端用了POST,后端Controller的方法是@GetMapping;或者前端传参方式是JSON,后端方法却要求表单字段。

这种问题用浏览器的F12开发者工具看一下Network面板,重点看请求URL、请求方法和响应体内容,基本上就能立刻定位原因。

7.6 订单提交失败、事务回滚

现象:点击提交订单后提示"库存不足"或"下单失败",但是部分数据已经写入了数据库。

排查:这种现象说明事务没有正确回滚。常见的坑有:

  • @Transactional注解加在了私有方法或内部调用方法上(Spring通过代理实现事务,私有方法、同类内部调用不走代理,事务无效);
  • 方法里手动catch了异常没有重新抛出,导致Spring感知不到异常,事务不会触发回滚;
  • 数据库表用的是MyISAM引擎(不支持事务),需要改为InnoDB。

注意,第三点容易在此项目中遇到。如果SQL脚本建表时我见过用默认引擎的情况,注意确认一下SHOW TABLE STATUS WHERE Name = 'order';的结果里Engine是不是InnoDB即可,不是的话就不能保证事务性。

7.7 导入SQL时报错或乱码

现象:SQL脚本导入时提示语法错误,或者导入后页面中文显示乱码。

排查:先确认脚本本身的编码格式,使用UTF-8编码;再确认导入时客户端连接字符集,比如命令行导入前先设一下:

mysql --default-character-set=utf8mb4 -u root -p

同时检查数据库和表是否也是utf8mb4字符集。有时候是脚本里用了某些函数或语法只适用于高版本MySQL,比如MySQL 8.0支持的写法在5.7里就会报错,需要根据自己所装的MySQL版本进行调整。

7.8 其他LESS常见但值得注意的小问题

这类问题我干脆做个快速整理:

问题现象最可能原因最快对策
前端页面白屏,控制台报Vue相关错误依赖版本冲突或未正确安装检查package.json,必要时重装node_modules
修改了数据库但页面数据不刷新浏览器缓存或者用户状态未更新硬刷新(Ctrl+Shift+R),或确认是否使用了缓存策略
Token过期后接口报错前端拦截器未统一处理401状态在axios响应拦截器中加入401跳转到登录页的逻辑
后台管理页面无法访问当前用户不是管理员角色用脚本内置的管理员账号登录,并检查roleId字段
图片上传失败上传目录无写权限给上传目录赋Linux可写权限,比如chmod -R 755 upload

8. 总结复盘与二次开发建议

8.1 从这份源码里学到了什么

把这个项目完整跑通后,它的价值不仅是"能运行",还在于它清晰展示了一套基本的电商系统由哪些部分构成。我复盘了一下,至少能在三个方面受益:

  • 后端方面:从三层架构、统一返回结果、事务管理到库存扣减的并发控制,这是一个浓缩的标准业务开发流程;
  • 前端方面:路由守卫、状态管理、表格CRUD、axios封装,这些都是前端岗位面试常问、日常开发常写的东西;
  • 数据库方面:表设计、索引、字符集选择、数据初始化,每一步都对应着真实项目的部署和运维需求。

如果只是把代码下载下来启动一遍就完事,那学到的东西有限。我更推荐的方式是拿着源码做一个"破坏性实验":比如故意删掉一个索引前后做一次性能对比;给某个接口添加一个参数,追踪它从前端到后端再到数据库的完整流转链路;或者给登录接口加一个验证码模块,看看JWT和用户状态是怎么管理的。这些实验做完,你才算真的"拥有"了这套代码。

8.2 适合二次开发的方向

基于这套商城系统,如果你有时间和精力,可以在下面这几个方向上做功能迭代:

  • 接入真实支付:比如微信支付和支付宝的完整对接,把模拟支付替换为真实交易流程(需要商户资质和相关环境);
  • 增加秒杀或促销模块:比如限时折扣、满减计算、优惠券领取与使用,这是电商业务中拉动用户活跃度的常见环节;
  • 引入Redis缓存:把首页热门商品、分类信息、用户Token等热点数据放到缓存中,降低MySQL的压力;
  • 完成前端体验优化:比如增加商品多图轮播、SKU规格选择(颜色、尺寸)、评价功能、收藏功能;
  • 做数据分析:在后台增加销售统计报表、用户活跃度图表,接入ECharts可视化。

其中,引入Redis那一步可以和系统已有的Token存储机制结合,用Redis做Token的自动续期和提前失效,是一个收益率挺高的改动点。但若只是个人学习阶段的演示项目,也不要一上来就把方案搞得过于庞大,先跑通,再做合理盲区上的增强会更稳妥。

8.3 一点掏心窝的建议

最后分享一个我自己的习惯:拿到任何一份源码的第一天,我都会先在本地把数据库脚本执行一遍,紧接着把项目启动一遍,然后花一个下午的时间把核心表之间的关联搞明白,在纸上画出它们的关系图。这个动作看似基础,但它能让我后面一周的调试都顺畅得多。

如果你在这个项目上遇到什么奇怪的问题,不妨按照章节七的排查清单走一遍。如果排查不掉,还可以看日志——SpringBoot的日志输出里有很多线索,比如SQL执行错误、远程接口超时、依赖加载失败等等。日志是最忠实的朋友,不要嫌它长,它通常已经告诉了你答案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询