☰
宠物商城全栈项目实战:SpringBoot+Vue3+MySQL从设计到部署
2026/10/6 12:55:08 网站建设 项目流程

我自己前后带了几年应届生,也评审过不少毕业设计和技术分享,一个很深的感受是:很多人"学过"SpringBoot、Vue、MySQL,但真正把这三条线串起来做一个完整的全栈项目时,还是会卡在工程落地这一层。比如数据库表怎么设计才不返工,商品库存和订单扣减怎么保证一致性,Vue3的组合式API到底怎么组织代码才不乱——这些都不是靠背八股能解决的。所以看到这个"宠物商城"项目,我反而觉得它踩的点特别准:业务规模不大不小,正好能把主流技术栈完整走一遍,又不会像电商巨头那样复杂到劝退初学者。

这个项目的技术选型也很贴近当前企业的主流配置:SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0。没有盲目追新上SpringBoot3和JDK17,也没有用那些已经半退休的老框架,属于那种"学完就能在工作里用"的组合。另外它带了文档,这意味着你可以把它当成一个标准模板,不只是跑起来看效果,而是真正读代码、看设计、理解每个模块为什么这么写。

这篇文章我不打算写成一笔一划的源码逐行解读,而是从项目整体设计的角度,把宠物商城这套系统拆开来讲:技术栈为什么这么选、数据库怎么建模、核心业务(商品、购物车、订单、登录鉴权)在前后端分离下怎么落地、开发过程中最常见的坑和排查方法,以及怎么把它顺利跑起来甚至部署上线。无论你是拿它当毕业设计参考,还是想作为全栈入门练手项目,又或是要快速搭一个电商类系统的原型,这篇文章的内容都能让你少走不少弯路。

1. 技术栈选型:为什么是这套组合,而不是别的

先聊技术选型。很多初学者拿到一个项目会习惯性地问"用了什么技术",但很少有人问"为什么是这几个技术组合在一起"。这一步想明白了,后面编码才不会只是抄代码。

1.1 SpringBoot2、Vue3、MyBatis-Plus、MySQL8.0各自的定位

先看后端。SpringBoot2虽然在2022年底之后进入维护期,但目前企业存量项目和技术资料里它仍然是占比最大的版本,尤其是SpringBoot 2.7.x这一代,既有自动配置的便利,又兼容了Spring Cloud老版本的各种生态。对于学习者和做毕设的同学来说,SpringBoot2意味着资料多、出问题容易搜到答案、各种starter组件选型成熟,踩坑成本低。选它做后端,稳是第一位的。

Vue3这边则是另一番逻辑。Vue3 + Vite + 组合式API(Composition API)已经不是新东西,但直到现在仍然有很多教程在上老式的选项式API(Options API)。这个项目直接用Vue3的组合式API来写,个人认为是更有诚意的选择:一方面组合式API的逻辑复用能力确实比选项式强太多,同一个购物车的逻辑可以抽成useCart()这样的函数到处复用;另一方面<script setup>语法糖极大减少了样板代码,配合Vite的按需编译,开发体验比Vue2那套流畅得多。现在很多后台管理系统(比如若依前后端分离版)也都在往Vue3+TS迁移,提前熟悉这套写法是必要的。

MyBatis-Plus不是一个新框架,但它在中小型项目里的地位很稳。它本质上是对MyBatis的一次增强:内置通用Mapper,单表的增删改查几乎不用写XML,分页插件直接配一个拦截器就行,还有代码生成器可以一键生成entity/mapper/service/controller四层代码。宠物商城这种业务(用户表、商品表、订单表、购物车表)相当符合它的优势场景,几张表的CRUD如果每张都手写XML,既浪费时间也看不出水平。记住,MyBatis-Plus解决的是"常规CRUD效率"问题,而不是"复杂SQL性能"问题,遇到多表关联和复杂聚合查询时,该手写SQL还是得写。

MySQL8.0则是数据库侧的默认答案。8.0相比5.7有几个非常实用的提升:默认字符集utf8mb4,emoji和一些生僻字能直接存;支持窗口函数(ROW_NUMBER、RANK这类),写排名、分组TopN方便得多;新增的WITH公共表表达式也能让复杂查询更清晰。更关键的是,8.0的安装部署和连接配置在Docker、云数据库、Linux服务器上都已经非常成熟,网上随便一搜都是案例,对新手友好。

1.2 这套组合能解决什么问题,又有什么潜在代价

这套组合对应的是一个典型的中小型Web业务系统的黄金配置。你需要快速开发一个带用户登录、商品展示、购物车、订单流程的系统,同时要求代码结构清晰、后续可维护,那么这个组合能让你把主要精力放在业务逻辑而非底层配置上。SpringBoot负责把对象管理、事务、Web配置全部干掉,MyBatis-Plus干掉一半SQL,Vue3+Element Plus干掉前端组件和页面交互,你真正要写的核心代码,是订单状态流转、库存扣减、购物车选中逻辑这些偏业务的东西。

