news 2026/10/20 9:35:28

SpringBoot2+Vue3宠物商城系统:前后端分离实战全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot2+Vue3宠物商城系统:前后端分离实战全解析

前后端分离项目写了几年,技术栈审美会变得异常挑剔。2020年以后我接到的商城类项目需求,十条里有七八条是同一套配方:SpringBoot2 写后端、Vue3 写前端、MyBatis-Plus 管数据访问、MySQL8 做存储。这个 Java Web 宠物商城系统项目就是很典型的例子——它不搞花活,但把一套商城系统的骨架搭得相当完整,商品、分类、购物车、订单、会员、后台管理全都覆盖,还带文档,非常适合刚走完 JavaWeb 基础、想接触前后端分离实战的同学拿来练手,也适合有经验的开发者把它当成脚手架二次改造。

这个项目能解决什么问题?一句话概括:它用最小成本演示了一条完整的电商业务链路,从用户注册登录、浏览下单,到管理员处理订单、管理商品数据,每一层都给出了可以跑的代码。相比零散的教程 Demo,它最大的价值在于"完整"——前后端通过 RESTful 接口通信,数据库表结构设计合理,权限用 JWT 处理,分页、文件上传、条件查询这些高频需求都有落地实现。无论你是学生做毕业设计,还是初级工程师想研究一套正规项目的组织方式,这套代码都值得拆开来仔细看一遍。

接下来说实际拆解过程中的重点和那些藏在代码细节里的坑。我会按技术选型复盘、核心业务链路、关键代码实现、部署注意点、扩展方向这几个方面展开,读的时候建议你打开项目源码对着看,体验会更好。

1. 技术选型复盘:这套组合为什么长盛不衰

先说结论:这套技术组合能成为中小型 Web 项目的"标准答案",是因为每一个参与者在自己的位置上都是性价比最稳的选择。它们单拎出来都不是最新的,但组合在一起,学习成本、开发效率、上手门槛恰好达到一个完美的平衡点。

1.1 项目需要什么样的技术骨架

宠物商城不是纯粹的电商,它比标准 B2C 多了几个特色场景:宠物活体要按品种、年龄、性别做多维筛选;宠物用品有不同规格(口味、大小、适用体型);订单除了普通商品还会涉及预约、寄养这类服务型条目。但核心业务骨架仍然是经典的"用户-商品-订单"三角模型。

在这种规模的项目里,如果用传统的 SpringMVC + JSP 做同步页面,前后端代码会揉在一起,后期改一个页面要连带重启后端,开发和体验都差。如果上微服务那套(注册中心、网关、分布式事务),对这样的项目完全是过度设计,光搭建环境就能劝退一半人。于是 SpringBoot2 负责快速构建独立 API 服务、MyBatis-Plus 免去大量重复的持久层代码、Vue3 负责页面交互和组件化开发、MySQL8 提供稳定可靠的数据存储——每一层都有明确的职责分工,没有多余的环节。

1.2 SpringBoot2 与 MyBatis-Plus 的分工逻辑

SpringBoot2 在这里的核心作用不是"框架",而是"自动装配的生工程"。它把 Web 容器(内嵌 Tomcat)、JSON 序列化、参数校验、数据库连接池全都通过 starter 一键整合。开发者只需要关注自己的业务代码,启动一个 main 方法就完成了过去配置一大堆 XML 的工作。

MyBatis-Plus 则是 MyBatis 的增强工具,它提供了 BaseMapper 接口,单表 CRUD 不用写任何 SQL。但注意,这个"不用写 SQL"是有边界的:遇到多表联查、复杂统计、动态条件拼装,MyBatis-Plus 的 QueryWrapper 虽然能写,但可读性会迅速下降。所以在宠物商城项目里,合理的做法是简单操作依赖 MP 的通用方法,订单统计、报表这类复杂查询则直接在 Mapper 里写自定义 SQL,两者互补,而不是二选一。

1.3 Vue3 和 MySQL8 在这个项目里的定位

