Java Spring Boot + Vue宠物管理系统:前后端分离实战设计与部署指南
发布时间:2026/10/19 3:07:15 锦皓数字建站

这些年做过的管理系统项目不少但宠物管理系统这个题目很有意思业务不复杂却麻雀虽小五脏俱全。里面塞了宠物档案、客户管理、寄养预约、医疗服务、商品销售等多个模块技术栈刚好踩在前端分离开发的主流路子上后端用 Java 体系、前端用 Vue 框架。这套组合不光是高校里常见的课程设计和毕业设计选题放到真实创业项目里当 MVP 原型也完全说得过去。标题里列了源码、数据库、文档三件套说明这是一个能直接跑起来、能看懂、能二次开发的完整交付物。我前后接触过不少类似需求有人是想快速搭一套门店管理系统有人是为了学习前后端如何配合还有人只想在答辩时有东西可讲。这篇文章我就从技术拆解、模块设计、实操落地的角度把宠物管理系统的整个脉络讲清楚重点说说哪些地方值得花心思哪些地方最容易踩坑。1. 系统整体设计先想清楚要管理什么很多人在动手写代码之前就急着建表这其实是最大的误区。宠物门店的管理系统核心不是“写代码”而是先把业务角色和数据流转理清楚。我习惯的做法是先画一张业务场景图把谁在用系统、日常要完成什么操作、数据从哪来到哪去都列出来然后才谈技术选型。1.1 拆解核心业务宠物档案、预约寄养、服务记录、商品销售宠物管理系统听起来就是“给宠物做档案”但实际运营场景远不止这一点。一家普通的宠物店或者宠物医院每天要处理的业务大致有以下几类宠物档案登记新客户带宠物进店要记录宠物种类、品种、年龄、疫苗情况、绝育状态等基础信息客户信息管理宠物主人是谁、联系方式、地址、消费偏好这些决定后续的营销和服务跟进寄养与预约节假日寄养需求集中需要按时间段排期还要记录寄养期间的喂食、遛狗、健康状况医疗与美容服务记录每次洗澡、剪毛、打疫苗、看病都要留下服务痕迹方便下次服务时查看历史商品销售与库存宠物粮、玩具、日用品有进销存需求卖出去的商品要能挂在对应宠物和客户名下。这五个方向就是整个系统模块划分的依据。数据表不要追求大而全而是围绕这些真实操作去设计。举个例子寄养模块不应该只是“开始时间”和“结束时间”两个字段还要有每天喂食记录的明细子表否则运营时间一长客户问“我家狗昨天吃得怎么样”你根本答不上来。1.2 为什么选择前后端分离Java Vue 的组合逻辑这个系统选择 Java Vue本质上是选择了前后端分离的开发模式。后端负责业务逻辑和数据处理前端负责页面展示和交互两边用 JSON 格式的接口通信。这种架构的好处是团队可以并行开发——后端开发不用关心页面长什么样前端开发可以用 Mock 数据先做页面最后联调时再对接口。后端用 Java 体系具体来说就是 Spring Boot 那一套。Spring Boot 在 Java 领域基本是事实标准它能快速搭建独立运行的 Web 服务内置 Tomcat省去繁琐的 XML 配置。再加上 MyBatis 或 MyBatis-Plus 操作数据库几行代码就能完成增删改查。Java 生态的稳定性、类库丰富度以及招聘市场上的熟悉度让它成为这类业务系统的稳妥选择。前端用 Vue主要看中的是组件化开发和响应式数据绑定。Vue 对于新手来说上手曲线比 Angular 平缓比 React 的中文资料更友好配合 Element UI 这类组件库表格、表单、弹窗、分页这些后台管理界面常用组件都能直接拿来用开发效率提升非常明显。1.3 权限与状态设计让数据流转起来一个经常被忽略的设计点是权限模型。很多宠物管理系统的 Demo 做得很简陋登录进去所有功能都对所有人开放这在真实场景里是行不通的。宠物店里有前台、美容师、店长、财务这些角色不同角色应该看到不同内容。这个系统里建议采用基于角色的权限控制思路也就是 RBAC 模型。最简单实用的做法是三张核心表用户表、角色表、用户角色关系表。后端在接口层面做拦截校验前端根据角色动态渲染菜单。不用做到按钮级权限那么细但菜单级权限是必要的。举个例子前台人员可以登记宠物档案、创建寄养订单但价格折扣的修改权限应该只属于店长财务人员能看到营收统计但不应看到客户联系方式之外的其他敏感信息。这些限制在真实项目中是刚需在毕业设计答辩里也是加分项。状态设计同样关键。寄养订单不应该只有“进行中”和“已完成”两个状态而是要有“已预约、已入住、已完成、已取消、异常结束”等完整生命周期。每当我看到 Demo 里状态字段是随手写死的字符串时就知道这个系统离上线还差得很远。正确的做法是用枚举管理状态字典前后端共用一套状态码这样后续扩展不会失控。2. 技术选型与开发环境方向对了效率才能拉满技术选型这块我多说几句因为很多人在这一步就陷入选择困难Spring Boot 用 2.x 还是 3.xMyBatis 还是 JPAVue 用 2 还是 3Element UI 还是 Ant Design Vue如果没有人带光做决定就能耗掉一个周末。我这里直接给出经过验证的组合不说绝对正确但至少是一条不折腾的路。2.1 后端技术栈Spring Boot MyBatis-Plus 的选择理由后端我推荐 Spring Boot 2.7.x MyBatis-Plus 3.5.x理由很实在。Spring Boot 3.x 虽然已经发布但部分第三方组件还在适配期比如一些老版本连接池和工具库会报兼容性问题。对于宠物管理系统这种偏业务型的项目没必要追求最新版本稳定才是第一位的。2.7 版本成熟度高教程多踩坑经验随便一搜就是一大堆这对学习和开发效率都很重要。MyBatis-Plus 相比原生 MyBatis最大的价值在于内置了通用 Mapper 和通用 Service。单表查询基本不用写 SQL继承一个 BaseMapper 接口就自动拥有了增删改查、分页查询、条件构造器这些能力。宠物档案表、客户表这类简单的 CRUD 操作用 MyBatis-Plus 能省掉一半的代码量。多表关联查询再手写 XML两头兼顾不耽误事。数据库统一用 MySQL 8.0。8.0 相比 5.7 性能更好窗口函数等新特性在写统计报表时非常方便。字符集一定要用 utf8mb4不是 utf8。utf8mb4 是完整的 UTF-8 编码能存 emoji 表情和生僻字宠物名里出现一些特殊符号是常有的事用 utf8 就会报错。2.2 前端技术栈Vue Element UI 的生态优势前端推荐 Vue 2.7 Element UI 2.15这是一个被验证过无数次的组合。我知道 Vue 3 和 Element Plus 已经是大趋势但客观讲Vue 2 的生态资料更加丰富几乎所有你遇到的问题都有现成的解决方案。对于课程设计或者中小型管理后台Vue 2 完全够用而且从维护成本看很多企业里的存量项目也还是 Vue 2 居多。Element UI 的表单组件非常完善el-form 加 rules 就能做校验el-table 加列配置就有表格展示el-pagination 接个事件就是分页。这些组件让前端代码变得非常“声明式”你只需要描述页面状态不用关心 DOM 操作。对于没有前端基础的人来说照官方文档抄代码都能拼出一个像样的页面。这里有个小经验配一个 Vue 的 UI 组件库可以显著提升开发效率。很多人以为前端开发难其实难在那 20% 的复杂交互场景而宠物管理系统这类的后台页面80% 都是表单、表格、弹窗、消息提示Element UI 早就封装好的东西直接用就行了。2.3 数据库与开发工具的准备开发工具这块不搞花里胡哨够用就行。数据库可视化工具用 Navicat 或者 DataGripNavicat 操作简单适合新手DataGrip 对复杂查询的支持更专业。接口测试用 Postman 或者 ApifoxApifox 更懂国内团队还能自动生成接口文档。后端 IDE 用 IntelliJ IDEA前端用 VS Code这两个是各自的标配。一开始写代码前先把环境变量配好JDK 用 8 还是 11我建议 JDK 8Spring Boot 2.7 对 JDK 8 支持得很好兼容性最稳。Maven 用 3.6Node 用 14 或 16这两个版本在 Vue 2 项目里经过大量验证不会有奇奇怪怪的兼容性问题。如果 Node 版本太新反而容易出现 node-sass 编译失败的坑。2.4 版本兼容性隐患最容易忽略的坑技术栈版本真不是小事我见过太多人栽在这里。典型的场景就是跟着教程敲代码结果报错信息跟教程里完全不一样一查发现是版本差了好几个大版本。比较经典的坑有这么几个JDK 版本和 Maven 编译插件不匹配JDK 17 配上老版本的 maven-compiler-plugin会直接报不支持 release 版本错误Spring Boot 2.7 和 knife4j 或 Swagger 3 的兼容性问题部分版本需要额外的路径匹配策略配置Vue 2 项目装了最新版依赖可能拉到了不兼容的构建工具版本最常见的是 node-sass 安装失败Lombok 版本过旧在 JDK 高版本下会失效。我的建议是用项目里自带的 pom.xml 和 package.json不要随手去升级版本号。能用就用少折腾。3. 数据库设计与核心表结构系统的地基前面把模块和角色都想清楚了数据库设计就是水到渠成的事。这个系统的表结构不算复杂核心表大概六七张就够。我逐个讲讲设计思路重点说字段为什么要这么设计因为很多人的表结构从根上就有逻辑问题。3.1 数据模型总览五大核心表我把整个系统的核心表列出来按照业务模块划分核心模块数据表用途说明系统管理sys_user系统登录用户含用户名、密码、角色标识宠物档案pet_info宠物基本信息关联宠物主人客户管理pet_customer宠物主人信息含联系方式和地址寄养预约boarding_order寄养订单主表记录时间、费用、状态服务与商品service_record服务记录表记录美容、医疗等操作和商品消耗还可以加一张寄养明细表 boarding_detail用来记录寄养期间每天的喂食和健康数据。表别嫌多有些细分需求在第一次设计时就要预留好位置不然后期加字段比加表还难受。3.2 宠物档案表从字段设计看业务细节宠物档案表是整个系统的门面字段设计能看出设计者对业务理解的深度。基础表结构大概是这样的CREATE TABLE pet_info ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 宠物ID, pet_name VARCHAR(50) NOT NULL COMMENT 宠物昵称, pet_type TINYINT NOT NULL COMMENT 宠物类型1-狗2-猫3-其他, breed VARCHAR(50) COMMENT 品种, gender TINYINT DEFAULT 0 COMMENT 性别0-未知1-公2-母, birth_date DATE COMMENT 出生日期, weight DECIMAL(5,2) COMMENT 体重(kg), is_neutered TINYINT DEFAULT 0 COMMENT 是否绝育0-否1-是, vaccine_status VARCHAR(100) COMMENT 疫苗情况描述, customer_id BIGINT NOT NULL COMMENT 宠物主人ID关联客户表, avatar_url VARCHAR(255) COMMENT 宠物照片URL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标记0-未删除1-已删除 ) COMMENT 宠物档案表;有几个细节值得注意。第一pet_type 用 TINYINT 存枚举值不要直接存字符串“狗”“猫”。用枚举值可以在后端做统一字典翻译前端显示时再用标签映射性能和扩展性都更好。第二birth_date 用 DATE 类型而不用 VARCHAR因为后续要按年龄筛选宠物日期类型才能直接进行范围查询。第三字段注释一个都不能少。数据库注释是代码可读性的一部分接手你项目的人会感谢你的几个字。还有一个关键点逻辑删除字段。实际项目里宠物档案删错了要能恢复客户信息删除了要留审计痕迹所以不要用物理 DELETE而是加一个 deleted 标识。MyBatis-Plus 里配置逻辑删除后查询时自动追加 deleted 0 条件非常方便。3.3 关联关系的建立主外键与业务冗余的权衡关系型数据库的核心就是关系。宠物表和客户表之间是典型的“一对多”关系一个客户可能养了三只宠物一只宠物只属于一个主人。设计关系时要注意一个问题外键约束要不要加生产环境里很多团队会主动去掉数据库外键约束把关系维护逻辑放在服务层。原因很简单外键会造成插入和更新的锁竞争影响高并发下的性能而且在微服务拆分的趋势下跨库表已经没法用外键了。但对于宠物管理系统这种并发量不高的业务系统我建议保留逻辑上的关联不强制使用物理外键。也就是说pet_info 表里存 customer_id 字段但不加 FOREIGN KEY 约束靠应用层保证引用完整性。这样既保留了关系语义又不会在删除客户时被外键限制卡住。在查询时通过表关联把信息一次性查出来。比如寄养订单列表需要同时显示宠物名、主人电话、订单时间那就用 JOIN 查询SELECT bo.id, pi.pet_name, pc.customer_name, pc.phone, bo.start_date, bo.end_date, bo.status FROM boarding_order bo LEFT JOIN pet_info pi ON bo.pet_id pi.id LEFT JOIN pet_customer pc ON pi.customer_id pc.id WHERE bo.deleted 0 ORDER BY bo.create_time DESC实际编码时尽量少用 N1 查询能一次 JOIN 查出来的数据就不要在循环里逐条查。对这类接口的性能影响非常明显。3.4 初始化数据让系统一跑起来就有内容空表跑起来的系统登录进去一片空白这对演示和验收都是减分项。项目里最好准备一份初始化 SQL包含一个默认管理员账号、几个测试客户、几条宠物档案、两条寄养订单数据量不用大但要覆盖系统的各类状态。初始化数据要坚持“适量且合理”的原则不要造几百条垃圾数据看起来不真实反而影响体验。比如测试寄养订单的时间段要避开明显的日期冲突不同状态的订单都放一条这样答辩时切换状态演示就不用手忙脚乱临时造数了。4. 核心功能实操从零跑通一个模块理论说得再多不如动手跑通一个功能。我以“宠物档案管理”这个模块为例把从建表到前端页面的完整流程走一遍。这个模块麻雀虽小但覆盖了增删改查、分页、条件查询、图片上传这些常见需求跑通它其他模块都是类似的套路。4.1 后端接口开发宠物档案管理后端代码按 Controller、Service、Mapper 三层组织。先看实体类用 Lombok 简化样板代码Data TableName(pet_info) public class PetInfo { TableId(type IdType.AUTO) private Long id; private String petName; private Integer petType; private String breed; private Integer gender; private Date birthDate; private BigDecimal weight; private Integer isNeutered; private String vaccineStatus; private Long customerId; private String avatarUrl; private Date createTime; private Date updateTime; TableLogic private Integer deleted; }TableName 注解指定表名TableId 配置主键自增策略TableLogic 标记逻辑删除字段。MyBatis-Plus 的注解机制非常简单直接不需要配一堆 XML 文件这也是我推荐它而不是纯 MyBatis 的原因。Mapper 接口极其简洁public interface PetInfoMapper extends BaseMapperPetInfo { }是的就这一行。继承 BaseMapper 后insert、deleteById、selectById、selectPage 这些基础操作全都有了。分页查询需要配置一个分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }Controller 层直接调 Service 的方法RestController RequestMapping(/api/pet) public class PetInfoController { Autowired private PetInfoService petInfoService; GetMapping(/page) public Result page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String petName) { PagePetInfo page new Page(pageNum, pageSize); LambdaQueryWrapperPetInfo wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(petName), PetInfo::getPetName, petName); wrapper.orderByDesc(PetInfo::getCreateTime); return Result.success(petInfoService.page(page, wrapper)); } }注意这里的 LambdaQueryWrapper 条件构造器like 方法第一个参数是布尔值当 petName 为空时自动跳过这个查询条件代码很优雅。分页结果统一封装在 Result 对象里返回格式保持 { code, message, data } 一致前端联调会非常省心。4.2 前端页面开发Vue 组件与接口联调前端用 Vue 写宠物档案列表页。先封装请求方法// api/pet.js import request from /utils/request export function getPetPage(params) { return request({ url: /api/pet/page, method: get, params }) } export function addPet(data) { return request({ url: /api/pet, method: post, data }) } export function updatePet(data) { return request({ url: /api/pet, method: put, data }) } export function deletePet(id) { return request({ url: /api/pet/${id}, method: delete }) }页面组件结构就是标准的 Element UI 表格加弹窗表单template div el-form :inlinetrue :modelqueryParams el-form-item label宠物名称 el-input v-modelqueryParams.petName placeholder输入宠物名称 / /el-form-item el-form-item el-button typeprimary clickloadData查询/el-button /el-form-item /el-form el-table :datatableData border stripe el-table-column proppetName label宠物名称 / el-table-column proppetType label类型 / el-table-column propbreed label品种 / el-table-column propownerName label主人 / el-table-column label操作 template slot-scopescope el-button typetext clickhandleEdit(scope.row)编辑/el-button el-button typetext clickhandleDelete(scope.row)删除/el-button /template /el-table-column /el-table el-pagination :current-pagequeryParams.pageNum :page-sizequeryParams.pageSize :totaltotal current-changehandlePageChange / /div /template联调阶段最常见的问题就是字段名对不上。后端返回的是 petName前端写成了 pet_name结果列表数据全是空的。解决的唯一办法就是统一规范后端统一用驼峰命名前端就用驼峰中间加一层 map 转换是不得已而为之能避免就不要加。4.3 图片上传与其他细节宠物档案里有头像上传功能这个模块看着小实现起来却有几个坑。最稳妥的方案是后端提供一个通用的文件上传接口接收 MultipartFile保存到服务器本地目录返回可访问的 URL 给前端。不用引入 OSS 这种外部依赖也避免项目变得臃肿。PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) ext; String datePath new SimpleDateFormat(yyyyMMdd).format(new Date()); String savePath /upload/ datePath; File dir new File(savePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(savePath / newFileName)); return Result.success(/upload/ datePath / newFileName); }注意上传目录别放在临时文件里重启就会丢。最好配置成外部固定目录并在 Spring Boot 的配置类里加资源映射否则前端访问不到上传的图片。这个坑我提醒过很多人但还是不断有人踩。4.4 整套系统的部署打包与前后台合并开发完要部署运行最直接的方式是后端打包成 jar用命令启动前端打包成静态文件由后端托管。前端先构建npm run build生成的 dist 目录里就是打包好的静态文件。把 dist 目录复制到后端项目的 resources/static 下重新打包这样访问后端地址就能直接看到前端页面不用再单独配 Nginx对课程设计和演示场景来说非常方便。后端打包mvn clean package -DskipTests启动java -jar target/pet-manage-system.jarSpring Boot 内置 Tomcat一个 jar 就能跑起来。这也是 Spring Boot 受欢迎的原因之一部署简单到几乎没有门槛。5. 常见问题与排查技巧实录开发过程中踩坑是常态我把这个系统最常遇到的问题整理成速查表都是从项目现场总结出来的经验不一定都在文档里写全了但绝对实用。问题现象根本原因解决方案数据库中文乱码连接串没有指定 UTF-8URL 添加 characterEncodingutf8库表字符集设为 utf8mb4前端访问接口报 403/跨域前后端端口不同未配置跨域后端配置 CorsFilter 或跨域的过滤器登录状态失效Token 过期或请求头未携带前端请求拦截器统一设置 Authorization 请求头列表页报某字段不存在实体字段与表字段映射不一致检查 TableField 或驼峰转换配置上传图片后页面打不开资源映射未配置配置 WebMvcConfigurer 映射 /upload/** 到本地目录启动时报端口被占用8080 端口被其他进程占用改 server.port 或杀掉占用进程每一项我展开说几句。5.1 跨域问题的真实场景本地开发时 Vue 跑在 8080后端跑在 9090前端发请求肯定跨域。有经验的打工人会在开发环境配 Vite 或者 webpack 的 proxy 代理把 /api 开头的请求转发到后端地址这样浏览器上看起来是同源的。后端那边也要加上跨域处理的过滤器双保险。Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }上线部署后前后端合并部署就基本不存在跨域问题了。5.2 数据库连接失败的排查思路这类系统数据库连接失败十有八九是三个原因服务没启动、连接参数不对、用户权限不足。排查顺序建议先 ping 数据库地址再看端口通不通然后检查用户名密码最后看驱动和数据源配置。MySQL 8.0 的连接驱动是 com.mysql.cj.jdbc.Driver很多教程还在复制 5.x 的老驱动启动后直接报 ClassNotFound。新版驱动要求 URL 里带上 serverTimezone 参数比如spring.datasource.urljdbc:mysql://localhost:3306/pet_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse注意时区参数不加时区配置在默认设置下可能导致时间字段查询偏差 8 小时。5.3 前后端联调数据对不上的排查如果前端展示数据的结构和预想不一样最快的定位方式是打开浏览器开发者工具的 Network 面板看接口实际返回的 JSON 结构。很多时候是后端返回了完整对象嵌套前端页面想拿的字段在二层嵌套里或者后端把日期序列化成了时间戳前端需要格式化转换。这里建议后端统一用 JSON 视图对象返回不要直接把实体类暴露给前端。比如宠物列表接口需要展示主人姓名和电话宠物实体里只有 customerId那就专门建一个 PetInfoVO 对象包含 petName、ownerName、phone 等前端需要的字段。这样截断了实体类对外的信息暴露也让接口语义更清晰。5.4 追踪日志与调试技巧哪里有问题就打印日志看异常堆栈。Spring Boot 默认打印的日志已经够用了关键是别忽略最后的 Caused by 信息。很多人只看第一行异常就抓瞎实际上真正的错误原因往往在最后一行的 Caused by 里。比如报 connection refused可能是配置写在 application.yml 里没生效而本地环境变量里的数据库地址覆盖了配置文件。这类问题不看完整堆栈根本定位不到。经验就是先看 Caused by再顺藤摸瓜找配置源头。从项目角度看宠物管理系统非常适合当作前后端分离入门的第一套完整项目。它的业务边界清晰不需要处理复杂的支付逻辑、没有高并发的性能瓶颈、没有算法深度但完整覆盖了管理系统开发的全部套路。做一遍就懂了 Spring Boot 怎么搭、MyBatis-Plus 怎么用、Vue 组件怎么组织、前后端怎么联调。如果后续想往生产级方向扩展可以从这几处入手把本地图片存储换成对象存储引入 Redis 做缓存和验证码存储用枚举和字典表管理业务状态把 SQL 里的联表查询优化成分页加关联查询。这些升级方向技术含量逐步提高但底座就是当前这套系统。最后分享一个我个人的小习惯拿到这种项目不要急着跑起来先把 SQL 里的表结构看一遍然后在纸上画一张模块和数据表的关系图。理解数据从哪里来、到哪里去运行起来之后再去点页面很多设计上的巧思和问题一目了然。带着问题去看代码比别人先跑一步收获完全不一样。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。