当然,有得必有失。这套组合在某些场景下也有代价,提前知道可以帮你扬长避短:

  • MyBatis-Plus对复杂查询不友好:多表嵌套关联、动态排序、复杂子查询,写XML仍然不可避免,强行用Wrapper拼会非常痛苦。宠物商城的"后台订单筛选"如果有这种需求,建议直接写自定义SQL。
  • SpringBoot2毕竟是旧版本:如果你有学习SpringBoot3虚拟线程、GraalVM原生镜像的需求,或者想熟悉新特性,那这个项目就当基础盘来用,别死守版本。
  • 前后端分离带来了跨域和联调成本:前端和后端是两个工程,开发时要配代理,部署时要用Nginx转发,比JSP时代多了一层复杂度。

这些代价在宠物商城项目里都是可控的,因为业务规模不大,恰好能体验"带病"处理的过程,反而比用几千行的复杂项目练手更容易建立完整认知。

1.3 通用CRUD服务与代码生成器的使用经验

既然标题里特别出现了MyBatis-Plus相关热词,这里多说几句通用CRUD这一块。MyBatis-Plus提供了一套BaseMapper和IService,你自定义的Mapper接口只要继承BaseMapper<T>,就自动拥有了selectById、insert、updateById、deleteById这些方法。但这套机制有个新手容易用偏的地方:过度依赖通用方法,会让所有查询都变成"先拿出来再内存过滤",一旦数据量上来就完蛋。

举个例子。宠物商城的商品列表页经常要按分类、价格区间、上下架状态筛选,还要分页。如果你用list()把所有商品查出来再在Java里过滤,那商品几千条时页面就开始卡了。正确的做法是用MyBatis-Plus的LambdaQueryWrapper构造带WHERE条件的查询,让它生成SQL去数据库过滤,再用分页插件来处理LIMIT。我见过太多人把Wrapper只当"拼条件"的工具,忽略了它本质上还是会在数据库端执行的——只要条件字段有索引,性能不会有问题。

代码生成器也值得提。MyBatis-Plus的AutoGenerator可以根据数据库表一键生成实体类、Mapper、Service、Controller。很多新手生成完就直接用,结果Controller里全是薄薄的CRUD空壳,Service里也没业务逻辑,这种代码经不起推敲。我的建议是:生成器只用来生成entity和mapper层,Service和Controller最好手写,因为业务方法(比如"下单"这个动作涉及库存检查、订单创建、购物车清空三个步骤)没有任何代码生成器能帮你生成。用生成器图快,但业务方法必须自己写干净。

2. 宠物商城的整体设计:业务模块、数据库建模与工程结构

技术选型只是骨架,真正决定项目质量的是设计。宠物商城看起来业务简单,但麻雀虽小五脏俱全,该有的电商模块它一个不少。这一节重点讲整体设计思路,这是任何文档里都值得仔细研究的部分。

2.1 业务模块梳理与核心流程识别

拿到一个宠物商城的需求,不要急着写代码,先把业务模块画出来。通常包含这样几块:

  • 用户端:注册登录、个人中心、宠物商品浏览、商品详情、购物车管理、提交订单、订单列表、订单详情、(可选)支付成功后回调。
  • 管理端:商品管理(上下架、改价、改库存)、分类管理、订单管理(发货、取消、退款)、用户管理、(加分项)轮播图或公告管理。

这套业务的核心流程其实只有三条:浏览选品流程、购物车结算流程、订单状态流转流程。把这三条流程吃透,整个系统的数据库表结构和接口设计就清晰了。

以购物车结算流程为例,用户端大致是:加入购物车 -> 选中商品 -> 提交订单 -> 服务器校验库存和价格 -> 生成订单(订单状态变为"待支付") -> 支付成功(状态变"已付款") -> 管理员发货("已发货") -> 用户确认收货("已完成")。这个过程涉及多张表的协同更新,也是后面讲事务处理的重点。

用户浏览商品 → 加入购物车 → 购物车列表(勾选) → 提交订单 → 后端:校验库存 + 锁定库存 + 创建订单 + 清空购物车 → 支付(模拟)→ 订单状态更新 → 管理员发货 → 用户确认收货

这张流程图画完,数据库表怎么设计、Controller需要哪些接口、Service需要哪些事务方法,基本就自然推导出来了。流程驱动设计,而不是表驱动设计,这是做项目规划时最重要、也最容易被忽略的点。

2.2 数据库表结构设计实战要点

基于上面的流程,宠物商城最少需要这些表:

表名核心字段设计要点
user用户表id, username, password, nickname, phone, avatar, create_time密码存的是BCrypt加密后的密文,绝不能明文
category分类表id, name, parent_id, sort_order用parent_id支持两级分类,宠物/用品/食品等
product商品表id, category_id, name, subtitle, main_image, detail, price, stock, statusstatus上下架状态,price用decimal(10,2),图片存URL路径
cart购物车表id, user_id, product_id, quantity, checkeduser_id+product_id建唯一索引,防止重复记录
order订单表id, order_no, user_id, total_amount, status, receiver_name, receiver_phone, receiver_address, create_timeorder_no全局唯一,状态字段用tinyint存数字,对应关系写在常量类里
order_item订单明细表id, order_id, product_id, product_name, product_image, price, quantity快照字段很重要,订单生成后商品改名改价不影响历史订单
address收货地址表id, user_id, receiver_name, receiver_phone, address_detail, is_default一个用户可以多个地址,仅默认地址唯一

这里有两个设计细节值得展开。第一,订单明细表必须做商品快照,把下单那一刻的商品名、图片、价格原样复制进来。否则商家改了商品价格,用户的历史订单显示也会跟着变,这在电商里是绝对不允许的。第二,金额字段一律用decimal,禁止用float和double。二进制浮点数无法精确表示0.1这种十进制小数,订单金额累计、结算时一分钱差异就很麻烦。MySQL8.0的decimal在计算上完全够用,别贪那点存储空间。

2.3 项目工程结构与后端分层规范

工程结构决定代码可维护性,宠物商城项目的后端建议按功能模块分包,而不是按层分包。什么意思?按层分包是controller包下放所有Controller、service包下放所有Service,而按功能分包则是module/user下面放UserController、UserService、UserServiceImpl、UserMapper、User实体类。

按功能分包的好处在于:改一个功能时,所有相关文件都在同一个包里,不用来回跳目录。这种结构在SpringBoot项目里非常流行,尤其是业务模块多时(用户、商品、订单、购物车各占一个包)。后端分层的大致模式是:

controller —— 只做参数接收、调用service、返回结果,不写业务逻辑 service —— 业务逻辑层,事务都在这层处理 mapper —— 数据访问层,写SQL和MyBatis-Plus接口 entity —— 实体类,对应数据库表结构,字段和表字段一一对应 dto/vo —— 数据传输层,controller层输入输出用,不直接暴露entity

前端Vue3工程的结构同样有讲究,一般按views(页面)、components(通用组件)、api(接口请求封装)、utils(工具函数)、router(路由配置)来划分。api目录下建议一个模块一个文件,比如api/user.js里只放登录注册相关的请求方法,这样页面里调用时一目了然,后端改接口也只要找到对应文件改一处。前后端分离项目的良心,一半体现在接口文档,另一半体现在前端API目录的组织方式上。

2.4 为什么这个项目的文档值得重视

标题里特意标了【含文档】,这一点不要忽视。很多开源项目源码开放,但文档要么缺失、要么只写"如何启动"一句带过。这个项目既然带了文档,大概会包含环境搭建、数据库初始化脚本、接口说明、可能的部署指南。我个人的建议是:拿到项目后,先花一晚上精读文档,启动一遍项目,把文档描述的和实际代码对一遍。这一步看似浪费时间,实际上是把整个项目从"黑盒"变成"白盒"最快的方法,做完之后再读代码,效率完全不一样。

一份好的项目文档至少应该回答:JDK、Maven、Node.js版本要求是什么,如何创建数据库并导入初始化脚本,后端如何配置数据源和Redis(如果有),前端如何安装依赖和配置代理,默认账号和密码是什么,各模块的接口大致是干什么的。对照这些信息快速跑通项目,你一上来就有了"这个系统能做什么"的整体感知,后面看细节代码时才不会迷路。

3. 核心功能实现细节:从登录鉴权到订单流转

这一节是全文的重头戏,我把宠物商城里技术含量最高的几个模块逐一拆开讲。如果你正准备拿这个项目做二次开发或者面试讲解,这些内容可以直接当素材讲。

3.1 用户登录与JWT鉴权机制解析

用户模块表面上只是注册和登录,但背后是前后端分离下最核心的鉴权问题。传统的Session方式在前后端分离场景下不太方便(跨域要处理Cookie、分布式环境要共享Session),所以现在的主流方案是JWT(JSON Web Token)。JWT本质上是一段携带用户信息的加密字符串,服务器签发后发给前端,前端在后续请求的Header里带着它,服务器验证签名通过就认为请求合法。