Vue3 相对于 Vue2 的最大变化不是渲染性能,而是组合式 API(Composition API)带来的逻辑复用能力。商城项目有大量页面级功能——购物车的状态管理、表单校验、路由守卫、接口封装……如果用 Vue2 的选项式写法,逻辑会分散在 data、methods、computed 里,逻辑稍微复杂就难维护。Vue3 允许把同一个业务场景的状态和执行函数组织在一起,这在商城这种多模块项目里体验差别非常大。

MySQL8 则是很多人容易低估的一环。很多教材还在教 MySQL5.7 的写法,但 8.0 已经是当前生产环境的事实标准了。它默认字符集是 utf8mb4,真正做到完整支持 emoji 和生僻字;支持窗口函数,一些订单排名的 SQL 写起来简洁得多;另外缓存策略改变后,一些 5.x 时代的老调优经验已经失效,如果你还在用 5.7 的思路调 8.0 的性能,可能会事倍功半。

对比项传统方案本项目方案优势
前端渲染JSP / 服务端模板Vue3 + 前端路由前后端分离,团队并行开发
后端结构SpringMVC XML 配置SpringBoot2 自动装配减少配置,快速启动
数据持久层手写大量 MyBatis XMLMyBatis-Plus 通用 Mapper单表零 SQL,效率翻倍
数据库MySQL5.7MySQL8.0utf8mb4、窗口函数、更强性能

这套组合选完,下一步就是梳理业务功能,看看一个端到端的商城系统到底有哪些东西要写。

2. 核心业务功能域拆解:宠物商城必须覆盖哪些完整链路

如果你已经跑过项目,会发现这套代码由两个子工程组成:一个后端服务存放/api开头的接口,一个前端工程负责页面渲染。整个系统分为面向普通用户的"商城前台"和面向运营人员的"管理后台"两大块。

2.1 前台用户端的操作链路与接口设计

用户端不是简单地"浏览商品 + 下单",而是分阶段组织接口。注册登录阶段:前端提交用户名密码,后端校验通过后用 JWT 生成 Token 返回,后续所有请求都在请求头里携带 Token,由一个拦截器统一解析用户身份。这里的要点是——密码在数据库里绝不能明文存储,至少要经过 BCrypt 加盐哈希再落库,项目代码里如果用了简单 MD5,建议你自己改成 BCrypt,这是行业底线问题。

商品浏览阶段:首页要展示宠物轮播图、热销榜单、分类入口。这里高频的基础接口包括:分类列表接口、分页查询商品接口、商品详情接口、模糊搜索接口。宠物商城比较特殊的一点是商品规格——一只猫有多种"规格(品种、性别、年龄)",如果像普通衣服一样简单用 SKU 字段存,查询条件会非常难写。好的做法是单独建一张规格表,通过商品 ID 关联,筛选时用 JOIN 查出来再分组展示。

下单支付阶段:从购物车结算、生成订单、模拟支付到查询订单,这是一条完整事务链。购物车是典型的临时数据,可以用一张表存 userId、商品Id、数量、勾选状态;结算时将勾选的购物车条目读取出来,生成订单主表记录和订单明细子表记录,然后清空对应购物车条目。那个"清空"操作要和"生成订单"在同一个事务里,否则会出现订单生成了、购物车没清,或者反向的脏数据。

2.2 后台管理端的功能划分与权限控制

管理后台是运营人员的操作入口,功能和前台完全不同。它至少要有:管理员登录与鉴权、商品管理(上架/下架/编辑/库存增减)、分类管理、订单管理(查看订单详情、修改状态、发货)、用户管理(禁用/启用账户)、数据统计看板(销量排行、销售趋势)。

这里的权限控制不同于前台简单的 JWT 身份验证,还要做角色区分——比如普通管理员只能处理订单,商品管理员只能维护商品,超级管理员才有所有权限。项目里通常的做法是给管理员表加一个 role 字段,然后在后端拦截器里检查接口权限,而不是单纯靠前端隐藏按钮做限制。记住一个原则:前端的任何控制都只是体验优化,真正的权限边界必须在后端校验。

