news 2026/9/14 15:34:42

Spring Boot + Vue宠物管理系统实战:从设计到部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot + Vue宠物管理系统实战:从设计到部署

做宠物管理系统这个项目,最早是给学生准备的一套Spring Boot + Vue前后端分离实战案例。市面上这套题目的源码一搜一大把,但真正能一次跑起来的很少,要么数据库表结构缺胳膊少腿,要么前端依赖装不上,要么文档就一句“下载后自行研究”。所以我整理这套项目时给自己定了三个硬指标:源码能直接启动、数据库脚本完整、配套文档能让人照着做到上线。这篇文章会把整个系统的设计思路、表结构、后端接口、前端页面以及部署排错过程完整过一遍。打算做课程设计、毕业设计,或者纯粹想用Spring Boot + Vue练手的开发者,都能从这里拿到一套能落地的参考方案。

1. 为什么选Spring Boot + Vue做宠物管理系统

1.1 从需求场景说起:宠物管理到底要管什么

第一次看到“宠物管理系统”这个题目,有人觉得不就是写个增删改查么。真要做细,远远不止。比如一个流浪动物救助站,每天要录入新收容的猫狗,记录它们的外貌特征、健康状况、疫苗注射情况,还要管理领养人的申请和回访信息。再比如一个宠物店,需要关注在售宠物的品种、价格、库存,以及卖出后和主人的绑定关系。所以背后至少包含宠物档案、品种、主人/用户、领养记录、疫苗记录这几张核心表,业务上还涉及状态流转:待领养、已领养、休息中等等。只有先把业务场景梳理清楚,表和接口才不会设计得四不像。

1.2 为什么是Spring Boot + Vue而不是SSM或单独JSP

现在做管理系统,最常见的技术组合无非三类:Spring Boot + Vue、SSM + JSP、Django + React。我为什么在这套项目里选了Spring Boot + Vue?先看一个对比:

方案前端体验学习成本就业/毕设加分部署复杂度
Spring Boot + Vue前后端分离,界面流畅中等,需要理解跨域和代理高,主流岗位要求中等
SSM + JSP服务端渲染,页面较老低,单体架构易上手偏低,趋向过时
Django + React前后端分离,开发效率高中等,需要掌握Python栈看岗位方向中等

对多数课程设计和毕业设计场景来说,Spring Boot + Vue 最大的优势是“一次开发,很多地方都在用”。代码分成清晰的接口层、业务层和数据层,前端直接通过HTTP调接口,将来想接小程序或者App,后端几乎不用做大的改动。如果纯粹为了省事用JSP,确实能快速出活,但是页面效果和工程化程度都很难拿出手。所以这套项目我坚定选了前后端分离。前后端分离最大的门槛在环境配置和联调,这恰恰是学习者最容易卡住的地方,后面我会专门讲怎么避坑。

1.3 系统功能划分与主要角色

我从实际管理的角度把功能分成三类:

  • 基础档案管理:宠物信息的新增、编辑、删除、详情查看,品种管理。
  • 领养流程管理:领养人提交申请、管理员审核、记录领养状态。
  • 系统管理:用户登录、账号管理、操作日志(简单项目可省略)。

登录角色至少分管理员和普通用户,管理员维护所有数据,普通用户只能查看宠物列表和提交领养申请。权限不需要做得很重,但用户身份要判断,否则后台接口裸奔会被答辩老师问住。前后端页面我按角色区分菜单,接口层面用拦截器校验登录状态就足够。这样的设计既控制复杂度,又回应了“权限管理”这个常见的答辩问题。

2. 数据库设计:宠物管理系统的表结构是灵魂

2.1 核心表结构总览

表结构规划是这套项目的起点,我按业务关系拆成五张核心表:

  • sys_user:用户表,存放管理员和普通用户,字段id、username、password、nickname、role、create_time、update_time、delete_flag。
  • pet_breed:宠物品种表,字段id、breed_name、category(猫/狗),做字典数据。
  • pet:宠物信息表,字段id、pet_name、breed_id、gender、age、health_status、status、avatar、description、create_time、update_time、delete_flag。
  • pet_vaccine:疫苗记录表,字段id、pet_id、vaccine_name、vaccine_time、remark。
  • adoption_record:领养记录表,字段id、pet_id、user_id、apply_time、audit_status、audit_remark、adopt_time。

给最核心的pet表建表SQL如下:

CREATE TABLE `pet` ( `id` bigint NOT NULL AUTO_INCREMENT, `pet_name` varchar(50) NOT NULL COMMENT '宠物名', `breed_id` bigint DEFAULT NULL COMMENT '品种ID', `gender` tinyint DEFAULT '1' COMMENT '性别: 1公 2母', `age` int DEFAULT NULL COMMENT '年龄(月)', `health_status` varchar(100) DEFAULT NULL COMMENT '健康状况', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态: 1可领养 2已领养 3休息', `avatar` varchar(255) DEFAULT NULL COMMENT '图片地址', `description` varchar(500) DEFAULT NULL COMMENT '描述', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, `delete_flag` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除', PRIMARY KEY (`id`), KEY `idx_breed` (`breed_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宠物信息表';

MySQL 8+ 和 5.7 在字符集上的处理有差异,项目里统一utf8mb4,避免中文乱码。外键我没有建物理外键,只用索引和代码逻辑来维护关系。原因很简单:物理外键在删除宠物时容易造成不必要的约束麻烦,而且答辩时如果能说清“为什么不用外键”,反而是加分项。

2.2 字段设计时容易被忽略的细节

状态字段用tinyint而不是varchar。如果直接用“可领养”“已领养”“休息”,显示是方便,但代码里到处是中文比较,改一个词要改所有代码。我用tinyint存状态值,前端去映射中文文案。后端定义常量或枚举类,比如PetStatusEnum,代码可读性和可维护性都好很多。

时间字段统一datetime,别用timestamp。timestamp有2038问题,而且会带时区干扰。配合MyBatis-Plus的自动填充,@TableField(fill = FieldFill.INSERT) 就能在插入时自动写入create_time,不用每次手动set。逻辑删除delete_flag必须加。宠物信息尤其是救助站数据,误删后想恢复就很麻烦。MyBatis-Plus里配置@TableLogic,查询时自动过滤删除数据,非常省心。

索引不要盲目建。当前系统最频繁的查询是宠物列表按品种、状态、名称模糊查询,所以在pet表上建了idx_breed和idx_status。模糊搜索用LIKE '%name%'无法走常规索引,数据量不大时没关系,但如果将来宠物上万条,建议改上Elasticsearch或者全文索引,暂时不需要过度设计。

2.3 初始化数据与SQL脚本管理

源码包里的sql目录我放两个文件:init.sql(建库建表)和data.sql(初始化数据)。data.sql里至少包含:

  • 两个测试账号:admin/admin123,user/user123;
  • 宠物品种基础数据:中华田园猫、英国短毛猫、金毛、拉布拉多等;
  • 两三条宠物演示数据。

导入顺序一定是先init再data,很多新手直接双击data.sql报错,就是因为表不存在。另外,如果你的项目已经上线或者提交给老师,建议把脚本按版本维护,比如sql/v1.0_init.sql、sql/v1.1_add_adoption.sql。不要永远只有一份“最终版”,否则隔几个月想升级都不知道当初改了什么。这是个很实用的职业习惯。

3. Spring Boot后端搭建与核心接口实现

3.1 工程结构与依赖引入

先用Spring Initializr创建一个Spring Boot项目,Java版本就用8或11,Spring Boot版本建议2.7.x。为什么不用Spring Boot 3?3.x要求Java 17,很多课程设计机器上还跑着JDK 8,而且部分老版本MyBatis-Plus兼容性有坑。2.7.x已经足够稳定,等你熟练了再升级也不迟。

后端包结构如下:

com.example.petadmin ├── config ├── controller ├── entity ├── mapper ├── service │ └── impl ├── common │ ├── Result.java │ └── JwtUtil.java └── PetAdminApplication.java

pom.xml核心依赖:

依赖用途
spring-boot-starter-webWeb支持
mybatis-plus-boot-starterORM,减少SQL
mysql-connector-javaMySQL驱动
jjwt-api/impl/jacksonJWT生成解析
lombok减少实体类样板代码

application.yml数据源配置:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleteFlag

url里必须带serverTimezone=Asia/Shanghai,否则连接MySQL 8会报时区错误。这些配置都是实际踩坑后确定的,少一个都可能让你多折腾一小时。

3.2 宠物模块的增删改查实现

Controller层我习惯写得很薄,只做参数接收和结果返回:

@RestController @RequestMapping("/api/pet") public class PetController { @Autowired private PetService petService; @GetMapping("/page") public Result page(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, String keyword, Long breedId, Integer status) { return Result.success(petService.pagePet(pageNum, pageSize, keyword, breedId, status)); } @PostMapping public Result add(@RequestBody @Validated Pet pet) { petService.addPet(pet); return Result.success(); } @PutMapping public Result update(@RequestBody @Validated Pet pet) { petService.updatePet(pet); return Result.success(); } @DeleteMapping("/{id}") public Result delete(@PathVariable Long id) { petService.deletePet(id); return Result.success(); } }

分页参数一律从页码和size走,不要自己再用PageHelper,MyBatis-Plus自带分页插件。配置分页拦截器时别忘注入PaginationInnerInterceptor,否则分页失效,这是特别常见的新手坑。

Service层记得加事务注解@Transactional。提交领养申请时,要同时修改宠物状态和插入领养记录,只用Controller直接操作多个mapper一定会有隐患。在AdoptionService里完成事务控制,保证“申请失败不会改动宠物状态”。

3.3 登录鉴权与全局异常处理

这个项目没有引入Spring Security,而是用JWT + 拦截器实现登录态校验。原因很直接:课程设计重点是业务逻辑,Spring Security那套过长的过滤器链会分散精力。但完全不鉴权又说不过去,所以我写了JwtUtil工具类,登录成功后生成token,前端每次请求通过Authorization头带过来,拦截器统一校验。

核心逻辑:

  • LoginController校验用户名密码,生成token返回;
  • WebConfig注册拦截器,放行 /api/login,其他路径都校验;
  • 拦截器解析失败时抛出BusinessException,全局异常处理器转成401响应。

全局异常处理类:

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result handleBusiness(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } @ExceptionHandler(Exception.class) public Result handleException(Exception e) { return Result.error(500, "系统繁忙"); } }

这样前端拿到任何异常都是统一的JSON结构,弹提示就行,不用到处try catch。

3.4 后端开发中常见问题

我整理了几个实际编码中遇到的问题。

第一,实体类字段和数据库字段映射。MyBatis-Plus默认开启驼峰转下划线,所以Java里的petName能映射到pet_name。如果你自己写XML,别忘记在application.yml里开启map-underscore-to-camel-case。

第二,Jackson序列化时间。默认情况下LocalDateTime会序列化成数组,很难看。需要在application.yml里配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

第三,跨域问题。开发阶段前端跑在5173,后端8080,直接请求会被浏览器拦截。我在后端写了CorsConfig放行指定origin,或者更推荐在前端Vite配置代理,后文会讲。

第四,上传文件大小限制。宠物头像如果走本地上传,Spring Boot默认单文件最大1MB,需要手动改spring.servlet.multipart.max-file-size,否则大图传不上去。

4. Vue前端开发:从环境配置到页面落地

4.1 环境准备与安装依赖

前端我选Vue 3 + Vite + Element Plus,因为Vite启动速度快,模板比Vue CLI干净。Node.js建议用18 LTS版本,不要用太老的12,否则依赖安装时会报错。装好Node后先换镜像:

npm config set registry https://registry.npmmirror.com

然后创建项目:

npm create vite@latest pet-web -- --template vue cd pet-web npm install npm install element-plus axios vue-router@4 pinia

第一次执行npm install如果报错,多半是Node版本和依赖不匹配,先执行node -v查看版本。另一个常见问题是网络问题导致安装一半失败,删掉node_modules和package-lock.json,重新install即可。

这里要提一下:很多人以为npm install成功就能直接启动,实际还需要在main.js里注册Element Plus:

import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import router from './router' import { createPinia } from 'pinia' const app = createApp(App) app.use(ElementPlus) app.use(router) app.use(createPinia()) app.mount('#app')

4.2 路由、状态管理与axios封装

路由设计不复杂:

const routes = [ { path: '/login', component: Login }, { path: '/', component: Layout, redirect: '/pets', children: [ { path: 'pets', component: PetList }, { path: 'pets/edit/:id', component: PetEdit }, { path: 'adoptions', component: AdoptionAudit } ] } ]

编辑页面我用路由参数id,而不是用Pinia去存当前编辑对象。原因是用户刷新页面时,Pinia里没数据会变成空白,而URL里的参数能保证刷新后依然拿到正确的ID。这是“vue路由参数”一个非常实际的使用场景。

axios封装是每个项目必须做的。我建了src/utils/request.js,实例设置baseURL为/api,请求拦截器里从localStorage取token塞到Authorization头,响应拦截器里对HTTP 401做跳转登录,对业务code非200统一用ElMessage提示。否则每个页面都要写一遍错误处理,代码会翻倍。

4.3 宠物管理页面实战

宠物列表页是核心页面,包含:搜索区(keyword、品种、状态)、新增按钮、el-table列表、el-pagination分页。表格列:宠物名、品种、性别、年龄、状态、操作。状态列用el-tag展现不同颜色,例如“可领养”绿色、“已领养”蓝色。

新增/编辑我用el-dialog内嵌el-form,表单里宠物名必填,品种用el-select,数据从后端品种接口拉。提交前先通过表单校验,再调用后端接口。这里的“前端校验 + 后端校验”双保险很重要,前端提升体验,后端保证安全。宠物名、品种、状态都是必填字段,失血过多需要明确提示用户。

如果你想快速看到页面效果,可以先写死一点假数据,把表格和分页调通再对接接口。但我不建议一直用假数据,因为联调阶段最常见的“数据格式对不上”问题,一定是真实接口才能暴露出来的。

4.4 联调时常见错误

本地联调时,Vite默认端口是5173,后端是8080,最简单的解决办法是在vite.config.js里配代理:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端请求/api/xxx就转发到了后端。我见过很多人在代码里写死http://localhost:8080,然后打开浏览器报跨域,其实就是没配代理或者代理生效没重启。改完配置文件一定要重启dev server。

另一个常见错误是请求方式对不上。后端用@RequestBody接收JSON,前端axios.post默认就是JSON,没问题。但我见过有同学手动把Content-Type改成application/x-www-form-urlencoded,后端立刻报HttpMessageNotReadableException。建议前端不要自定义Content-Type,用axios默认的application/json即可。

时间格式问题也很常见:后端返回的LocalDateTime在JSON里默认带T,页面显示“2024-05-01T10:30:00”,很难看。我在后端做了jackson全局配置,前端也可以在表格列里做格式化,两选一。推荐后端直接格式化好,前端少写方法。

5. 前后端联调、打包与部署实录

5.1 接口规范与联调流程

前后端分离项目,接口规范是最重要的“合同”。这套项目统一返回:

{ "code": 200, "message": "success", "data": { } }

凡是code不是200,前端一律在axios响应拦截器里弹出message。注意分页接口的返回格式也要固定,我建议data里包含records和total两个字段,前端分页组件直接塞。

联调前先做一件事:打开后端控制台,把MyBatis-Plus的SQL日志打开。之前yml里配置的StdOutImpl就会打印每条SQL。如果前端页面数据不对,先看后端SQL执行结果,能省掉大量无意义的猜测。其次是让前端先用Postman或Apifox把“宠物分页查询”等关键接口测通,再写页面代码,避免两边同时出问题不知道怀疑谁。

5.2 前端打包与Spring Boot集成部署

如果你只需要交付一个jar包,最省事的做法是把前端dist目录复制到后端src/main/resources/static下,然后重新打包:

npm run build cp -r dist/* ../backend/src/main/resources/static/ cd ../backend mvn clean package -DskipTests java -jar target/pet-admin.jar

启动后直接访问http://localhost:8080/ 就是前端页面,静态资源和/api接口同域,也不存在跨域了。

如果项目打算长期运行且访问量稍大,我更喜欢用Nginx分开部署。前端静态文件交给Nginx,接口反向代理到Spring Boot:

server { listen 80; server_name pet.example.com; root /opt/pet-web/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }

注意try_files那句不能省,否则刷新页面到/pets/edit/1会出现404,因为前端是history路由。

5.3 运行时问题排查与优化

项目跑起来后遇到最多的是两类问题:接口报错和页面慢。

接口报错先看后端日志,日志里能看到异常栈,基本能定位。如果是数据库连接失败,检查MySQL服务是否启动、账号密码是否正确。如果是404,先确认请求路径和自己的Controller映射是否一致。如果是空指针,大概率是查出来对象为null,比如宠物删除后领养记录还指向它,代码里需要先判断再取值。

页面慢通常不是后端慢,而是前端打包后没做路由懒加载。我在路由配置中用动态import,例如:

const PetList = () => import('../views/PetList.vue')

这样首屏只加载通用框架,进入对应路由才加载对应JS,体验会明显提升。数据库层面给查询多的表加了索引,因为数据量不大,暂时没有更深的优化。另外JVM启动参数可以适当设置,比如java -jar -Xms256m -Xmx512m pet-admin.jar,避免个人服务器内存不够被杀掉。

6. 文档、源码组织与后续扩展

6.1 文档怎么写才有人看

这套项目的一大卖点是“附文档”,但很多文档就是使用说明粘贴了一遍,没价值。我按接手的人最需要的顺序来写:

  1. 项目介绍:一句话说明系统能做什么。
  2. 技术栈:写清楚后端Spring Boot版本、JDK版本、前端Node版本、MySQL版本。
  3. 环境要求:JDK 8+、Node 16+、MySQL 8。
  4. 快速启动:分后端和前端写,包含每个命令。
  5. 数据库导入:init.sql和data.sql的导入步骤,特别强调先建库再执行脚本。
  6. 接口说明:把主要接口列成表格,包含地址、请求方式、参数、返回示例。
  7. 常见问题:收录自己实战中遇到的问题。

文档不是给别人看的,更是给三个月后的自己看的。很多同学写完代码后根本不想写文档,等到答辩前一天才匆忙补,效果很差。我的习惯是每个模块完成就顺手写一段,最后汇总,这样文档永远是热乎的。

6.2 源码目录如何组织才能让接手的人不骂人

最终交付的源码目录结构建议是:

pet-management/ ├── backend/pet-admin/ # Spring Boot后端 ├── frontend/pet-web/ # Vue前端 ├── sql/ │ ├── init.sql │ └── data.sql └── docs/ ├── 快速启动.md ├── 部署文档.md └── 接口文档.md

不要把所有代码平铺在一个文件夹里,前后端至少分开。后端不要传target、前端不要传node_modules,这些是常识,但在课程设计里总是有人打包传上去,导致别人解压后跑不起来以为代码有问题。

后端包名用com.example.petadmin清晰直观。controller统一处理请求,service写业务,mapper只做数据库操作。前端按照views、router、api、utils分层。组件命名用大驼峰,文件名和组件名保持一致。这些规则虽然简单,但对代码可读性提升非常大。

6.3 实际开发过程中我个人比较坚持的几个习惯

第一,接口路径统一以/api开头,方便后续做网关或反向代理。第二,涉及手机号等敏感信息,日志里不要打印明文。宠物管理系统虽然不涉及支付,但用户手机号也是敏感数据。第三,改完代码一定要跑一遍回归测试,至少把宠物增删改查和登录领养这五条主流程走一遍,不要只测自己改的那个接口。

另外,很多初学者会忽略“测试数据”的重要性。data.sql里的演示数据要有真实感,比如宠物名“小橘”“旺财”、品种“中华田园猫”“金毛”,健康状况写“已驱虫”“疫苗齐全”。这种细节在答辩演示时特别加分,因为老师一看就明白系统是能落地的,而不是拿几张空表凑数。

6.4 后续功能还能怎么扩展

这套系统的骨架搭好后,扩展方向很多。比如给宠物增加多图上传,可以接MinIO或阿里云OSS;领养审核流程增加短信通知,可以用阿里云短信服务;想做一个宠物视频展示模块,可以研究HLS切片后在Vue里用video.js播放m3u8流。这里要提醒的是,每加一个功能最好保持“后端接口 + 前端页面 + 数据库脚本 + 文档更新”同步,不要只写代码不补脚本和文档,否则项目会越来越难维护。

我个人的体会是,做管理系统最大的收获不是学会某个框架,而是建立起“用工程化思维拆解一个真实需求”的能力。源码能跑只是及格,能说清楚每个表为什么这么建、每个接口为什么这么设计、每个坑是怎么踩出来的,才是这套项目真正值钱的地方。希望这篇记录能帮你在自己的宠物管理系统项目里少走几步弯路。

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

Spring IOC源码学习:从声明式事务入口拆解代理机制

Spring IOC 源码学习,我从声明式事务的入口点开始拆做了几年 Java 后端,Spring 天天在用,但真正下决心啃一次源码,还是因为被线上一个事务失效的问题折磨到头皮发麻。业务方法明明标了Transactional,异常也抛了&#x…

作者头像 李华
网站建设 2026/9/14 15:29:25

Apache Fesod替代EasyExcel:可控性优先的Excel处理方案

1. 项目概述:从EasyExcel到Apache Fesod的迁移动因 “再见了EasyExcel,我决定用Apache Fesod”——这句话乍看像一句情绪化吐槽,但背后藏着一个在Java生态中反复上演、却长期被低估的现实困境: 当Excel处理需求从“能导出”升级为…

作者头像 李华
网站建设 2026/9/14 15:28:33

内网穿透绑定自定义域名:CNAME解析、HTTPS证书与自动续期实战

做开发这行,内网穿透基本是绕不开的日常工具。早期我用免费工具自带的随机域名,地址又长又没规律,偶尔还得靠收藏夹才能找回来,后来在一次线上联调的时候临时域名被平台回收,整个对接直接卡死,才下定决心给…

作者头像 李华