☰
SpringBoot+Vue宠物信息管理平台毕设全流程开发实战
2026/9/30 11:50:09 网站建设 项目流程

毕设季聊一个前几天被问到最多的题目:Java+Vue的宠物系统。很多人看到"基于SpringBoot+Vue的宠物信息综合管理平台"就开始焦虑,其实这个题目在毕设库里算是性价比很高的那一类。它不像纯粹的电商系统那么重,也不像图书管理系统那样一眼见底,核心业务覆盖了信息展示、内容管理、状态流转、权限控制,刚好是Java后端和Vue前端都能充分发挥的尺度。这篇文章我会直接从架构设计讲到核心模块落地,再到部署演示避坑,按我实际带过的项目来还原整个开发过程,给正在选型或者已经开工的读者一个能直接落地的参考。


1. 选题价值与需求拆解:为什么这个题目适合做毕设

1.1 它覆盖的知识点刚好卡在"够用又不冗余"的位置

毕设选题最忌讳两件事:太简单显得没工作量,太难做到一半自己先崩。这个宠物系统的好处在于,它的业务天然分成两条线:

一条线是信息展示链路:宠物信息、科普文章、活动公告。这类需求在技术实现上涉及分页查询、条件筛选、图片处理、富文本内容展示,难度适中,够你写出一套像样的后台管理逻辑。

另一条线是业务状态链路:用户浏览宠物、提交领养申请、管理员审核、审核通过后变更宠物状态。这条链路涉及前后端交互、数据库状态设计、接口权限控制,正好可以体现你对业务流转的理解,而不是单纯堆CRUD接口。

从评阅老师的角度来看,这样一个系统能讲清楚"谁在用""能做什么""数据怎么流转"三个问题,答辩时就不至于支支吾吾。

1.2 角色与权限的确定决定了整个系统骨架

我见过不少学生一上来就画了六七个角色,最后做出来一堆没人用的空页面。这个系统最合理的角色划分就是两个:

  • 普通用户:浏览宠物、查看科普文章、申请领养、查看申请进度。
  • 系统管理员:管理宠物信息、管理科普内容、审核领养申请、处理用户留言、发布公告。

如果你需要多加一个亮点,可以拆出**“用户管理”**由管理员操作,把用户的预约记录、领养记录都挂在用户维度下,这样页面之间的关联性会强很多,数据看板也有东西可画。

建议:角色控制在三个以内(用户、管理员、游客)。游客只能浏览,用户需要登录后操作,管理员进入独立后台。这样前端的路由守卫、后端的拦截器配置都有了合理的存在理由,不会显得硬凑。

1.3 模块划分与页面清单

基于上面的角色设计,我把整个系统的功能模块拆成下面这张表,开发时可以按这个列表去估工作量:

模块页面/功能主要操作
宠物信息宠物展示、宠物详情、宠物筛选按品种/年龄/性别筛选,查看详情
领养申请提交申请、申请记录、申请进度用户提交表单,管理员审核
知识科普科普文章列表、文章详情浏览、后台发布、编辑、下架
公告管理首页公告、公告详情管理员发布,用户查看
留言反馈用户留言、留言回复用户提交,管理员回复
用户管理登录注册、个人资料、收藏管理员禁用/启用用户
后台管理数据概览、模块管理入口管理员操作

按这个清单,后端的实体类大概是7个,接口约30个左右,前端页面10个上下,工作量非常可控,又完全撑得起一篇毕业论文的架构图和系统设计描述。


2. SpringBoot后端落地:表结构、接口设计与关键代码路径

2.1 数据库表设计的关键取舍

这个项目的表关系不复杂,核心就是三张业务表加用户体系。我在设计时最纠结的地方是领养申请与宠物之间的状态同步问题。

宠物表pet的基本字段:

CREATE TABLE `pet` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '宠物名字', `species` varchar(20) NOT NULL COMMENT '品种:cat/dog/other', `breed` varchar(50) DEFAULT NULL COMMENT '具体品种', `age` int DEFAULT NULL COMMENT '年龄(月)', `gender` tinyint DEFAULT NULL COMMENT '性别:1公 2母', `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0可领养 1已申请 2已领养 3已下架', `cover_image` varchar(255) DEFAULT NULL COMMENT '封面图URL', `images` text COMMENT '更多图片,JSON数组', `description` text COMMENT '宠物描述', `created_at` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里的status字段是整个业务链路的开关。用户在详情页看到宠物状态为"可领养"时才能提交申请;提交申请后,管理员还未审核时宠物状态应为"已申请",防止其他用户重复申请同一条宠物;管理员审核通过后改为"已领养"。这个状态机逻辑必须在后端处理,不能依赖前端控制。

领养申请表adoption_application:

CREATE TABLE `adoption_application` ( `id` bigint NOT NULL AUTO_INCREMENT, `pet_id` bigint NOT NULL, `user_id` bigint NOT NULL, `reason` varchar(500) DEFAULT NULL COMMENT '领养理由', `phone` varchar(20) DEFAULT NULL, `address` varchar(200) DEFAULT NULL, `experience` varchar(500) DEFAULT NULL COMMENT '养宠经验', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0待审核 1已通过 2已拒绝 3已取消', `apply_time` datetime DEFAULT NULL, `review_time` datetime DEFAULT NULL, `review_note` varchar(255) DEFAULT NULL COMMENT '审核意见', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有一个我实际踩过坑的点:一开始我在申请表里冗余了pet_name字段,觉得列表页展示方便,但后来发现改名时会出现数据不一致。后面直接用关联查询,或者在前端拿到pet_id后通过接口补查。毕设阶段不建议在表里做过多的反范式设计,冗余字段会导致你答辩时被问到"如果数据不一致怎么办",很难答。

用户表就用 Spring Security 或 Sa-Token 配套的用户模型扩展即可,额外加avatar、phone、status字段就够用。不建议自己手写Session管理,框架自带机制已经足够了。

2.2 接口设计:路径规划与返回结构

接口设计是答辩时最容易被深挖的地方。我推荐的路由风格是 Restful 风格,但不要过度设计。下面是个人认为比较合理的接口清单:

  • POST /api/auth/register,注册
  • POST /api/auth/login,登录
  • GET /api/pet/page,分页获取宠物列表,支持species、status筛选
  • GET /api/pet/{id},获取宠物详情
  • POST /api/pet,管理员新增宠物
  • PUT /api/pet/{id},管理员编辑宠物
  • PUT /api/pet/{id}/status,修改宠物上下架状态
  • POST /api/adopt/apply,提交领养申请
  • GET /api/adopt/my,当前用户查看自己的申请记录
  • GET /api/admin/adopt/list,管理员查看所有申请,支持按状态筛选
  • POST /api/admin/adopt/review,管理员审核申请(通过/拒绝)
  • GET /api/article/page,科普文章分页列表
  • GET /api/article/{id},文章详情
  • POST /api/admin/article,管理员发布文章
  • PUT /api/admin/article/{id},修改文章
  • DELETE /api/admin/article/{id},删除文章

统一返回结构我使用的是最通用的Result<T>类:

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

这里有个细节值得注意:不要为了省事直接返回Map或者裸JSONObject。统一返回结构能让你在写前端axios拦截器的时候少掉很多判断逻辑。后面接前端的时候,后端返回什么格式就由这个类一锤定音,整个项目前后端对接口的速度快很多。

2.3 领养申请的状态流转实现

状态流转是这个项目的业务核心。我的实现思路是这样的:

AdoptService中处理提交申请的代码逻辑:

@Transactional public Result<Void> applyAdopt(AdoptApplyDTO dto) { // 1. 查询宠物信息 Pet pet = petMapper.selectById(dto.getPetId()); if (pet == null) { return Result.error("宠物不存在"); } // 2. 校验状态 if (pet.getStatus() != 0) { return Result.error("该宠物暂不可领养"); } // 3. 校验用户是否已申请过 Integer count = applicationMapper.selectCount( new LambdaQueryWrapper<AdoptionApplication>() .eq(AdoptionApplication::getPetId, dto.getPetId()) .eq(AdoptionApplication::getUserId, SecurityUtil.getCurrentUserId()) .ne(AdoptionApplication::getStatus, 3)); // 排除已取消的申请 if (count > 0) { return Result.error("您已申请过该宠物,请勿重复申请"); } // 4. 创建申请记录 AdoptionApplication application = new AdoptionApplication(); BeanUtils.copyProperties(dto, application); application.setUserId(SecurityUtil.getCurrentUserId()); application.setStatus(0); application.setApplyTime(new Date()); applicationMapper.insert(application); // 5. 锁定宠物状态 pet.setStatus(1); petMapper.updateById(pet); return Result.success(null); }

注意@Transactional注解一定要加,因为第4步和第5步是对两个表的操作,如果中间抛异常会导致申请记录存在但宠物状态没更新,数据就不一致了。

审核逻辑同理,管理员通过后需要把pet.status改成2(已领养),同时把同宠物其他待审核的申请自动置为"已拒绝",这一步一定要做。我见过一个项目没处理这个,审核通过一条申请之后,其他用户的申请记录里还显示"审核中",用户去查看详情才发现宠物已经被领走了,体验非常差。

2.4 文件上传与静态资源映射

宠物系统的图片处理是避不开的。SpringBoot 里处理文件上传很简单,但有两个坑必须提前规避:

第一个是上传路径的问题。Windows 本地开发时路径分隔符和 Linux 服务器不一样,我建议把上传目录做成可配置项,放在application.yml里:

app: upload: path: ./uploads/ url-prefix: /uploads/**

然后写一个 WebMvc 配置类做静态资源映射:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Value("${app.upload.path}") private String uploadPath; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/uploads/**") .addResourceHandler(uploadPath); } }

第二个是图片访问404的问题。很多学生上传成功后就拿本地绝对路径往数据库里存,比如C:\Users\xxx\Desktop\...,换一台电脑或者打包部署就全挂了。正确的做法是在数据库里存相对路径/uploads/2024/xx.jpg,前端用VITE_API_BASE_URL + '/uploads/...'拼接访问。


3. Vue前端实现:从页面组织到接口对接的完整思路

3.1 技术选型与工程初始化

Vue 端的选型我比较推荐直接用Vue 3 + Vite + Pinia + Element Plus。Vite 的启动速度比 Webpack 快太多,毕设演示调试时体验会好很多,不用每次改一行代码等半天热更新。Element Plus 的表格、表单、弹窗组件能省下大量的界面工作,让前端代码更聚焦在业务逻辑上,而不是反复手写样式。如果你对 Vue 2 更熟,用 Vue 2 + Element UI 也没问题,这个项目没有任何技术栈上的硬性约束,你答辩时能讲清楚自己项目用到的技术就行。但如果你是从零开始学,我建议直接学 Vue 3,现在新的课程、插件生态基本都在 Vue 3 侧。

工程初始化命令:

npm create vue@latest pet-front cd pet-front npm install npm install axios pinia element-plus @element-plus/icons-vue

3.2 路由设计:前后端两种页面,一套路由

这个系统的页面分成"用户侧"和"管理侧"两块。我推荐用路由的 children 嵌套配合动态 import 来组织:

const routes = [ { path: '/', component: () => import('@/layout/UserLayout.vue'), children: [ { path: '', component: () => import('@/views/home/HomePage.vue') }, { path: 'pets', component: () => import('@/views/pet/PetList.vue') }, { path: 'pets/:id', component: () => import('@/views/pet/PetDetail.vue') }, { path: 'articles', component: () => import('@/views/article/ArticleList.vue') }, { path: 'articles/:id', component: () => import('@/views/article/ArticleDetail.vue') }, { path: 'adopt/my', component: () => import('@/views/adopt/MyApplications.vue') }, { path: 'profile', component: () => import('@/views/user/UserProfile.vue') }, ], }, { path: '/admin', component: () => import('@/layout/AdminLayout.vue'), meta: { requiresAdmin: true }, children: [ { path: '', component: () => import('@/views/admin/Dashboard.vue') }, { path: 'pets', component: () => import('@/views/admin/AdminPetList.vue') }, { path: 'pets/edit/:id?', component: () => import('@/views/admin/AdminPetEdit.vue') }, { path: 'adoptions', component: () => import('@/views/admin/AdminAdoptionList.vue') }, { path: 'articles', component: () => import('@/views/admin/AdminArticleList.vue') }, { path: 'articles/edit/:id?', component: () => import('@/views/admin/AdminArticleEdit.vue') }, { path: 'users', component: () => import('@/views/admin/AdminUserList.vue') }, ], }, { path: '/login', component: () => import('@/views/LoginPage.vue') }, { path: '/register', component: () => import('@/views/RegisterPage.vue') }, ]

路由守卫是实现角色权限控制的关键位置:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAdmin) { const userInfo = localStorage.getItem('userInfo') if (!token) { next('/login') } else if (userInfo?.role !== 1) { next('/') // 普通用户直接回首页 } else { next() } } else { next() } })

这里的一个细节:不要把用户角色信息只存在前端。虽然这个项目里后端每个接口几乎都会校验权限,但前端的路由守卫只是做展示层控制,真正的安全屏障必须在后端。答辩时也要强调这点,这是加分项。

3.3 Axios 封装与 token 管理

Axios 封装几乎是前端必考面试题,毕设里写好也非常加分。我的封装思路是:

import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 10000, }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res } if (res.code === 401) { // token 失效 localStorage.removeItem('token') localStorage.removeItem('userInfo') router.push('/login') } ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) }, error => { ElMessage.error(error.message || '网络异常') return Promise.reject(error) } ) export default request

登录后的 token 统一放入 localStorage,后端JWT校验。需要注意,如果后端用了Sa-Token或者自定义拦截器,实际 token 格式会有所差异,前端配合后端调整Authorization前缀即可。

3.4 核心页面的组件划分思路

拿宠物详情页举例,我推荐的组件划分是这样的:

  • PetDetail.vue— 页面容器,负责数据获取和路由参数读取
  • PetInfoCard.vue— 宠物信息展示卡片,包含图片轮播、基本信息
  • PetAdoptForm.vue— 领养申请表单组件,通过dialog弹出
  • PetStatusTag.vue— 宠物状态标签,根据 status 渲染不同颜色

组件划分合理,答辩的时候可以讲组件复用和通信方式。比如PetStatusTag在列表页和详情页都用到,这就是一个很自然的复用场景。

3.5 用户侧的关键体验:图片加载与状态展示

宠物领养系统是一个以图片为主的信息平台。图片处理上,我建议前端统一封装一个PetCover.vue组件:

  • 加载中显示占位图
  • 加载失败显示默认图
  • 统一处理object-fit: cover样式
<template> <div class="pet-cover"> <el-image :src="src" fit="cover" lazy > <template #placeholder> <div class="image-placeholder">加载中...</div> </template> <template #error> <div class="image-placeholder">图片加载失败</div> </template> </el-image> </div> </template>

这个小组件能避免非常常见的裸图片alt空白问题。尤其是部署后图片地址配错时,一张张裂开的图片非常影响答辩观感。


4. 联调、部署与答辩演示:最容易翻车的地方全踩了一遍

4.1 前后端联调的常见问题

联调阶段最常遇到的问题有三个。

第一个是跨域。本地开发时前端在localhost:5173,后端在localhost:8080,必须处理跨域。最简单的方案是在后端加配置:

@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); } }

但注意allowedOriginPatterns和allowCredentials(true)必须配合使用,单独使用*加allowCredentials(true)会被浏览器拦截。

第二个是接口路径前缀不一致。前端baseURL里写了/api,后端controller的 RequestMapping 又没有/api前缀,导致所有请求404。这个排查起来特别耗时间,建议后端所有接口统一以/api开头,前端baseURL也保持/api,两边对照一目了然。

第三个是时间格式不一致。后端返回LocalDateTime默认格式是2024-02-11T18:30:00,前端如果直接用会显示一个带T的字符串,非常难看。建议在application.yml里配置:

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

4.2 打包部署:从本地到服务器的完整链路

毕设答辩一般有两种形式:自己带电脑演示,或者部署到服务器/远程演示。

如果是本地演示,SpringBoot 项目直接用 IDE 启动,前端用npm run dev启动开发服务器即可。这里的问题在于这是两个不同的端口,演示时浏览器地址栏要同时开着两个地址,切换后台和前台挺乱,还容易暴露前后端分离的结构。

更好的方案是构建部署同一端口。在后端src/main/resources目录下创建static文件夹,将前端npm run build生成的dist目录内容全部拷贝进去。这样 SpringBoot 会同时托管前端静态资源和后端接口,浏览器只需访问http://localhost:8080就能打开系统。

这个方案的好处非常多:

  • 演示时只有一个端口,只开一个页面;
  • 不需要额外安装 Nginx;
  • 部署到云服务器只打一个 jar 包,几分钟搞定。

需要注意,如果选择打包进后端,前端的baseURL就不能写带具体端口的绝对地址。开发时可以用环境变量区分:

// .env.development VITE_API_BASE_URL = 'http://localhost:8080/api' // .env.production VITE_API_BASE_URL = '/api'

这样开发时走跨域代理,生产环境走同源,非常顺畅。

4.3 答辩演示的几个细节

答辩演示中,下面这几项看起来很普通,但每年都有学生在这里失误:

  1. 准备一个演示账号。至少准备一个管理员账号和一个普通用户账号,密码简单好记。纸上写下来备用,如果现场网络不好连登录都费劲,基本就完蛋了。

  2. 提前准备演示素材。宠物图片不要用网上随便找来的低清图,建议百度搜索关键词找一些高清无版权的宠物图片;科普文章内容也要通畅完整。这部分功夫不下足的话,展示效果会大打折扣。

  3. 预埋一条业务数据链路。比如提前把某个宠物设置为"可领养",演示时现场提交申请、现场审核通过,一气呵成。这条链路是项目的核心亮点,现场演示的效果远好于静态讲界面。

  4. 关闭 IDE 自动保存外的其他干扰项。答辩现场开着屏幕可能弹消息,把无关软件提前清理掉。用浏览器演示建议用无痕窗口,避免因登录信息串号等意外。

4.4 演示过程中最常被问到的问题清单

答辩时评阅老师经常围绕这个系统问的问题,大概有这几个方向:

  • 数据一致性问题:为什么申请通过后要同步修改宠物状态?你用了事务吗?
  • 权限控制问题:用户直接访问后台管理地址怎么办?前端路由守卫和后端接口校验的关系?
  • 表设计问题:为什么领养申请表里不存宠物名?如果有多个用户同时申请同一只宠物怎么办?
  • 扩展性问题:如果要在系统中增加宠物医疗记录模块,需要在哪些地方改动?

这些问题我建议在答辩前自己先写一遍答案,不要现场临时组织语言。核心答痛点:事务控制、接口权限、状态机设计。如果这三个方向都能说明白,老师的问题基本就都覆盖了。


5. 从毕设到作品集:这个系统还能怎么延伸

学业结束之后,这个项目如果只是被封存在一个文件夹里其实挺可惜。从我带项目的经验看,下面几个进阶方向能让这个系统从"毕设作品"变成"简历亮点":

第一个方向是数据可视化。在管理后台加一个统计面板,展示领养申请趋势、宠物种类分布、各品种申请量Top10。技术实现上可以用 ECharts 画柱状图和饼图,后端给几个聚合统计接口。这一步的技术含量不高,但视觉效果很直观,简历作品集里会很出彩。

第二个方向是地图交互。如果想让系统更有特色,可以给宠物信息加上"所在地点",在首页嵌入地图组件展示附近的可领养宠物。这里要注意的是,地图 SDK 的配置流程非常繁琐,尽量提前一周以上预留联调时间。

第三个方向是消息通知。当前系统里用户提交申请后,只能自己登录查看进度。如果需要更完整的业务闭环,可以加一套通知机制:审核通过或拒绝后发送站内信。技术实现可以是简单的数据库消息表,也可以使用 WebSocket 做实时通知推送,具体根据自身基础选型。

第四个方向是测试与代码质量。给后端的 Service 层写几个单元测试,给前端配置 ESLint + Prettier,把项目跑在 GitHub Actions 上做持续集成。这些内容答辩时不一定能演示,但在简历上写"熟悉 JUnit 单元测试、CI/CD 基础"是实打实的加分项,含金量比多写一个没有业务意义的 CRUD 模块高得多。

从我实际带项目的体会来说,这个宠物平台题目的空间足够深挖,也能作为后续求职作品集里的完整项目案例。关键是不要停留在"把功能做出来",而是把业务设计逻辑和技术选型理解到位,这样不管是答辩还是面试,才能讲得清楚、答得从容。希望在大家手里的这份 SpringBoot + Vue 宠物系统,不只是完成为毕业而毕业的任务,也能成为带你真正走进全栈开发大门的第一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询