2.3 订单状态机的设计是这套代码的精华之一

电商项目最容易写乱的就是订单状态。很多人用一两个字符串字段来回改,改着改着状态就乱了。这个项目的订单状态设计可以当模板来学,核心是让状态流动遵循一条单向流转链:

待支付 → 已支付(待发货) → 已发货 → 已完成 ↘ 已取消 待支付 → 已取消(超时关闭)

每个状态变更都对应一次真实的业务操作:用户点击取消、用户支付成功、管理员发货、管理员确认完成。代码里状态枚举和状态变更的合法性校验是必须的——不能允许从"已完成"直接跳到"待支付"。我在自己的商城项目里见过不止一次这种 bug,根源就是状态变更散落在多个 Service 方法里,没有集中校验。建议学这套代码的时候,把状态变更统一抽到一个 OrderStatusService 里,每个公开方法只允许一次合法流转,排查问题会容易十倍。

3. 关键功能代码级实现笔记:从 Service 层到 Mapper 层的细节

这一部分我挑几个实际运行中最容易出问题的功能写,每一个都配合背后的原理讲清楚为什么这么写。你可以对照项目源码里的具体类看,效果更好。

3.1 购物车结算的事务边界问题

购物车结算逻辑看着简单,无非是查购物车、算总价、插订单表、删购物车记录。但越是简单的逻辑越容易踩事务的坑。