登录接口的处理流程一般是:

  1. 前端提交用户名+密码,后端先按用户名查用户,拿不到直接返回"用户名不存在"。
  2. 拿到用户后用BCryptPasswordEncoder.matches()校验密码,密码是注册时BCrypt加密的,所以这里比对的是密文,不是明文。
  3. 校验通过后,用JWT工具类生成token,把用户id、用户名、过期时间塞进token里。
  4. 返回给前端{ token, userInfo },前端把token存到localStorage或Pinia里。
  5. 前端在axios请求拦截器里统一加上Authorization: Bearer <token>头。
  6. 后端用一个拦截器/过滤器统一校验token,校验通过就把用户信息放进ThreadLocal或RequestContext,方便后续业务代码直接取当前用户。

宠物商城这类系统的鉴权还有一个加分项:用户角色区分。普通用户和管理员在同一个用户表里,可以用role字段区分。登录成功后前端根据角色决定显示用户端还是管理端页面,后端则用Spring Security或拦截器保护管理端接口。用SpringBoot2自带的Spring Security做起来会有一点配置学习成本,但胜在专业,且不用自己造轮子。

提示:JWT有个常见问题是无法主动让token失效,因为token本身是无状态的。如果项目有"修改密码后强制下线"这类需求,简单做法是把token版本号也写进JWT里,改密码时让版本号+1,老token自然失效。这是我的实战经验,许多入门项目不会处理这个细节,面试时讲出来却是个亮点。

3.2 商品与分类模块:图片存储和列表分页的两种思路

商品模块是商城门面,它的核心难点不在CRUD,而在商品图片的存储方案和列表分页查询的性能。

