前后端分离、SpringBoot、Vue、MyBatis、MySQL,这几个技术词放一起,基本就是国内中小型电商项目最常见的标准模板。我今天要拆解的这套ONLY在线商城系统,就是把这套组合从头到尾跑通的实战项目,完整源码、SQL脚本、部署流程都有。项目名字里的ONLY是我随手起的代号,跟任何服装品牌没关系,大家直接把它当一套普通商城项目看就行。
这套系统本身不算重,但电商的核心链路全部覆盖了:前台用户能注册登录、浏览商品、加购物车、下单付款;后台管理员能管理商品分类、处理订单、管理用户。对正在学SpringBoot+Vue的初学者来说,它是一个可以把前后端如何协作、接口怎么设计、部署怎么落地彻底搞明白的绝佳样例;对需要快速搭演示项目的朋友来说,按这篇文章的步骤操作,基本一下午就能把整套系统跑起来。
1. 项目概述:这套商城到底做了什么
1.1 核心功能与页面地图
我在设计这套商城的时候,没有一上来就堆功能,而是先把角色拆清楚。系统只有两类用户:普通买家和后台管理员,所以页面也分成两个端口。
买家端主要页面有:首页、商品列表页、商品详情页、购物车、订单结算页、我的订单、个人中心、登录注册。管理员端主要页面有:后台首页看板、商品管理、商品分类、订单管理、用户管理。整体逻辑就是买家下单,管理员接单发货,这就是最基础的B2C闭环。
功能点拆开看:
- 用户模块:注册、登录、JWT鉴权、个人资料修改、收货地址维护。
- 商品模块:分类浏览、商品列表分页、商品搜索、商品详情、上下架状态控制。
- 购物车模块:加入购物车、修改数量、勾选商品、删除商品。
- 订单模块:提交订单、生成订单明细、模拟支付、查看订单列表、确认收货。
- 后台模块:商品CRUD、分类CRUD、订单状态操作、用户列表查看。
这里没有做秒杀、优惠券、积分商城这类复杂的营销玩法。原因很简单:第一版项目最重要的是把主干链路做稳,电商的骨架先立起来,后续要加营销功能,在表结构和代码分层都合理的前提下扩展并不难。
1.2 技术栈选型与理由
| 层次 | 选型 | 选择理由 |
|---|---|---|
| 后端框架 | Spring Boot 2.7.x | 生态成熟、配置简洁,适合中小型项目快速开发 |
| 持久层 | MyBatis | SQL可定制性强,复杂查询好控制,面试常问 |
| 数据库 | MySQL 8.0 | 主流关系型数据库,支持事务,运维资料多 |
| 鉴权方案 | JWT | 前后端分离下天然合适,服务端无状态,扩展性好 |
| 前端框架 | Vue 2.7 + Element UI | 组件成熟、上手快,适合快速搭后台界面 |
| 状态管理 | Vuex | 统一管理用户信息、购物车数等全局状态 |
| 构建工具 | Maven + npm | 后端和前端最普及的构建方案,几乎没有学习成本 |
有人问为什么不用MyBatis-Plus,而是用原生的MyBatis。我的想法很简单:如果你连原生MyBatis的Mapper接口、XML映射、动态SQL都写过一遍,后面再用Plus是非常容易的;反过来,一开始就用Plus,遇到复杂查询和SQL优化时会很被动。商城项目里有不少多表关联和条件统计的SQL,原生MyBatis能让我完全掌控SQL,出现性能问题也知道怎么改。
1.3 前后端分离架构的工作方式
前后端分离不等于两个项目各写各的,重点是它们通过HTTP接口通信。前端只负责页面渲染和用户交互,后端只负责业务逻辑和数据持久化,两者通过约定的JSON数据格式对接。
在这套ONLY商城里,后端统一返回一个Result对象,结构如下:
json复制{
"code": 200,
"message": "操作成功",
"data": {}
}
前端的axios封装统一处理这个结构,code为200时取data,code为401时跳登录页。这样不管是商品列表还是下单接口,前后端都遵守同一套协议,省去了大量沟通成本。
目录结构上也做了明确分层。后端按照Controller、Service、Mapper、Entity四层划分,前端按照views、router、store、api、utils划分。这套结构说不上多高级,但它足够清晰,后文我会逐个模块展开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统设计思路:数据库与接口先想清楚
2.1 数据库表结构设计
我在动手写代码之前,花了不少时间设计数据库表。表结构如果设计不合理,后面写SQL和业务逻辑会相当痛苦。这套商城最终落地的核心表有9张,我整理成表格方便对照。
| 表名 | 作用 | 关键字段 |
|---|---|---|
| user | 用户表 | id, username, password, nickname, phone, avatar, create_time |
| category | 商品分类表 | id, name, sort, icon |
| product | 商品表 | id, category_id, name, subtitle, main_image, price, stock, sales, status |
| product_image | 商品图片表 | id, product_id, url, sort |
| cart_item | 购物车表 | id, user_id, product_id, quantity, checked |
| address | 收货地址表 | id, user_id, receiver, mobile, province, city, district, detail, is_default |
| order | 订单主表 | id, order_no, user_id, address_snapshot, total_amount, pay_amount, status, pay_status, create_time |
| order_item | 订单明细表 | id, order_id, product_id, product_name, product_image, price, quantity, total_price |
| banner | 首页轮播图表 | id, image, link, sort, status |
商品表单独拆一张product_image表,是因为一个商品通常有多张图片,如果全塞在product表里的image字段里,用逗号拼接也可以,但后续要单独维护图片排序和主图时会很麻烦。用子表的方式更规范,查询时一对多关联即可。
订单表里存了address_snapshot字段,这个是快照数据。因为用户下单后如果修改了收货地址,订单历史里的地址不应该跟着变。这个细节很多人会忽略,但实际电商里非常重要。同样的道理,order_item里冗余了product_name和product_image,也是为了让订单数据不依赖商品表的变化。
2.2 订单状态与支付逻辑
订单模块是电商系统里最绕的部分。我设计了两个状态字段:status和pay_status。
status表示订单生命周期状态:
- 0:待支付
- 1:待发货(已支付)
- 2:待收货(已发货)
- 3:已完成
- 4:已取消
pay_status表示支付状态:
- 0:未支付
- 1:已支付
可能有人问:status为“待支付”的时候pay_status一定为0,为什么还要单独存一个支付状态?因为后续如果接入真实支付,会出现“已支付但订单还没发货”的中间状态,而且退款逻辑也依赖支付状态,分开存能避免状态机混乱。
模拟支付的实现也很简单,用户下单后跳到支付页面,点击“确认支付”,后端直接调用一个本地支付模拟接口,把pay_status更新为1,status更新为1。如果接入支付宝沙箱或微信支付,只需要替换这个支付接口即可,订单核心逻辑完全不用改。
2.3 接口设计要点
前端和后端是并行开发的,所以接口文档必须先行。我没有用特别重的接口管理工具,直接在项目里维护了一份接口清单,约定好请求方式和参数。
几个核心接口示例:
| 功能 | 请求方式 | 接口路径 |
|---|---|---|
| 用户登录 | POST | /ap |
