ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

前后端分离项目实战:SpringBoot+Vue+MyBatis+MySQL宠物店系统全套部署指南

前后端分离项目实战:SpringBoot+Vue+MyBatis+MySQL宠物店系统全套部署指南 做前后端分离项目很多人卡在第一步网上找的源码要么缺数据库脚本要么跑起来一堆环境问题要么前后端联调直接懵圈。这套网上宠物店系统是我最近完整整理过的SpringBootVueMyBatisMySQL四件套从零到部署的全流程源码都在我直接把踩过的坑和经验写出来照着做就能在自己电脑上跑起来。这套系统适合什么人正在做毕业设计的在校学生、想练手前后端分离项目的初级开发、以及想快速搭一套电商类模板接业务需求的开发者。宠物店这个业务域很典型——商品展示、购物车、订单、用户管理一套做完你对SpringBoot后端接口设计、Vue组件化开发、MyBatis数据持久化、MySQL表结构设计这四个核心技能点基本就全覆盖了。1. 项目整体设计与技术选型1.1 为什么是这套技术栈先说说选型逻辑。网上宠物店系统这个题目核心诉求是完整、能跑、看得懂。SpringBoot负责后端接口Vue负责前端页面MyBatis封装数据库操作MySQL存数据——这是目前国内中小型项目最主流的一套组合没有之一。SpringBoot的优势在于约定大于配置你不需要像传统SSH一样堆一堆XML配置。一个spring-boot-starter-web依赖拉进来内嵌Tomcat直接跑这对新手极其友好。Vue的响应式数据绑定让前端页面状态管理变得直观购物车数量变化、登录态切换这些交互不用手动操作DOMv-model一把梭。MyBatis则把SQL和Java方法做映射复杂查询你能完全控制SQL语句比JPA那种自动生成的查询在黑盒里调试舒服得多。有个容易忽略的点这套组合的岗位需求量在国内依然巨大。技术选型不只看性能上限更要看学习成本、社区资料丰富度和就业市场匹配度。你搜Java后端招聘十个有八个要求SpringBootMyBatis这本身就是选它的理由。1.2 前后端分离架构怎么理解前后端分离的核心就是前端和后端各跑各的服务通过HTTP接口通信。前端开发用Vue的devServer端口8080后端SpringBoot跑在8081两边互不干扰。浏览器访问前端页面前端通过axios发请求到后端接口后端处理完返回JSON前端拿到数据渲染页面。这样做的直接好处有三个。第一团队可以并行开发前端调Mock数据后端用Postman测接口互不阻塞。第二后端接口可以给多个端复用将来要出小程序、App同一套接口直接对接。第三部署上前后端可以分开扩容前端扔Nginx后端做集群伸缩性灵活。但这个架构有个必须处理的痛点——跨域。浏览器的同源策略规定页面所在域名和请求的接口域名不一致就拦截。所以后端必须配置CorsConfig允许前端的来源访问。这个我在第五节会详细讲很多人系统跑不起来八成是这块没配好。1.3 项目功能模块拆解宠物店系统从业务上看拆成五个核心模块就够了用户模块注册、登录、个人信息管理密码用MD5加盐存储。商品模块宠物商品的分类展示、搜索、详情查看分页查询。购物车模块加入购物车、修改数量、删除商品、合计金额。订单模块提交订单、订单列表、订单详情、订单状态流转。后台管理模块管理员登录、商品上下架、订单状态修改。每个模块都是标准的增删改查加上业务逻辑但组合起来就是一个完整的电商闭环。做这个项目的过程中你会自然理解VO视图对象、DTO数据传输对象的分层设计——前端需要的数据结构和数据库表结构往往不一致不能直接把Entity丢给前端需要一个转换层。2. 数据库设计与核心表结构2.1 数据库表设计思路数据库是整系统的地基表设计好了后面的代码写起来行云流水表设计乱了Service层全是补丁代码。宠物店系统我设计了六张表user用户表存用户ID、用户名、密码、手机号、头像地址、创建时间。category商品分类表比如狗狗、猫咪、水族、宠物用品。pet宠物商品表存名称、分类ID、价格、库存、图片URL、描述、上下架状态。cart购物车表关联用户ID和宠物ID加一个数量字段。orders订单表存订单号、用户ID、总金额、订单状态、收货信息、创建时间。order_item订单明细表一个订单对应多个商品记录每个商品的快照信息。核心设计原则是关联外键和状态字段。外键体现数据关系比如pet表的category_id关联category表状态字段则用整数表示业务状态比如订单状态0待付款、1已付款、2已发货、3已完成、4已取消比用字符串更省空间、查询更快。一个容易踩的坑订单明细表必须把商品名称、价格、图片这些信息冗余存储不能只存商品ID。因为商品可能下架、改价但历史订单里的快照信息不能跟着变。这是电商系统的通用做法——用空间换历史数据的一致性。2.2 MySQL 5.7安装与初始化如果你电脑上还没有MySQL推荐装5.7版本别追最新版。为什么5.7是经过千万级项目验证的稳定版本网上资料多遇到问题一搜就有答案。MySQL 8.0虽然性能更好但密码加密方式变了caching_sha2_password老版本的客户端和驱动连不上新手容易在这里卡半天。安装步骤大致三步。第一步去官网下载mysql-5.7.44-winx64.zip解压版解压到D:\mysql目录。第二步在目录下新建my.ini配置文件设置端口3306、字符集utf8mb4、数据目录。第三步管理员命令行执行mysqld --initialize-insecure初始化再执行mysqld --install注册系统服务net start mysql启动。这里有个细节--initialize-insecure会生成一个不需要密码的root用户方便首次登录。登录后立刻执行ALTER USER rootlocalhost IDENTIFIED BY 你的密码;把密码设置好。如果你直接用了--initialize带安全模式会自动生成随机密码在日志文件里找起来麻烦不如insecure方式省心。数据库创建用CREATE DATABASE pet_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;字符集用utf8mb4因为要存表情符号和特殊文字utf8mb4才是完整的UTF-8。然后用source命令导入我提供的pet_shop.sql脚本六张表和数据一次到位。2.3 MyBatis映射文件编写要点MyBatis的核心是Mapper接口加XML文件。接口定义方法XML写SQL语句两者通过namespace和方法的全限定名绑定。比如PetMapper接口里有ListPet selectPage(Param(offset) int offset, Param(limit) int limit)对应的XML里就有select idselectPage resultTypecom.petshop.entity.Pet SELECT * FROM pet WHERE status 1 ORDER BY create_time DESC LIMIT #{offset}, #{limit} /select写XML的时候注意三件事。第一resultType写实体类的全限定名MyBatis会自动把数据库列名转成驼峰命名的Java属性前提是配置文件里开了map-underscore-to-camel-case: true。比如数据库的create_time自动映射到Java的createTime不用写一堆resultMap。第二动态SQL用where、if标签做条件拼接比如搜索功能传了关键词才加WHERE name LIKE条件不传就查全部避免字符串拼接SQL的注入风险。第三批量插入用foreach标签购物车结算时多条明细一次插入性能比循环单条插入高一个量级。3. 后端SpringBoot核心代码实现3.1 项目结构规范与分层思想后端包结构我按职责分层标准的三层架构。controller层接收请求参数、调用service、返回结果service层写业务逻辑、事务控制mapper层只做数据库操作。另外加entity实体类、dto请求参数封装、vo返回视图对象、config配置类、common通用工具和返回结果封装。一个典型请求的流转是这样的前端GET /api/pet/list?page1size8先到PetController的list方法参数绑定到PageQuery对象然后调用PetService.pageQuery(...)方法。Service层判断分类、计算分页偏移量调用PetMapper的查询方法拿到数据后把Pet实体转成PetVO比如隐藏库存这种敏感字段包上统一返回对象Result.success(data)返回前端。统一返回对象是我特别推荐的一个设计。所有接口无论成功失败返回格式都是{code: 200, message: success, data: {...}}前端axios拦截器统一判断code不用每个接口单独处理错误代码量直接减少三分之一。格式统一了前后端联调时沟通成本也低对着接口文档就知道返回结构长什么样。3.2 分页查询与条件搜索的实现宠物列表页必须做分页不然几十条数据一次全查出来前端渲染卡顿接口响应也慢。我这里用的是手写分页不引入PageHelper插件。为什么PageHelper虽然方便但它通过拦截器改写SQL有时候会和你自己写的复杂SQL冲突出问题排错成本高。自己写分页就两行SQL的事一条SELECT COUNT(*)查总数一条LIMIT查当前页数据逻辑全在掌控中。Service层分页方法的逻辑是先算总页数再查当前页数据最后把分页信息和数据列表一起封装到PageVO里返回。搜索功能加了一个keyword参数在Mapper的XML里用if testkeyword ! null and keyword ! 动态拼接AND pet.name LIKE CONCAT(%, #{keyword}, %)。用CONCAT而不是直接在SQL里写%#{keyword}%是因为#{}预编译占位符不接受字符串拼接%和%写在占位符里面会报错或查不出结果——这是新手最常见的坑。3.3 购物车与订单的事务处理购物车和订单是业务逻辑最重的部分核心难点在事务。用户点击结算后端要同时做三件事把购物车数据复制到订单明细表、生成订单主表记录、清空购物车。这三步任何一步失败数据就乱了——比如订单生成了但购物车没清空用户重复提交订单。所以Service方法上必须加Transactional注解让Spring管理事务任何一个异常都整体回滚。具体逻辑是接收一个OrderCreateDTO里面包含用户ID和购物车ID列表。Service层先根据购物车ID查出所有商品和数量计算总金额生成订单号用时间戳加随机数保证唯一插入orders表拿到自增ID再把每个商品快照插入order_item表最后删除对应的购物车记录。这个过程中还要校验库存——库存不足直接抛异常让事务回滚前端提示用户库存不够。订单号生成有个小细节不要用数据库自增ID当订单号那样会泄露订单量而且看起来不专业。我用的方案是yyyyMMddHHmmss加六位随机数或者直接用UUID去掉横线30位以内唯一性有保障。4. 前端Vue核心页面与前后端联调4.1 Vue环境搭建与项目初始化前端用Vue 2加Vue CLI作为基础环境搭建。为什么选Vue 2而不是Vue 3如果你面向的是大部分网上的教程和毕设要求Vue 2的生态最稳Element UI组件库成熟遇到问题搜一圈全有答案。Vue 3的Composition API更现代但配套的Element Plus还在持续更新部分第三方库兼容性不如Vue 2。当然如果你已经有Vue 3基础选Vue 3也没有问题下面的思路完全一样。环境搭建三个必备工具Node.js推荐14.x LTS版本、npmNode自带、Vue CLI脚手架。npm install -g vue/cli全局安装脚手架后执行vue create pet-shop-frontend创建项目。创建过程中会让你选配置我选的是Manually select features勾选Babel、Router、Vuex用2.x版本包管理器用npm。项目创建后在src目录下核心是views页面组件、components公共组件、router路由配置、api接口请求封装、storeVuex状态管理这几个文件夹。组件化开发的关键是让每个页面由多个组件拼装而成比如商品列表页由SearchBar、PetCard、Pagination三个组件组成这样每个组件职责单一后面维护改一个组件不影响其他页面。有个安装依赖时的坑npm install经常因为网络问题卡住或报错建议把npm镜像源切换成国内镜像源执行npm config set registry https://registry.npmmirror.com下载速度能快十倍node-sass这种需要编译的包也不会动不动就下载失败。4.2 前端路由与页面架构设计Vue Router管理页面跳转。我配置了以下几个页面路由/首页跳转到/pet/list宠物列表页、/pet/detail/:id详情页、/cart购物车、/login登录、/register注册、/order/list订单列表、/admin后台管理。路由守卫是前端权限控制的关键。在router/index.js里配置beforeEach全局前置守卫判断访问的页面是否需要登录。比如访问/cart时检查localStorage.getItem(token)是否存在没有token就next(/login)跳登录页有token就放行。这个逻辑很简单但它是前端用户感知到的第一道权限关卡。接口请求统一封装在src/api/request.js里。用axios创建实例配置基础URL为http://localhost:8081/api这个地址指向后端服务。再通过axios拦截器统一处理token——请求拦截器把token加到请求头Authorization字段响应拦截器判断HTTP状态码和业务code码code为401表示登录过期跳转登录页并提示用户。4.3 商品列表页与购物车交互实现商品列表页是整个系统的门面。页面加载时调用getPetList({ page: 1, size: 8 })接口拿到数据后渲染成卡片网格。每张卡片显示宠物图片、名称、价格、库存状态点击加入购物车按钮调用addToCart接口。这里有个体验优化的细节加入成功后用this.$message.success(已加入购物车)给用户即时反馈避免用户以为按钮没反应反复点击。购物车页面的核心是响应式数据绑定。数量加减用v-model绑定当前行数据数量变化时调用后端接口更新数据库。合计金额用Vue的计算属性computed遍历购物车列表累加每行的price * quantity任何数据变化时自动重新计算不需要手动触发更新——这是Vue最有魅力的地方。前端调接口的坑集中在两个地方。第一跨域问题后端没配CORS时浏览器报No Access-Control-Allow-Origin header is present我在后端加了全局配置类解决。第二接口参数格式不匹配比如后端是RequestParam接收单个参数前端却在body里传JSON就会报参数绑定异常。这两个问题应当在联调第一天就解决掉不然每个页面都报同样的错心态直接崩。5. 项目部署与常见问题排查5.1 前端打包与Nginx部署开发完成后的前端需要打包部署npm run build会生成dist目录里面是压缩优化后的静态文件——HTML、JS、CSS。这里要改一个关键配置vue.config.js中publicPath从/改成./否则打包后JS和CSS的引用路径是绝对路径部署到服务器子目录时全部404。生产环境我用Nginx托管静态文件。在nginx.conf里配置一个server块listen 80root /usr/share/nginx/html/pet-shop-frontend指向dist目录index index.html。核心配置是location /api/ { proxy_pass http://127.0.0.1:8081/api/; }——把前端发起的/api请求反向代理到后端的8081端口这样浏览器的访问都在同一个域名下跨域问题在部署环境直接消解不需要后端再配CORS。还有一个前端路由的Nginx配置location / { try_files $uri $uri/ /index.html; }。这句配置的作用是当用户直接访问/pet/detail/3这种前端路由地址时Nginx找不到对应文件就回退到index.html交给Vue Router处理。不加这行配置你刷新页面就直接404这是前后端分离部署最经典的坑。5.2 SpringBoot后端打包与启动后端打包用Maven。在pom.xml里配置好spring-boot-maven-plugin执行mvn clean package -DskipTests生成可执行jar包。因为这个项目拆了前后端目录后端在pet-shop-backend目录执行打包命令即可。启动方式我推荐用脚本而不是直接java -jar。写一个start.sh脚本#!/bin/bash nohup java -jar pet-shop-backend.jar \ --server.port8081 \ --spring.datasource.urljdbc:mysql://127.0.0.1:3306/pet_shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai \ --spring.datasource.usernameroot \ --spring.datasource.password你的密码 \ logs/pet-shop.log 21 echo PetShop started, pid: $!这里有几个必须注意的点。数据库URL里useSSLfalse不能少本地开发环境MySQL没有配SSL证书加了这个参数避免连接时SSL握手报错。serverTimezoneAsia/Shanghai指定时区不然MySQL连接会报时区错误或者时间差八小时。nohup和的组合让jar包在后台运行终端关闭也不影响进程。日志重定向到文件出了问题直接tail -f logs/pet-shop.log看日志排查。启动成功验证方式curl http://localhost:8081/api/pet/list?page1size1返回JSON数据说明后端正常。再访问前端域名能看到页面且能拉到数据整个系统就跑通了。5.3 高频报错与解决方案速查把我在部署和运行过程中实际踩过的高频问题整理成了一张速查表都是血泪经验。端口被占用启动报Port 8081 was already in use。解决方案是netstat -ano | findstr 8081查PIDtaskkill /PID 进程号 /F杀掉或者改配置文件端口。这个问题在多次启动测试时特别常见之前的java进程没有完全退出。数据库连接失败报Access denied for user rootlocalhost或Communications link failure。前者是密码错误或用户权限问题重新确认数据库账号密码后者是MySQL服务没启动或端口不是3306先net start mysql再检查连接串。MyBatis绑定异常报Invalid bound statement (not found)。原因是Mapper接口和XML文件没有正确关联检查三个地方接口所在包路径和XML的namespace是否一致、XML文件名是否和接口名相同、application.yml里mapper-locations配置是否指向classpath:mapper/*.xml。前端白屏页面空白且控制台无报错。通常是publicPath配置问题或路由模式问题。publicPath改成./Router用hash模式createWebHashHistory这两个配置组合在生产环境最稳不用依赖服务器配置路径转发。跨域请求被拦截浏览器控制台报CORS policy错误。开发环境用后端CorsConfig配置allowedOrigins(http://localhost:8080)生产环境用Nginx反向代理消除跨域。两条路选一条走就行别两个都开着偶尔会有冲突。5.4 两个必须养成的调试习惯最后分享两个对我来说帮助极大的调试习惯。第一个是打印SQL日志。在application.yml里配置mybatis: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这样每次MyBatis执行SQL控制台都会打印完整的预处理语句和参数值。前端反馈数据查不出来的时候我第一件事就是看后端日志里SQL到底执行了什么、参数传对了没有。很多问题根本不用断点调试看SQL日志一眼就定位——比如WHERE id ?的参数是null那不是SQL的问题是前端压根没把ID传过来。第二个是把接口测试前置。联调之前先用Postman或Apifox把所有接口跑一遍确认每个接口的入参出参符合预期。我见过太多项目联调时互相甩锅——前端说后端接口有问题后端说前端参数传错了。用接口文档工具把契约定死测试通过再让前端对接能省掉两个通宵的联调时间。6. 这套系统的可扩展方向做完这套宠物店系统你手里的东西不只是能跑的项目更是一个可以不断加功能的电商骨架。我建议按这三个方向扩展难度递增但每个都很有价值。方向一是支付模块。接入微信支付或支付宝沙箱环境把订单状态流转从待付款到已付款这段补上。这需要你理解支付回调机制、订单状态机的幂等处理是电商系统最核心的难点简历上写这个含金量直接拉满。方向二是管理后台的权限控制。现在管理员和普通用户共用一套接口只是靠前端路由隐藏入口。你可以引入Spring Security或Shiro做基于角色的接口鉴权区分管理员接口和用户接口这能让你的系统达到生产级安全标准。方向三是商品搜索升级。现在用的是MySQL的LIKE模糊查询数据量上来以后性能会明显下降。可以引入Elasticsearch做商品搜索用IK分词器支持中文分词搜索同时把搜索历史、热门搜索这些功能一起做了就是一套完整的搜索解决方案。我在实际带人做这类项目时有几个体会。和网上那些完整度参差不齐的教程相比一个能从数据库脚本一路跑到Nginx部署的项目价值远高于零散的代码片段。它让你把所有环节走通一遍之后你在工作中遇到任何一个环节的问题脑子里都会有一条完整的链路图这是刷多少道面试题都换不来的。如果你正在跑这套系统卡在哪个环节了无论是环境配置、代码报错还是部署问题把错误日志贴出来我看到了会抽时间帮你排查。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进