
做宠物管理系统这个项目最早是给学生准备的一套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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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/admin123user/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或11Spring Boot版本建议2.7.x。为什么不用Spring Boot 33.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.javapom.xml核心依赖依赖用途spring-boot-starter-webWeb支持mybatis-plus-boot-starterORM减少SQLmysql-connector-javaMySQL驱动jjwt-api/impl/jacksonJWT生成解析lombok减少实体类样板代码application.yml数据源配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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: deleteFlagurl里必须带serverTimezoneAsia/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走不要自己再用PageHelperMyBatis-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: GMT8第三跨域问题。开发阶段前端跑在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 vitelatest pet-web -- --template vue cd pet-web npm install npm install element-plus axios vue-router4 pinia第一次执行npm install如果报错多半是Node版本和依赖不匹配先执行node -v查看版本。另一个常见问题是网络问题导致安装一半失败删掉node_modules和package-lock.json重新install即可。这里要提一下很多人以为npm install成功就能直接启动实际还需要在main.js里注册Element Plusimport { 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 Bootserver { 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 文档怎么写才有人看这套项目的一大卖点是“附文档”但很多文档就是使用说明粘贴了一遍没价值。我按接手的人最需要的顺序来写项目介绍一句话说明系统能做什么。技术栈写清楚后端Spring Boot版本、JDK版本、前端Node版本、MySQL版本。环境要求JDK 8、Node 16、MySQL 8。快速启动分后端和前端写包含每个命令。数据库导入init.sql和data.sql的导入步骤特别强调先建库再执行脚本。接口说明把主要接口列成表格包含地址、请求方式、参数、返回示例。常见问题收录自己实战中遇到的问题。文档不是给别人看的更是给三个月后的自己看的。很多同学写完代码后根本不想写文档等到答辩前一天才匆忙补效果很差。我的习惯是每个模块完成就顺手写一段最后汇总这样文档永远是热乎的。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流。这里要提醒的是每加一个功能最好保持“后端接口 前端页面 数据库脚本 文档更新”同步不要只写代码不补脚本和文档否则项目会越来越难维护。我个人的体会是做管理系统最大的收获不是学会某个框架而是建立起“用工程化思维拆解一个真实需求”的能力。源码能跑只是及格能说清楚每个表为什么这么建、每个接口为什么这么设计、每个坑是怎么踩出来的才是这套项目真正值钱的地方。希望这篇记录能帮你在自己的宠物管理系统项目里少走几步弯路。