ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue宠物信息管理平台毕设全流程开发实战

SpringBoot+Vue宠物信息管理平台毕设全流程开发实战 毕设季聊一个前几天被问到最多的题目JavaVue的宠物系统。很多人看到基于SpringBootVue的宠物信息综合管理平台就开始焦虑其实这个题目在毕设库里算是性价比很高的那一类。它不像纯粹的电商系统那么重也不像图书管理系统那样一眼见底核心业务覆盖了信息展示、内容管理、状态流转、权限控制刚好是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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里的status字段是整个业务链路的开关。用户在详情页看到宠物状态为可领养时才能提交申请提交申请后管理员还未审核时宠物状态应为已申请防止其他用户重复申请同一条宠物管理员审核通过后改为已领养。这个状态机逻辑必须在后端处理不能依赖前端控制。领养申请表adoption_applicationCREATE 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有一个我实际踩过坑的点一开始我在申请表里冗余了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}删除文章统一返回结构我使用的是最通用的ResultT类Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }这里有个细节值得注意不要为了省事直接返回Map或者裸JSONObject。统一返回结构能让你在写前端axios拦截器的时候少掉很多判断逻辑。后面接前端的时候后端返回什么格式就由这个类一锤定音整个项目前后端对接口的速度快很多。2.3 领养申请的状态流转实现状态流转是这个项目的业务核心。我的实现思路是这样的AdoptService中处理提交申请的代码逻辑Transactional public ResultVoid 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 LambdaQueryWrapperAdoptionApplication() .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 vuelatest pet-front cd pet-front npm install npm install axios pinia element-plus element-plus/icons-vue3.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 classpet-cover el-image :srcsrc fitcover lazy template #placeholder div classimage-placeholder加载中.../div /template template #error div classimage-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: GMT84.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 答辩演示的几个细节答辩演示中下面这几项看起来很普通但每年都有学生在这里失误准备一个演示账号。至少准备一个管理员账号和一个普通用户账号密码简单好记。纸上写下来备用如果现场网络不好连登录都费劲基本就完蛋了。提前准备演示素材。宠物图片不要用网上随便找来的低清图建议百度搜索关键词找一些高清无版权的宠物图片科普文章内容也要通畅完整。这部分功夫不下足的话展示效果会大打折扣。预埋一条业务数据链路。比如提前把某个宠物设置为可领养演示时现场提交申请、现场审核通过一气呵成。这条链路是项目的核心亮点现场演示的效果远好于静态讲界面。关闭 IDE 自动保存外的其他干扰项。答辩现场开着屏幕可能弹消息把无关软件提前清理掉。用浏览器演示建议用无痕窗口避免因登录信息串号等意外。4.4 演示过程中最常被问到的问题清单答辩时评阅老师经常围绕这个系统问的问题大概有这几个方向数据一致性问题为什么申请通过后要同步修改宠物状态你用了事务吗权限控制问题用户直接访问后台管理地址怎么办前端路由守卫和后端接口校验的关系表设计问题为什么领养申请表里不存宠物名如果有多个用户同时申请同一只宠物怎么办扩展性问题如果要在系统中增加宠物医疗记录模块需要在哪些地方改动这些问题我建议在答辩前自己先写一遍答案不要现场临时组织语言。核心答痛点事务控制、接口权限、状态机设计。如果这三个方向都能说明白老师的问题基本就都覆盖了。5. 从毕设到作品集这个系统还能怎么延伸学业结束之后这个项目如果只是被封存在一个文件夹里其实挺可惜。从我带项目的经验看下面几个进阶方向能让这个系统从毕设作品变成简历亮点第一个方向是数据可视化。在管理后台加一个统计面板展示领养申请趋势、宠物种类分布、各品种申请量Top10。技术实现上可以用 ECharts 画柱状图和饼图后端给几个聚合统计接口。这一步的技术含量不高但视觉效果很直观简历作品集里会很出彩。第二个方向是地图交互。如果想让系统更有特色可以给宠物信息加上所在地点在首页嵌入地图组件展示附近的可领养宠物。这里要注意的是地图 SDK 的配置流程非常繁琐尽量提前一周以上预留联调时间。第三个方向是消息通知。当前系统里用户提交申请后只能自己登录查看进度。如果需要更完整的业务闭环可以加一套通知机制审核通过或拒绝后发送站内信。技术实现可以是简单的数据库消息表也可以使用 WebSocket 做实时通知推送具体根据自身基础选型。第四个方向是测试与代码质量。给后端的 Service 层写几个单元测试给前端配置 ESLint Prettier把项目跑在 GitHub Actions 上做持续集成。这些内容答辩时不一定能演示但在简历上写熟悉 JUnit 单元测试、CI/CD 基础是实打实的加分项含金量比多写一个没有业务意义的 CRUD 模块高得多。从我实际带项目的体会来说这个宠物平台题目的空间足够深挖也能作为后续求职作品集里的完整项目案例。关键是不要停留在把功能做出来而是把业务设计逻辑和技术选型理解到位这样不管是答辩还是面试才能讲得清楚、答得从容。希望在大家手里的这份 SpringBoot Vue 宠物系统不只是完成为毕业而毕业的任务也能成为带你真正走进全栈开发大门的第一步。
返回列表