@Transactional(rollbackFor = Exception.class) public Long settleCart(List<Long> cartItemIds, Long userId) { // 1. 校验购物车条目归属当前用户 List<CartItem> cartItems = cartItemMapper.selectBatchIds(cartItemIds); if (cartItems.stream().anyMatch(item -> !item.getUserId().equals(userId))) { throw new BusinessException(500, "购物车数据异常"); } // 2. 计算总金额(注意用 BigDecimal,不要用 double) BigDecimal totalAmount = cartItems.stream() .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getCount()))) .reduce(BigDecimal.ZERO, BigDecimal::add); // 3. 创建订单主记录 Order order = new Order(); order.setUserId(userId); order.setOrderNo(generateOrderNo()); order.setTotalAmount(totalAmount); order.setStatus(OrderStatusEnum.UNPAID.getCode()); orderMapper.insert(order); // 4. 创建订单明细 for (CartItem item : cartItems) { OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(item.getProductId()); orderItem.setProductName(item.getProductName()); orderItem.setPrice(item.getPrice()); orderItem.setCount(item.getCount()); orderItemMapper.insert(orderItem); } // 5. 删除已结算的购物车条目 cartItemMapper.deleteBatchIds(cartItemIds); return order.getId(); }

这段代码最关键的只有两点。第一,@Transactional注解默认只回滚RuntimeException,如果业务里抛的是自定义检查异常,一定要加rollbackFor = Exception.class,否则方法执行到一半出错,前一半的数据照样提交,这是最常见的"看上去有事务,实际上没有"的假象。第二,金额计算必须用BigDecimal,double在涉及商品单价和数量的乘法时会产生精度丢失,商城里这种事一旦出错就是资损问题,不能妥协。

3.2 商品列表与搜索:MyBatis-Plus 条件构造器的正确姿势

前台商品列表通常有多个筛选条件:关键字、分类、价格区间、上下架状态、排序方式。如果用传统 MyBatis 手写 SQL,这些条件要逐个判断是否为空再拼where片段,代码又长又容易漏条件。MyBatis-Plus 的LambdaQueryWrapper就是为这个场景设计的。

public IPage<Product> searchProducts(ProductQuery query) { LambdaQueryWrapper<Product> wrapper = Wrappers.lambdaQuery(); // 关键字模糊搜索商品名 wrapper.like(StringUtils.hasText(query.getKeyword()), Product::getName, query.getKeyword()); // 分类筛选 wrapper.eq(query.getCategoryId() != null, Product::getCategoryId, query.getCategoryId()); // 价格区间 wrapper.ge(query.getMinPrice() != null, Product::getPrice, query.getMinPrice()); wrapper.le(query.getMaxPrice() != null, Product::getPrice, query.getMaxPrice()); // 只查上架商品 wrapper.eq(Product::getStatus, ProductStatusEnum.ON_SALE.getCode()); // 排序策略 if ("price_asc".equals(query.getSort())) { wrapper.orderByAsc(Product::getPrice); } else if ("sales_desc".equals(query.getSort())) { wrapper.orderByDesc(Product::getSalesCount); } else { wrapper.orderByDesc(Product::getCreateTime); } Page<Product> page = new Page<>(query.getPageNum(), query.getPageSize()); return productMapper.selectPage(page, wrapper); }

这里有个新手非常容易犯的错误:like条件的第一个布尔参数是"是否拼接该条件",很多人会漏掉这个参数,或者写反逻辑,导致条件变成"空值也要拼接",查出来的结果永远是空。记住规则:第一个参数传true,条件就参与 SQL;传false,MyBatis-Plus 会自动忽略这一条。配合StringUtils.hasText判断空字符串,可以避免用户输入空格引起的误匹配。

3.3 宠物图片上传与静态资源映射

商城系统离不开图片上传。宠物商品的图片尤其多,一只宠物往往要传五六张展示图。这套代码里的实现是典型的本地存储方案:前端用 FormData 上传文件,后端接收MultipartFile后保存到服务器磁盘固定目录,然后把保存后的文件访问 URL 返回给前端。

这里我强烈建议你注意一个容易被忽略的问题——文件不能存到项目运行目录(classes)里,否则项目重启或重新部署时图片会全部消失。正确做法是在服务器上单独规划一个目录,比如/data/image/pet/,然后用 WebMvc 配置类做静态资源映射:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:/data/image/pet/"); } }

这样图片放在磁盘,项目重启不会丢,也不占用 classpath 空间。另外要做图片压缩——手机端上传的照片动辄几 MB,直接存原图会把服务器带宽和数据库体积都撑爆。如果项目里没有压缩逻辑,建议加一个简单方案:用 Java 自带的ImageIO把宽度超过 1200 的图片等比缩放后再落盘,对用户体验和服务器压力都有明显改善。

3.4 订单编号生成与分页插件的关联坑

订单号生成是一个看起来不起眼、但实际非常讲究的问题。通常不能用数据库自增 ID 直接当订单号,因为会暴露系统销量,也容易被人猜测遍历。项目里常见的做法是"时间戳 + 随机数 + 用户 ID 后几位"拼成唯一编号。如果并发量高,还要考虑做号段缓存或分布式 ID,但在这个体量的项目里,用时间戳 + 三位随机数就够用了,只要注意用数据库唯一索引兜底防止撞号。

分页则是 MyBatis-Plus 插件的功劳。但有个问题容易在联调时暴露:如果前端传的页码从 0 开始,而后端Page默认从 1 开始,第一页的数据会错位甚至空白。这个项目里前后端如果哪个地方对不上,优先检查的就是这里——统一约定"页码从 1 开始"能少踩很多坑。还有一点是 MyBatis-Plus 分页插件的高版本已经不再支持旧版的limit ? offset ?拼接方式,一定要在配置类里注册MybatisPlusInterceptor并添加PaginationInnerInterceptor,否则分页查询会查出全量数据。

4. 部署与上线的易错环节:从本机跑通到服务器稳定运行

拿到源码之后,第一步当然是本地把前后端跑起来。但很多文章只会说"导入运行",不告诉你哪些地方会因为环境差异而翻车。下面这几条是我自己在部署这类项目时几乎每次都会遇到的坑,直接给你排好。

4.1 MySQL8 驱动与连接配置细节

如果你用的是 MySQL8,连接数据库的驱动类不再是com.mysql.jdbc.Driver,而是com.mysql.cj.jdbc.Driver。SpringBoot 的spring.datasource.driver-class-name如果没改,启动时就会报ClassNotFoundException。

连接 URL 也要注意两个参数:serverTimezone必须指定,常用的中国时区写法是Asia/Shanghai;useSSL建议设为false,避免控制台刷一堆 SSL 连接警告。

spring: datasource: url: jdbc:mysql://localhost:3306/pet_mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver

还有一个 MySQL8 特有的坑:allowPublicKeyRetrieval在某些连接工具和驱动版本下不设置会报Public Key Retrieval is not allowed错误。这不是大问题,加进 URL 后面就好。如果你在导入数据库脚本时发现中文乱码,检查一下 Schema 的默认字符集是不是utf8mb4,MySQL8 建库脚本里高频出现这个问题。

4.2 Vue3 开发环境跨域与生产环境路由

前端开发环境调用后端接口时会遇到经典的跨域问题:页面跑在localhost:5173,接口在localhost:8080,浏览器默认会拦截跨域请求。正确的处理方式不是在后端加@CrossOrigin全局放开(这样生产环境也会放开,有安全风险),而是在 Vite 配置里做代理:

// vite.config.js server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端请求/api/pet/list时,开发服务器会把请求转发到后端,而且请求头里的 Host 也会被改写成后端地址,浏览器感知不到跨域的存在。后端只需要在拦截器里做身份校验,不必操心跨域逻辑。

另一个容易踩的坑是 Vue3 路由的 history 模式上线后刷新 404。原因是history模式依赖浏览器的 History API,页面刷新时浏览器会真的去请求/pet/detail/1这个路径,而后端没有对应的路由,就会返回 404。解决办法是在 Nginx 配置里加一条 rewrite 规则,把所有路径都指到index.html:

location / { try_files $uri $uri/ /index.html; }

如果你是后端工程师,第一次配前端上线时十有八九会漏这一条,记得在部署清单里把它写进去。

4.3 Linux 服务器部署的常规流程与资源规划

项目部署到 Linux 服务器时,通常流程是这样:本地mvn clean package打成 jar 包,用 FTP 工具传到服务器;前端npm run build生成dist目录,传到 Nginx 的html目录。后端用nohup java -jar pet-mall.jar > logs/pet-mall.log 2>&1 &启动,注意一定要加nohup ... &,否则关掉终端进程就结束了。

资源规划方面,一台 2 核 4G 的云服务器跑这套商城完全够用。但要注意 JVM 参数:不要直接用默认配置,建议启动时加上-Xms512m -Xmx1024m,防止堆内存占用过高导致服务器 OOM。如果访问量大,可以再加一个 Nginx 做静态资源缓存——商品图片这种不常变动的资源,设置expires 30d能显著降低后端负载。

5. 这套代码的扩展方向:从毕业设计到能上线的商用项目

当你把项目跑通、理解了核心链路之后,下一步就是如何把它改造成真正可商用的系统。下面这几个扩展方向是按优先级排的,每个方向我都能给出具体的改造思路。

5.1 接入第三方支付与订单对账

目前项目里的"支付"多半是模拟操作,只把订单状态改成已支付。要跑真实业务,就要接入第三方支付平台的接口。核心改动有两点:一是下单接口生成收款参数后需要生成支付二维码;二是需要接收支付结果回调(通知接口),在回调里校验签名并修改订单状态。

这里最大的坑是回调接口必须是幂等的——同一笔支付结果可能被通知多次,如果每次通知都执行一次状态修改,可能出现重复发货。套路是在回调处理里先查订单当前状态,如果已经是已支付就直接返回成功,不再重复修改。

5.2 宠物档案与寄养/预约服务

宠物商城和普通电商的差异点,最终都会落在"宠物服务"上。比如宠物购买后需要免疫档案、驱虫提醒;寄养服务要预约时间、选择寄养房间、上传日常照片。这些在项目里没有现成实现,但表结构完全可以扩展:增加宠物档案表(关联用户和宠物商品)、预约订单表(关联订单和寄养套餐)、服务记录表(每次寄养生成一条)。前端加两个页面即可,后端逻辑和现有的订单模块非常相似,复用率很高。

5.3 拼团、优惠券与营销模块

营销玩法是商城持续运营的灵魂,也是当前项目最值得扩展的地方。优惠券模块至少要覆盖:发券(注册送、满减送)、领券(用户手动领取)、核销(下单时校验可用券)、过期清理(定时任务扫过期券)。拼团则需要额外一张拼团活动表和拼团参与表,下单时记录成团状态,成团后再把订单状态流转到待发货。这一块涉及的并发逻辑要小心,可以有意识地学习一下高并发下的库存扣减和幂等设计。

5.4 数据统计可视化

后台管理端的看板目前如果是简单的列表统计,可以考虑引入更丰富的图表展示。用 Vue3 生态的图标库绘制销量折线图、分类占比环形图,界面会更专业。数据来源上,可以写一个定时任务,每天凌晨把前一天的订单汇总写入统计表,这样看板查询不需要实时聚合大表,性能也稳。


这套代码的完整度在同体量项目里算是名列前茅的,从数据库脚本到后台接口再到前端页面,全链路都是闭环的。我个人的建议是不要只把它当成"要做的东西"直接交作业,而是逐层吃透——先按前两节的内容梳理业务模块,再对着第三节的位置点开代码看具体实现,最后自己动手把购物车或订单模块重写一遍。踩过几次坑之后你就会发现,以后做任何电商项目,脑子里都有一张清晰的"业务-技术"映射图。最后再分享一个小技巧:改动数据库表结构之前,一定要先备份一份原始数据脚本,我在扩展优惠券模块时改错过一张订单表,多亏有备份才没有把演示数据弄丢。

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

NVIDIA驱动安装失败与CUDA不可用的系统级原因解析

1. 为什么显卡驱动装不上、一更新就黑屏、CUDA跑不起来&#xff1f;这不是玄学&#xff0c;是可复现的系统级问题你是不是也遇到过这些场景&#xff1a;刚装好Ubuntu&#xff0c;nvidia-smi报“NVIDIA-SMI has failed”&#xff0c;连驱动都没加载&#xff1b;或者Windows下更新…

作者头像 李华
网站建设 2026/10/15 10:33:44

LSTM-GPR混合模型实现新能源汽车销量概率预测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/19 12:54:15

SpringBoot电商平台课程设计实战:从环境搭建到答辩演示

简介&#xff1a;这是一份基于SpringBoot的电商平台毕业设计论文文档&#xff0c;主要面向计算机专业毕业生、高职院校学生以及正在学习Spring Boot开发的读者&#xff0c;解决电商平台设计思路欠缺、毕业设计论文结构混乱等问题。文档以正规学位论文格式编排&#xff0c;包含中…

作者头像 李华
网站建设 2026/10/14 18:11:05

扣子智能体部署源码包:配置、踩坑与验证全攻略

简介&#xff1a;这是一份基于扣子&#xff08;Coze&#xff09;平台的智能体快速部署可运行源码包&#xff0c;专为零基础入门AI智能体开发的个人开发者、产品运营及技术爱好者设计。压缩包内共3个文件&#xff0c;主要包括HTML交互页面、inscode代码配置及Git忽略文件&#x…

作者头像 李华
网站建设 2026/10/17 11:41:50

无需VPS!Windows本地部署OpenClaw+Ollama,用RTX显卡跑AI Agent

身边不少朋友看到“AI Agent”四个字&#xff0c;第一反应就是得租一台服务器&#xff0c;然后装Linux、配环境、挂代理、配安全组……一套下来&#xff0c;还没开始写功能&#xff0c;人先被服务器折腾麻了。OpenClaw这个开源Agent框架&#xff0c;我最初也差点走上“租VPS”这…

作者头像 李华