图片存储有几种常见选择:本地磁盘存储、云存储OSS、Base64直接存库。宠物商城项目一般用前两者。本地磁盘存储的做法是:后端配置一个可访问的静态资源映射路径(比如/images/**映射到服务器磁盘某个目录),上传的图片保存到该目录,数据库里存的是相对路径或完整URL。如果是云存储OSS(比如阿里云OSS或腾讯云COS),则是前端直传或后端转传,数据库存URL,这种方式生产环境更常用,因为无需担心磁盘扩容和图片访问并发问题。

分页查询则是另一个重点。用MyBatis-Plus时,只需配置一个分页插件拦截器,然后Service层调用page(new Page<>(current, size), wrapper)就能拿到分页结果。但注意以下几个容易出问题的地方:

  • 分页参数校验:页码不能小于1,每页大小别设置成能一次查1万条,前端传递的current和size要先用Math.max做兜底。
  • 条件构造别把筛选字段拼错:商品列表常见筛选条件有category_id、status、价格区间、关键词模糊搜索,用LambdaQueryWrapper的eq和like即可,但关键词搜索字段需要确认是匹配name还是subtitle,别两张表字段混着写。
  • 分页结果处理:查出来的实体类可能带了不必要的字段(比如商品详情detail很长),如果要返回给前端列表页,建议用VO对象只保留列表页需要的字段,去掉大文本字段能明显减少网络传输。

商品模块还有一个"隐藏关卡"是库存管理。商品表里的stock字段不是摆设,商品详情页要显示剩余库存,购物车结算时要检查库存够不够,下单成功后要扣减库存。这些场景涉及并发控制,我在下一节展开讲。

3.3 购物车与订单模块的事务一致性处理

购物车模块本身不复杂——无非是增删改查加一个"选中/取消选中"状态。真正考验功底的是从购物车创建订单这一步,因为它涉及多张表的同步更新,必须保证要么全部成功,要么全部失败,这就是数据库事务的应用场景。

下单的核心代码逻辑大致是:

  1. 接收请求参数:收货地址id、订单里的商品id列表(从购物车选中的记录里拿)。
  2. 查询用户选中的购物车记录,逐个商品检查:商品是否存在、是否上架、库存是否足够。
  3. 计算订单总金额(这里必须用数据库里的商品价格,而不是前端传过来的价格,防止被篡改)。
  4. 扣减库存:UPDATE product SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity}。这行SQL是关键,在数据库层面做条件更新可以避免并发超卖。
  5. 插入订单主表记录,生成唯一订单号。
  6. 插入订单明细表(多行)记录,保存商品快照。
  7. 清空已购买的购物车记录。

这7步必须放在同一个@Transactional方法里。SpringBoot的事务管理很容易用,但也经常有人踩坑,常见的有三种:

  • 事务不生效:方法被private修饰、同类内部调用、类未被Spring管理,这些都会让@Transactional静默失效。我的经验是先确认调用入口是Controller -> Service公有方法,且代理生效。
  • 事务回滚不彻底:默认只有RuntimeException才触发回滚,如果你在业务里抛了自己定义的Exception子类(非运行时异常),记得在注解里指定rollbackFor = Exception.class。
  • 长事务问题:事务范围越大、耗时越长,数据库连接占得越久。下单这个操作里不要做远程调用(比如发短信、调支付接口),尽量只做本地数据库操作,日志和通知放到事务提交后再做。

库存扣减的UPDATE ... WHERE stock >= #{quantity}写法,是个特别值得说的细节。很多初学者写的时候会先查库存,然后在Java里判断,再执行更新。这种"先查后改"的方式在高并发下会出问题——两个请求同时查到了库存8,都判断"够扣",然后都去扣减,结果库存变成了负数或扣超了。把判断条件放进WHERE里,让数据库的锁和原子性来保证一致性,才是正解。如果是秒杀级的高并发场景,还可以用乐观锁(版本号)或Redis预扣库存+异步落库的方式,但宠物商城这种规模用条件更新足够。

3.4 接口设计与统一返回结果封装

做前后端分离项目,统一接口返回格式是工程规范里的第一条。如果没有统一格式,有的接口返回{success: true, data: ...},有的返回{code: 200, data: ...},前端写axios响应拦截器时就会疯掉。

宠物商城这类项目通常约定一个通用返回体:

{ "code": 200, "message": "操作成功", "data": { } }

code=200表示成功,非200表示业务失败,比如401未登录、403无权限、500服务器异常、1001库存不足等。前端在axios响应拦截器里统一判断code,非200则弹出ElMessage提示。后端实现上通常是写一个Result类(泛型类),Controller的方法统一返回Result<T>,Service层抛业务异常时由全局异常处理器@RestControllerAdvice统一捕获并转成对应code的返回体。这样业务代码里就不需要到处写try-catch了。

控制器层接口的URL设计也有套路。商品模块是公有的可以匿名访问,但购物车和订单模块都需要登录,所以接口上要分清楚。常见的URL风格是RESTful的:

GET /user/info 获取当前登录用户信息 POST /user/login 登录 POST /user/register 注册 GET /product/list?categoryId= 商品列表 GET /product/{id} 商品详情 POST /cart/add 加入购物车 GET /cart/list 购物车列表 POST /order/create 创建订单 GET /order/list?status= 订单列表(按状态筛选) GET /order/detail/{orderNo} 订单详情

加上适当的中文注释或Swagger(Knife4j)注解,接口文档就有了。我在看项目源码时,最关注的就是:接口是否统一返回Result<T>,是否对参数做基本校验,异常是否被全局捕获。这三个点决定了代码是不是可直接用于生产的"干净工程",而不是只能本地跑的"玩具代码"。

3.5 Vue3前端页面与状态管理的组织方式

前端的价值在于把后端接口变成用户能实际操作的界面。宠物商城的前端整体规模不大,但涉及页面不少:首页、商品列表、商品详情、购物车、订单确认、订单列表、个人中心、管理后台。用Vue3来组织这些页面,有几个关键技术点值得关注。

路由设计:前端路由按模块划分,通常有layout布局组件(顶部导航+侧边栏+内容区),然后子路由挂在不同页面组件上。管理端和用户端可以用不同的布局,也可以共用一个布局但根据角色动态生成菜单。路由守卫router.beforeEach里检查本地有没有token,没有就跳转登录页。

状态管理:这个项目的状态管理工具大概率是Pinia(Vue3的官方推荐)。购物车数据、登录用户信息、订单状态这些全局共享的数据建议放在Pinia里。特别是购物车,如果放在组件内部用ref存,切换路由后数据会丢失,每次进入页面都要重新请求后端接口,体验很差。合理做法是:登录后请求一次购物车数据放入Pinia,增删改时同步更新Pinia和后端,这样页面跳转不会反复加载。

组合式API的组织:以商品详情页为例,可以把"获取商品详情"、"加入购物车"、"收藏"这几个操作抽成useProductDetail()组合函数,里面返回响应式数据和方法。这种抽象既让页面组件保持简洁,也能在多处复用。很多人写Vue3还是把所有逻辑堆在setup里,一眼看过去几百行全是变量和方法定义,这其实是把Vue2的methods写法平移过来了,没有真正理解组合式API的"按功能组织"哲学。

UI组件库:Vue3项目里最常用的还是Element Plus,它的表单、表格、弹窗、消息提示组件很成熟。宠物商城后台管理页面基本就是Element Plus的常规用法:el-table展示商品列表、el-form做新增编辑表单、el-dialog做弹窗、el-pagination做分页、el-upload做图片上传。这些组件的使用门槛很低,但实际开发中“表单校验规则”和“表格分页回显”仍是报错高发区,后面排查章节一并讲。

4. 开发实战:从初始化到跑通全流程的操作记录

前面讲了很多设计层面的东西,这一节回到动手环节,把从零到一跑通这个项目的完整过程梳理一遍,每个阶段该注意什么,我都标出来。

4.1 环境准备与项目初始化检查清单

拿到带源码和文档的项目,第一步不是急着启动,而是按顺序检查环境,避免装到一半发现版本对不上。

环境项推荐配置踩坑提醒
JDKJDK 8 或 JDK 11SpringBoot2一般用JDK8足够,JDK17也能跑但个别旧依赖可能报警
Maven3.6.x以上下载依赖慢的配置阿里云镜像
Node.js16.x或18.xVue3+Vite需要Node14+,但18.x最稳
MySQL8.0.x注意数据库连接URL要显式加useSSL=false&serverTimezone=Asia/Shanghai
IDEIDEA(后端)+ VS Code(前端)后端用IDEA社区版够用,前端VS Code装Volar插件

数据库初始化这一步尤其容易出问题。项目文档里一般会带sql/xxx.sql文件,用Navicat或命令行导入前,先确认连接的字符集是utf8mb4。MySQL8.0默认字符集已经是utf8mb4了,但如果你的库创建得早,可能还是latin1或者utf8mb3,导入中文数据会乱码。命令行的保险操作是:

mysql -u root -p --default-character-set=utf8mb4 source /path/to/pet_shop.sql;

导入完成后,用SHOW TABLES;确认表都建出来了,再随便SELECT * FROM user;看一眼中文显示是否正常。这块没问题,才继续往后端工程里配置数据源。

4.2 后端工程启动与常见配置项解读

后端工程一般是标准Maven结构,src/main/java和src/main/resources。启动前必改的配置文件是application.yml(或application.properties),重点看这几项:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pet_shop?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4 username: root password: 你的密码 redis: # 如果项目用了Redis,这里的host和port也要核对 mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

配置里最容易忽略的是serverTimezone。MySQL8.0驱动的时区校验很严格,不配或配错可能直接报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。看到这种乱码报错别慌,就是时区问题,把URL改成serverTimezone=Asia/Shanghai即可。

另外注意MyBatis-Plus的logic-delete-field配置。如果项目里用了逻辑删除(即删除不是真实DELETE,而是把deleted字段置1),那所有Mapper方法都会被自动拼接WHERE deleted=0条件。这个特性很方便,但如果你在XML里手写了SQL,一定要记得在条件里也加deleted=0,否则会出现"逻辑删掉的记录被手写SQL查出来"的诡异bug。

启动后端时,控制台会打印SpringBoot的启动banner和端口信息。建议启动后先curl http://localhost:8080/product/list(或项目里定义的公开接口)确认接口能通,再去看前端。

4.3 前端工程启动与代理配置

前端工程启动相对简单,进入前端目录后:

npm install npm run dev

npm install时有两个高频问题:一是网络原因导致安装超时,可以切换npm config set registry https://registry.npmmirror.com使用镜像源;二是依赖版本冲突(比如Vite和Node版本不匹配),解决方法是删除node_modules和package-lock.json后重新安装。

前端开发服务器默认跑在localhost:5173(Vite)或localhost:8081,而后端在8080,两者端口不同,必然遇到跨域。Vite解决跨域的官方推荐方式是配置server.proxy代理,在vite.config.js里写:

import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

这段配置的含义是:前端页面里所有以/api开头的请求,都会被Vite开发服务器转发到后端8080端口。于是前端代码里请求URL写成/api/product/list就能访问到http://localhost:8080/product/list。这是开发环境的标准玩法,不要在后端代码里写一个"允许所有来源跨域"的配置来解决开发问题,那到生产环境会变成安全隐患。

4.4 Docker部署MySQL8.0与Linux环境注意事项

本地开发用Navicat连MySQL非常方便,但如果你在Windows上装原生MySQL遇到各种权限和字符集问题(这太常见了),我推荐改用Docker。Docker装MySQL8.0极其简单,几步就能起来一个干净环境:

docker pull mysql:8.0 docker run -d \ --name pet-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=你的密码 \ -e TZ=Asia/Shanghai \ -v /mydata/mysql/data:/var/lib/mysql \ mysql:8.0

启动后用docker exec -it pet-mysql bash进入容器,再用mysql -uroot -p登录执行导入SQL脚本。这个方案的优点是环境隔离、卸载干净、不会污染宿主机,适合装过MySQL但搞得一团糟的情况。生产环境或Linux服务器上反过来操作即可——优先在宿主机直接用apt/yum装MySQL8.0或同样用Docker Compose管理,后者更容易迁移和备份。

如果你真要部署到服务器上,我补充几个部署经验:

  • 后端打包:mvn clean package -Dmaven.test.skip=true生成JAR包,用java -jar pet-shop-0.0.1-SNAPSHOT.jar运行,或者用nohup/systemd当守护进程。
  • 前端打包:npm run build生成dist目录,里面是纯静态文件,用Nginx托管。
  • Nginx配置:监听80端口,location /指向dist目录,location /api/反向代理到后端8080,如果前后端不采用统一前缀风格,就按实际路由分别配置。这样开发环境的代理逻辑就完美移植到了生产环境。

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

跑项目的过程中一定会遇到各式各样的报错和奇怪现象。这一节把我整理的排查经验按模块写出来,基本覆盖全栈项目的各个角落,你可以当作速查手册。

5.1 数据库连接与编码类问题速查

连接报错:Access denied for user 'root'@'localhost',多半是账号密码配错或MySQL8.0的认证插件问题。MySQL8.0默认的caching_sha2_password插件对老版本驱动不支持,一旦代码里用的MySQL Connector/J版本过旧(5.x),就会报Unable to load authentication plugin。解决方法是升级驱动为mysql-connector-java8.x,或者在MySQL里执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码';改成兼容认证方式。

中文乱码:前端提交中文到后端乱码,或后端返回中文到前端乱码,优先检查三处——数据库连接URL是否带characterEncoding=utf8mb4,前端页面是否设了UTF-8(Vite项目默认就是),数据库表字段的字符集是否为utf8mb4。三处全对,乱码问题基本绝迹。

启动时MySQL连接超时:有些老项目会配spring.datasource.hikari.connection-timeout过短,MySQL启动慢或网络波动就连不上,可以把超时调大,或者确认MySQL服务确实起来了。用docker ps看容器状态,用ss -lnt看3306是否监听,都是有效排查手段。

5.2 MyBatis-Plus使用中的几个高频翻车点

Wrapper条件失效:写LambdaQueryWrapper链式条件时,eq、like的字段名没有用Lambda表达式而是用字符串(QueryWrapper),一旦数据库字段改名或实体字段和表字段映射错位,就会报"Unknown column"。建议统一用lambdaQuery()或LambdaQueryWrapper的写法,编译期就能发现字段是否存在。

更新null值不生效:MyBatis-Plus的updateById默认会忽略实体里的null字段,即"想置空某个字段时,字段为null它不会生成对应的SET语句"。如果你确实需要把字段更新为NULL,要用UpdateWrapper的set(column, null),或者实体字段上加@TableField(updateStrategy = FieldStrategy.IGNORED)。这个坑在"管理员把商品下架理由清空"这种场景特别容易遇到。

分页插件不生效:配置了PaginationInnerInterceptor但page查询还是返回所有数据,检查你的MyBatis-Plus版本和SpringBoot版本是否兼容。低版本MyBatis-Plus对SpringBoot2.7的支持有坑,升级到mybatis-plus-boot-starter最新3.5.x版本基本能解决。

逻辑删除和唯一索引冲突:逻辑删除的deleted字段如果参与唯一索引(比如user_id和product_id在购物车表建了唯一索引),删除后再次插入相同记录会因唯一索引冲突而报错。解决方案是把deleted设计成0/1/删除时间戳——逻辑删除时写入当前时间戳,而不是固定置1,唯一索引自然不冲突。这也是很多实战项目惯用的办法。

5.3 前端Vue3+Element Plus的常见疑难

数据不更新:Vue3的响应式系统对reactive包裹的对象有限制,直接通过索引替换数组元素或增减对象属性可能不会触发更新。这时候有两个方案:一是用ref代替reactive来管理数组,二是用Array.prototype.splice或直接给ref.value重新赋值。我见过很多新人纠结"页面为什么不刷新",其实就是响应式依赖收集的问题,搞懂ref和reactive的边界场景能省不少调试时间。

跨域问题:如果前端请求报了Access-Control-Allow-Origin相关的错误,先检查Vite的proxy配置是否正确,再检查是不是用了fetch/axios直接请求绝对地址。记住开发环境优先用代理,不要在后端代码里靠@CrossOrigin("*")关闭所有跨域限制,那等于把生产环境的安全防线提前拆掉。

Element Plus按需引入带来的问题:很多Vue3项目用unplugin-vue-components做组件的按需自动引入,好处是打包体积小,坏处是一旦某个组件的样式依赖了其他组件的全局样式(比如el-select的下拉面板依赖el-popper),可能样式丢失。遇到类似问题,检查是否需要在main.js里额外引入element-plus/dist/index.css,或手动引入相关组件。这一点网上资料不少,留个心眼即可。

el-table分页和请求参数不同步:表格翻页后请求参数还是旧的条件,这是因为el-pagination的current-change事件没有和查询条件合并。处理方式是始终用同一个queryParams对象存页号、页码和筛选条件,翻页、筛选时都统一调getList()方法。听起来像是基础功,但实际项目里这类"条件参数和分页参数打架"的问题是出现频率最高的。

5.4 跨模块问题:部署和后端日志排查技巧

后端排查故障最依赖的就是日志。SpringBoot默认日志输出到控制台,但部署到服务器后,建议配置logback将日志写入文件,比如logs/pet-shop.log。如果接口返回500,先看日志定位是空指针、SQL异常还是业务异常;如果是SQL语法错误,日志里通常会打印出完整的SQL语句,拿这条SQL去Navicat里执行验证,比纯看代码效率高得多。

接口排查的另一个思路是直接用curl或Postman测接口,先绕开前端。这样做能快速判断问题出在后端还是前端。比如商品列表接口500了,用Postman带同样的参数请求,如果后端报错,就是后端代码问题;如果后端返回正常,那就要去看前端页面的请求参数有没有传错。模块化排查,是真正的全栈排错方法论。

6. 项目的扩展空间与二次开发建议

到这里,整套宠物商城系统的核心内容基本讲完了。最后我想聊聊这个项目的"天花板"和扩展方向。很多人拿到一套源码,跑通之后就开始写论文或做演示,再往前就不动了。但实际上这个项目非常适合再接几个模块,锻炼自己独立设计完整功能的能力。

6.1 值得做的扩展方向(从易到难)

如果我是你的话,我会按这个顺序加功能:

  • 收藏功能:用户收藏商品,新增favorite表(user_id + product_id),加两个接口(收藏/取消、列表),页面加一个心形按钮。这个功能完全建立在已有代码模式上,1天就能改完。
  • 商品搜索:用MySQLLIKE实现关键词搜索(针对商品名和副标题),再加一个搜索页,一般来说2天搞定。如果想更有挑战,可以尝试接入全文索引或Elasticsearch,不过宠物商城数据量下有点杀鸡用牛刀。
  • 订单自动取消:用户下单后如果N分钟未支付,自动取消订单并恢复库存。用SpringBoot的@Scheduled定时任务扫一遍超时订单即可,注意扫的时候要处理并发情况(防止和用户手动支付同时操作)。
  • 秒杀或限时折扣:在商品表加discount_price和discount_start/end字段,下单时判断是否在折扣期内,价格用折扣价。这一步能练到条件判断的边界设计,也是个不错的面试讲点。
  • 后台数据统计:用MySQL8.0的聚合函数和窗口函数统计每日订单量、销售额Top10商品等。结合ECharts做一个简单的数据大屏,也是展示能力的亮点。

建议不要一上来就上重量级的分布式方案和消息队列,那会喧宾夺主,把"宠物商城"做成"秒杀中间件演示项目",反而失去了业务完成的连贯性。

6.2 把项目变成简历和面试加分项的建议

如果你打算把这个项目写进简历或用于面试讲解,我有个诚恳的建议:不要只把它描述成"一个宠物商城系统",而要说清楚你遇到的挑战和怎么解决的。面试官最感兴趣的不是CRUD的完成度,而是几个经典问题的答法:

  • 库存扣减为什么用UPDATE ... WHERE stock >= #{quantity}?如何避免超卖?(把条件更新和乐观锁说清楚)
  • 订单模块怎么保证多表数据一致性?(把事务、回滚、传播行为说清楚)
  • 图片上传如何设计,本地存储和云存储的取舍是什么?
  • 前后端分离跨域问题怎么在开发和部署两个阶段分别解决?
  • JWT鉴权为什么比Session适合前后端分离,它有什么缺点?

这些问题我在正文里都做了详细展开,你把这个项目真正吃透后,可以挑其中两三个讲出自己的实操体会,远比背八股文有说服力。简历上也可以直接在项目描述里点出这些技术难点,效果比罗列框架清单好得多。

7. 写在最后的一些实在话

我在实际帮人调试这类项目时,最大的体会是:全栈项目的难点不在于某个单独的框架,而在于把框架之间的缝隙填平——后端接口怎么返回前端才方便处理、数据库表结构怎么设计才能少改两次需求、事务边界划到哪里才既安全又不拖垮性能。这套宠物商城项目恰好把这些问题都暴露了一遍,我认为它适合沉下心来做,而不是跑完就丢。

如果你拿到了源码,我建议你至少做三件事:第一,不看任何教程的情况下,把数据库的ER图自己画一遍,对照表结构和代码核对;第二,找到下单这个核心业务方法,把它的调用链路从Controller到Mapper完整走读一遍,看懂事务和库存扣减怎么配合;第三,自己动手改一个功能(比如加一个"宠物疫苗接种记录"的新模块),用已有代码模式去写新功能。做完这三件事,这个项目对你的价值就能从"跑通"变成"掌握"。

最后再分享一个小技巧:在你把项目跑起来、准备做二次开发或写总结文档时,给代码里的关键业务方法加中文注释,哪怕只是两三句,比如"这里用条件更新防超卖"、"订单明细存快照,防止商品信息变化影响历史订单"。理由很简单——过两个星期你回头看自己写的代码,最能帮助回忆的不是类名和方法名,而是当时为什么要这么写。注释是写给未来的自己看的,这个习惯越早养成收益越大。

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

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

立即咨询