宠物商城系统实战:SpringBoot+Vue前后端分离开发与部署全解析

发布时间:2026/10/8 2:27:35
宠物商城系统实战:SpringBoot+Vue前后端分离开发与部署全解析 宠物用品交易网站听起来是个很“传统”的练手项目但把商品、购物车、订单、用户、后台管理这一整套流程用 SpringBoot Vue MyBatis MySQL 跑通你会发现里面全是前后端分离项目实战的经典知识点。这个项目我前后搭了三遍第一遍败在版本搭配第二遍死在联调跨域第三遍才把整套体系摸顺。所以这篇文章我把完整源码和部署教程结合起来写从架构设计、环境准备、模块实现到上线部署每一步都讲清楚“为什么这么做”。适合正在准备毕业设计、想系统入门前后端分离实战或者想拿一套完整电商项目源码快速学习的人直接参考。1. 项目整体架构与设计思路1.1 为什么选前后端分离架构我见过很多同学做交易网站用 JSP Servlet页面和服务端代码强耦合。改个按钮样式要重启 Tomcat管理员和买家页面全挤在一套模板里越改越乱。前后端分离的核心思路是把“数据”和“展示”彻底拆开后端只暴露接口返回 JSON前端用 Vue 接收数据自己渲染 DOM。这样买家端的商品列表、购物车、订单页面和管理员端的商品管理页本质上都是同一个后端接口池里取数据各自独立维护。这个宠物用品交易系统的业务流程本身也天然适合接口化。用户注册登录、首页轮播图拉取、商品分类分页查询、加购下单个个都是标准的 REST 接口POST /user/login、GET /product/list、POST /cart/add、POST /order/create。每个接口只干一件事前端拿到数据后自行处理状态和交互。团队协作层面也更舒服——前端和后端可以并行开发只要先把接口文档约定好两边各写各的最后联调时集中解决跨域和字段对齐问题。1.2 技术栈选型背后的“为什么不”这个项目的后端我选了 SpringBoot MyBatis前端选 Vue数据库用 MySQL。这套组合在 Java 岗位招聘要求里出现频率非常高可以说是“求职标配”。但选型时我慎重做了几个判断。第一Spring Boot 版本一定要克制。网上很多人直接下了最新版 Spring Boot 3.x结果 MyBatis 官方 starter 还没跟上、或者 JDK 必须升到 17、又或者 druid 连接池配置方式变了一堆兼容问题冒出来——这就是“springboot版本太高”这类搜索热的由来。我这里用的是 Spring Boot 2.7.18 JDK 8成熟稳定MyBatis、druid、lombok 全都能平滑兼容。如果你的机器已经装了 JDK 17也可以跑 2.7.x但建议统一 JDK 8省得后面部署时环境不一致。第二持久层为什么选 MyBatis 而不是 Spring Data JPA宠物用品交易涉及多表联查的场景很多比如查订单时需要 join 商品表和订单明细表MyBatis 可以直接写 SQLSQL 怎么写完全由我控制调优也直观。JPA 的自动生成 SQL 虽然写着省事但一旦出现复杂查询生成的 SQL 性能不可预知排查问题还要先翻译 JPQL对新手并不友好。MyBatis 的 mapper 接口 XML 映射文件虽然多写几行但每个 SQL 都明明白白。第三Vue 前端我用的是 Vue 3 Vue Router Pinia。Vue 3 的 Composition API 写业务逻辑时组织性更强Pinia 比 Vuex 少很多样板代码。如果你以前只学过 Vue 2也不用慌本文的组件代码大部分在 Vue 2 里改改写法也能跑。1.3 功能模块划分与数据库表设计先拆功能再建表这是一定要遵守的顺序。站在买家角度系统要有注册登录、浏览商品、按分类搜索、查看商品详情、加入购物车、提交订单、查看我的订单。站在管理员角度系统要有商品分类管理、商品管理上架/下架/编辑、订单状态管理、首页轮播图管理。基于这些功能我设计了 8 张核心表如下表所示表名作用关键字段user用户表id, username, password, phone, avataradmin管理员表id, username, passwordcategory商品分类表id, name, sortproduct商品表id, category_id, name, subtitle, price, stock, main_image, statuscart购物车表id, user_id, product_id, quantity, checkedorders订单主表id, order_no, user_id, total_price, status, create_timeorder_item订单明细表id, order_id, product_id, product_name, product_image, price, quantitybanner轮播图表id, image_url, link_url, sort这里有几个设计要点我在建表时反复斟酌过订单表一定要拆主表和明细表。如果一个订单包含 3 种商品只有明细表一字排开才能准确记录每种商品的快照价格和数量而不是只在主表里存一个总价。product 表的 status 字段用于软上下架不要真的删除商品记录否则历史订单明细就变成“无源之水”。用户密码不要明文存后面我会讲用 BCrypt 加密。2. 环境准备与项目初始化2.1 JDK、Maven、MySQL 的版本搭配与安装先把本地环境准备好。我的建议配置是 JDK 1.88u202 以上、Maven 3.8.x、MySQL 5.7.44开发工具用 IntelliJ IDEA 2022。JDK 安装完后记得配好环境变量 JAVA_HOME 和 PATH。注意不要只配 PATH 不配 JAVA_HOME很多 IDE 和 Maven 启动脚本是靠 JAVA_HOME 来找 JDK 的。Maven 安装后在 conf/settings.xml 里加一个阿里云镜像否则拉依赖就像蜗牛爬mirror idaliyunmaven/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirrorMySQL 安装是很多人头疼的点。Windows 10 上装 MySQL 5.7.44 时我总结了一套最省心的流程先下载 mysql-5.7.44-winx64.zip 免安装版解压到 D:/mysql。然后在 D:/mysql 下新建 my.ini配置基于目录和端口[mysqld] basedirD:/mysql datadirD:/mysql/data port3306 character-set-serverutf8mb4以管理员身份打开 cmd进入 bin 目录后依次执行mysqld --initialize-insecure、mysqld install、net start mysql。初始化用--initialize-insecure表示 root 密码为空启动成功后用mysql -u root -p登录再执行ALTER USER rootlocalhost IDENTIFIED BY 你的密码;设置密码。整套流程大概 5 分钟比安装版省心得多卸载也干净。Linux 服务器上我部署的是 MySQL 8.0。注意 8.0 默认认证插件是 caching_sha2_password项目里的 JDBC 连接串必须带上allowPublicKeyRetrievaltrue否则会报 Public Key Retrieval is not allowed。这个坑我部署时遇到过印象很深。2.2 Vue 环境配置与脚手架项目创建前端环境要求 Node.js 16 和 npm。建议先换 npm 镜像源执行npm config set registry https://registry.npmmirror.com。然后全局安装 Vue CLInpm install -g vue/cli验证版本用vue --version。创建项目时执行vue create pet-front选择 Vue 3 预设。这个创建过程要等一段时间如果你发现卡在 node-sass 之类的位置半天不动大概率是网络问题重新切镜像后再试。项目创建完成后继续安装路由、状态管理和 HTTP 库npm install vue-router4 pinia axios。这里我想专门提一下 Vue Router。系统里路由分两块买家端路由/home、/product/list、/cart、/order管理员端路由/admin/dashboard、/admin/goods。最好在路由配置里加一个前置守卫访问购物车、订单等页面时判断本地是否有 token没有则跳转登录页。这个“路由守卫 token 校验”是前后端分离项目里权限控制最常见的实现方式。2.3 后端工程结构初始化与基础配置后端的 Maven 工程我命名为 pet-server采用标准分包结构com.pet.server ├── common // 统一返回、异常处理、常量 ├── config // 拦截器、跨域配置 ├── controller // 接口层 ├── entity // 数据库实体 ├── mapper // MyBatis 接口 ├── service // 业务逻辑层 └── utils // 工具类JWT、MD5等这种分层的目的很明确controller 只负责接收参数和返回结果不写业务逻辑service 处理业务规则比如下单时要校验库存、计算总价mapper 只做数据读写。我见过很多项目把所有代码堆在 controller 里一个方法几百行后面根本没法维护。application.yml 里核心配置如下server: port: 8088 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pet_shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.pet.server.entity configuration: map-underscore-to-camel-case: true特别注意map-underscore-to-camel-case: true。数据库字段是下划线风格如 create_timeJava 属性是驼峰风格createTime打开这个配置后 MyBatis 会自动映射省去大量 ResultMap 手动映射的样板代码。3. 核心业务模块实现3.1 用户注册、登录与 JWT 认证用户模块是第一个要写的功能因为购物车、订单都依赖用户身份。注册接口的逻辑前端传 username 和 password后端先查重再对密码做 BCrypt 加密然后插入 user 表。BCrypt 是单项 hash 算法每次生成的密文都不同比 MD5 安全得多即使数据库被拖库也无法通过彩虹表反推出原始密码。登录接口是重点。我采用了 JWTJSON Web Token做无状态认证——后端登录成功时生成一个 token 返回给前端前端把 token 存到 localStorage之后每次请求在请求头带Authorization: Bearer token。后端写一个拦截器统一校验 token解析成功就放行否则返回 401。JWT 的优势很明显服务器不保存 session天然适合前后端分离。服务器部署多实例时session 还需要 Redis 做共享JWT 完全不需要。我用的是 jjwt 0.9.1 这个库生成 token 的代码大致是这样String token Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(username, user.getUsername()) .setExpiration(new Date(System.currentTimeMillis() 7*24*3600*1000L)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();token 有效期我设置的是 7 天这样用户在移动端和 Web 端体验都比较舒服。密钥 secretKey 不要写在代码里从配置文件中读取避免泄露。拦截器里需要对登录和注册两个接口做白名单放行其他接口都过校验。前端路由守卫和后端拦截器的双重校验保证了即使有人绕过前端直接调接口没有 token 也拿不到数据。3.2 商品模块与前端商品卡片组件商品模块是整个系统的门面。后端接口有三个分类列表、分类下商品分页查询、商品详情。分页查询这里我用 MyBatis 手动写分页 SQL没有引入 PageHelper 插件原因是这个项目的表数据量并不大LIMIT 直接干就完了少一个依赖少一个坑。核心 SQL 如下select idselectPageByCategoryId resultTypeProduct SELECT p.id, p.name, p.subtitle, p.price, p.main_image, p.stock FROM product p WHERE p.status 1 if testcategoryId ! null AND p.category_id #{categoryId} /if ORDER BY p.id DESC LIMIT #{offset}, #{pageSize} /select前端做一个商品卡片组件 ProductCard.vue用 Vue 3 的 slot插槽来扩展卡片底部预留一个操作插槽买家页放“加入购物车”按钮管理员页放“编辑/上下架”按钮。同一个组件两种用法这就是插槽的典型价值——不需要复制粘贴第二套卡片代码template div classproduct-card img :srcproduct.mainImage :altproduct.name / div classproduct-info h4{{ product.name }}/h4 p classprice{{ product.price }}/p slot nameactions/slot /div /div /template买家页面调用时传入购物车按钮管理员页面传入编辑按钮组件内部零改动。Vue 插槽这个功能很多人写组件时容易忽略但这恰恰是组件复用性高低的关键。3.3 购物车与订单模块实现购物车表是典型的“中间关系表”以 user_id product_id 定位一条记录。加入购物车接口的逻辑要注意幂等性同一用户加同一个商品时如果购物车已有记录就 update quantity数量1而不是 insert 一条新记录。否则用户手滑点了两次加购购物车里会出现两行同一个商品。下单流程是整个系统业务最重的部分我详细说一下。前端从购物车勾选记录提交订单传一个 cartIds 数组后端查询这些购物车记录join 出商品实时价格和库存然后循环校验库存是否充足只要有一个商品库存不足整个下单事务回滚接着扣减库存计算总价插入订单主表和明细表最后删除对应的购物车记录返回订单号给前端。这个流程必须加Transactional注解。我最初写的时候漏了这个注解测试时并发下单导致库存变成负数后来补上才发现事务的重要性。Spring 的声明式事务默认遇到 RuntimeException 才会回滚所以 service 里校验失败时要抛出 RuntimeException 或自定义业务异常继承 RuntimeException而不是返回一个错误对象就走人。订单创建完成后我把订单号 order_no 设计成“日期 随机数”格式比如 202506011530120001这样既避免自增 ID 暴露订单量也方便人眼识别。3.4 MyBatis 数据访问与缓存实战MyBatis 这块我想单独拿出来说因为面试基本必问。它的工作机制是SqlSessionFactory 根据 XML 接口生成 mapper 代理对象调用方法时执行对应 SQL结果通过 ResultMap 或驼峰映射转成实体类。面试常问的缓存机制这里也很有说头。一级缓存是 SqlSession 级别的缓存默认开启。同一个 SqlSession 内执行两次相同的查询第二次直接走缓存。但 Spring 管理下每次 Mapper 操作通常是独立 SqlSession所以一级缓存容易被忽略。二级缓存是 Mapper 级别的跨 SqlSession 共享需要在 mapper XML 里显式开启cache evictionLRU flushInterval600000 size512 readOnlytrue/这里我要提醒一句二级缓存别随便开。如果你的项目里存在通过多表 join 查询的 SQL缓存了结果之后其中一张表的数据更新另外一张表的缓存不会被自动清空就会产生脏数据。宠物用品这种商品库存频繁变动的业务开二级缓存反而容易翻车。我最终只在商品分类表这种极少变更的查询上开了二级缓存商品和库存一律不走二级缓存。MyBatis 官方面试题里还有一个高频点#{}和${}的区别。#{}会生成 PreparedStatement 占位符能防 SQL 注入${}是字符串拼接直接嵌入 SQL有注入风险。项目里所有查询条件我都用#{}只有在 ORDER BY 的排序字段这种不能走占位符的场景才用${}而且使用时必须做白名单校验不能直接透传用户输入。4. 前后端联调与部署上线4.1 跨域配置与 Axios 请求封装前后端分离开发中最经典的问题就是跨域。我在 IDEA 里跑后端 8088 端口Vue 开发服务器默认 8080 端口前后端端口不同浏览器就会拦截跨域请求。解决方案有两种一种是后端加跨域配置用 Spring Boot 的 WebMvcConfigurer 实现全局跨域Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }另一种是前端用 Vue CLI 的 devServer 代理在 vue.config.js 里配module.exports { devServer: { proxy: { /api: { target: http://localhost:8088, changeOrigin: true } } } }开发阶段我建议用代理方式这样前端代码里统一请求/api/...部署后由 Nginx 反代到后端改动最小。生产环境一定不要开启后端全放行的跨域会降低安全性。Axios 请求封装这块我建了一个 request.js统一做了四件事设置 baseURL、请求头带 token、响应拦截器统一处理业务码、401 时跳转登录页。核心代码service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) service.interceptors.response.use( res res.data, err { if (err.response err.response.status 401) { router.push(/login) } return Promise.reject(err) } )统一封装的好处是业务代码里不用每个接口都写一遍“取 token、塞 header、处理错误”后期维护成本低很多。4.2 前端打包与后端启动本地部署本地部署是最快的验证方式。后端在 IDEA 里直接运行 main 方法即可启动应用会监听 8088 端口。前端执行npm run build打包产物在 dist 目录。如果不想用代理需要 Nginx 做静态资源托管和接口反向代理。一个精简版 Nginx 配置如下server { listen 80; server_name localhost; root /home/pet/pet-web/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8088; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这个配置里有几个容易被忽略的细节。try_files $uri $uri/ /index.html;是 Vue Router 历史模式的标配否则刷新 /cart 页面时会 404——因为 Nginx 在磁盘上找不到 /cart 这个文件。location /api/的 proxy_pass 后面的 URL 不要再带 /api 前缀否则会变成 /api/api/xxx这是新手常犯的错误。4.3 服务器上线部署与数据库初始化服务器部署我的流程是这样先装 JDK 8、MySQL 8.0、Nginx。然后 MySQL 里创建 pet_shop 数据库导入项目附带的 pet_shop.sql 脚本。执行前先确认字符集是 utf8mb4否则商品描述里的 emoji 会变成问号。后端打 jar 包用mvn clean package -DskipTests然后通过 systemd 服务方式启动而不是直接 nohup 裸跑。用 systemd 的好处是崩溃自动重启、开机自启动、日志统一管理。一个最小 unit 文件如下[Unit] Descriptionpet-server Afternetwork.target [Service] ExecStart/usr/bin/java -jar /home/pet/pet-server.jar Restartalways Userroot [Install] WantedBymulti-user.target前端构建产物上传到 /home/pet/pet-web/distNginx 按上面的配置指过去。整个过程我建议先本地跑通再上服务器。不要直接在服务器上联调排错那样效率太低了。4.4 常见问题排查速查表写到这里我把实操中遇到的典型问题整理成一张速查表方便直接对照排查现象可能原因解决办法后端启动报 Table pet_shop.xxx doesnt exist数据库脚本未导入或表名大小写不一致重新执行 SQL注意 Linux 下表名区分大小写前端请求接口 404代理路径或 Nginx 路由配置错误先直接访问后端接口地址验证后端是否正常登录成功但访问其它接口 401token 未传或过期检查拦截器白名单和 axios 请求头中文乱码数据库连接串未指定编码URL 加 characterEncodingutf8库表字符集用 utf8mb4商品列表接口返回慢缺少索引或命中缓存脏数据给 product.category_id 加普通索引检查缓存配置Maven 依赖报错镜像源不稳定换阿里云镜像IDEA 里 Reload All Maven Projects另外特别强调一个问题如果你把前后端都部署在同一台服务器上一定要注意防火墙只开放必要的端口。80Nginx可以对外开放但 3306 只允许内网访问不要对公网暴露 3306否则数据库被爆破的风险很高。后端 8088 端口也要么关掉公网访问要么只允许 Nginx 所在 IP 访问。写到最后分享几个真实感受。这套宠物用品交易系统从零到跑通我最深的体会是前后端分离项目的问题不是“写不出来”而是“版本、环境、联调、部署”这四个环节总有一个在给你上课。我第一遍做的时候在 Spring Boot 3.x 的兼容问题上卡了整整两天后来退回到 2.7.x 才顺畅联调阶段又被跨域搞得晕头转向直到熟悉了代理和 Nginx 反代才真正理解“分离”的含义。如果你也想通过这个项目练手我建议你从数据库设计开始把表结构理顺了再动手写代码然后严格按“环境准备 → 模块实现 → 联调部署”的顺序推进。遇到报错不要慌先看日志再查文章最后再改代码——大部分问题的答案都藏在异常堆栈里。希望这份源码和部署教程能帮你少踩几个坑快速把这个宠物用品交易闭环跑起来。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询