☰
SpringBoot+Vue宠物领养系统实战:业务建模、状态机与答辩避坑指南
2026/10/15 21:40:55 网站建设 项目流程

简介:这份资源是面向计算机专业毕业生与答辩准备的宠物领养系统答辩PPT,配套SpringBoot+Vue.js技术栈的毕业设计项目,适合需要完成开题、答辩或参考同类Web系统开发的学生使用。压缩包内共1个pptx文件,大小约2.65MB,内容涵盖选题背景与意义、开发环境说明、管理员与用户双角色功能结构设计、系统操作流程及结论致谢等答辩核心模块,可直接用于答辩演示或作为PPT排版与内容组织参考。目前已有1484人学习下载,说明该选题与技术方案在毕业设计中具有较高关注度。通过这份PPT,读者能快速梳理SpringBoot后端与Vue.js前端结合的B/S架构项目脉络,理解宠物领养、认领审核、教学视频与感谢信等模块的设计思路,并借鉴其功能结构图与流程图的呈现方式,为自身答辩材料撰写和项目讲解提供实用参考。

1. 从一份答辩 PPT 反推:宠物领养系统到底该怎么落地

很多同学做毕业设计,选题定了“宠物领养系统”,技术栈选了 SpringBoot + Vue,代码吭哧吭哧写完,最后卡在答辩 PPT 上——不是不会做 PPT,而是讲不清楚这套系统到底解决了什么问题、技术选型为什么这么定、数据表为什么这么设计。答辩老师问一句“你这个领养流程怎么防止一只宠物被重复领养”,当场就懵了。

这篇笔记不聊 PPT 排版,聊的是这套系统本身。把 SpringBoot + Vue 宠物领养系统从业务建模、接口设计、状态机流转到部署验证整条链路拆开讲一遍。适合正在做类似选题的在校开发者、需要快速搭一套前后端分离管理系统的初中级工程师,以及想搞清楚“一个真实业务系统该长什么样”的 Vue 后端入门者。读完你至少能自己把领养主流程跑通,并且知道答辩时哪些点最容易被追问。

2. 领养业务建模:为什么不能只有一张宠物表

2.1 领养系统的核心实体与关系

很多人第一版设计就是三张表:用户表、宠物表、领养记录表。跑起来没问题,但一答辩就露馅。真实领养业务里,宠物有“待领养 / 审核中 / 已领养 / 下架”多个状态,领养申请有“待审核 / 通过 / 拒绝 / 已取消”多个状态,而且申请和宠物之间是一对多关系——同一只宠物可能同时收到多个申请,但最终只能有一个通过。

所以最小可用的实体至少是五个:用户(user)、宠物(pet)、领养申请(adoption_apply)、宠物分类(category)、操作日志(operation_log)。操作日志这张表很多人会忽略,但答辩时老师特别爱问“你怎么追踪一只宠物从发布到被领养的全过程”,有这张表你就能答上来。

关系上,用户和宠物是一对多(一个用户能发布多只宠物),用户和领养申请是一对多(一个用户能提交多个申请),宠物和领养申请也是一对多(一只宠物能被多次申请)。最终“通过”的那条申请,会把宠物状态锁死为“已领养”。

2.2 数据表字段设计与状态枚举

下面这张表是我一般会用的宠物表核心字段,直接抄就行:

字段名类型说明
idbigint主键,自增
namevarchar(50)宠物昵称
category_idint分类外键
age_monthint月龄,比直接存年龄好算
gendertinyint0 未知 1 公 2 母
health_statustinyint0 健康 1 观察中 2 治疗中
adopt_statustinyint0 待领养 1 审核中 2 已领养 3 下架
publisher_idbigint发布人
create_timedatetime发布时间

领养申请表的关键字段是 pet_id、applicant_id、apply_status(0 待审核 1 通过 2 拒绝 3 取消)、apply_reason、audit_time、auditor_id。注意 apply_status 和 pet 的 adopt_status 是两套状态,不要混用,这是答辩高频追问点。

2.3 状态流转图与并发控制思路

领养主流程的状态流转是这样的:宠物发布后 adopt_status=0,有人提交申请后变成 1(审核中),管理员审核通过后变成 2(已领养),同时该宠物的其他待审核申请全部置为 2(拒绝)。如果审核拒绝,宠物回到 0。

这里有个坑:两个人同时点“申请领养”,如果代码里只是先查再改,会出现两只申请都通过的情况。常见做法是在更新宠物状态时加乐观锁,用update pet set adopt_status=2 where id=? and adopt_status=1,看影响行数是否为 1,为 0 就说明被别人抢先了,直接回滚事务并提示“该宠物已被领养”。

-- 审核通过时的核心更新,利用 where 条件做乐观锁 UPDATE pet SET adopt_status = 2, update_time = NOW() WHERE id = #{petId} AND adopt_status = 1; -- 同时把该宠物其他待审核申请批量拒绝 UPDATE adoption_apply SET apply_status = 2, audit_time = NOW() WHERE pet_id = #{petId} AND apply_status = 0 AND id != #{applyId};

这两条 SQL 必须放在同一个@Transactional方法里,任何一条影响行数为 0 都要抛异常回滚。参数说明:petId 是宠物主键,applyId 是本次通过的申请主键,audit_time 记录审核时间用于后续追溯。

3. 后端接口分层:SpringBoot 里怎么把领养流程写干净

3.1 Controller-Service-Mapper 三层职责划分

SpringBoot 项目最容易写乱的地方就是 Controller 里塞业务逻辑。我的习惯是 Controller 只做参数校验和结果封装,Service 写业务规则和事务,Mapper 只做单表 CRUD。以“提交领养申请”为例,Controller 收到请求后先校验用户是否登录、宠物是否存在,然后调 Service 的 apply 方法。

@RestController @RequestMapping("/api/adoption") public class AdoptionController { @Autowired private AdoptionService adoptionService; @PostMapping("/apply") public Result apply(@RequestBody @Valid ApplyDTO dto, HttpServletRequest request) { // 从拦截器塞进 request 的 userId 取当前登录用户 Long userId = (Long) request.getAttribute("userId"); adoptionService.apply(dto.getPetId(), userId, dto.getReason()); return Result.success(); } }

逻辑说明:@Valid触发 DTO 上的注解校验,比如 petId 不能为空、reason 长度限制。userId 不从请求体取,而是从登录态取,防止前端伪造。参数说明:ApplyDTO 至少包含 petId 和 reason 两个字段。

3.2 领养申请接口的参数校验与幂等处理

幂等这块很多人不做,答辩时被问到“用户手抖点了两次申请怎么办”就答不上来。简单做法是在 adoption_apply 表上加唯一索引(pet_id, applicant_id, apply_status)不行,因为拒绝后还能再申请。更稳的做法是提交前先查该用户对该宠物是否有“待审核”的申请,有就直接返回“请勿重复提交”。

public void apply(Long petId, Long userId, String reason) { Pet pet = petMapper.selectById(petId); if (pet == null || pet.getAdoptStatus() != 0) { throw new BizException("该宠物当前不可申请"); } // 幂等:同一用户对同一宠物不能有进行中的申请 int count = applyMapper.countPending(petId, userId); if (count > 0) { throw new BizException("你已提交过申请,请等待审核"); } AdoptionApply apply = new AdoptionApply(); apply.setPetId(petId); apply.setApplicantId(userId); apply.setApplyReason(reason); apply.setApplyStatus(0); applyMapper.insert(apply); // 宠物进入审核中 petMapper.updateStatus(petId, 1); }

参数说明:countPending 对应 SQL 是select count(*) from adoption_apply where pet_id=? and applicant_id=? and apply_status=0。注意这里没有加锁,因为唯一性由业务查询保证,高并发下仍可能穿透,生产环境建议再加分布式锁或数据库唯一约束兜底。

3.3 审核接口的事务边界与回滚条件

审核接口是整个系统最需要事务的地方。通过时要同时做三件事:更新申请状态、更新宠物状态、拒绝其他申请。任何一步失败都必须整体回滚。

@Transactional(rollbackFor = Exception.class) public void audit(Long applyId, Long auditorId, boolean pass) { AdoptionApply apply = applyMapper.selectById(applyId); if (apply == null || apply.getApplyStatus() != 0) { throw new BizException("申请不存在或已处理"); } if (pass) { int rows = petMapper.lockAdopted(apply.getPetId()); if (rows == 0) { throw new BizException("该宠物已被其他人领养"); } applyMapper.updateStatus(applyId, 1, auditorId); applyMapper.rejectOthers(apply.getPetId(), applyId); } else { applyMapper.updateStatus(applyId, 2, auditorId); // 拒绝后宠物回到待领养,但仅当没有其他待审核申请时 int pending = applyMapper.countPendingByPet(apply.getPetId()); if (pending == 0) { petMapper.updateStatus(apply.getPetId(), 0); } } }

逻辑说明:rollbackFor = Exception.class保证受检异常也回滚。lockAdopted 就是前面那条乐观锁 SQL。rejectOthers 批量拒绝同宠物其他待审核申请。参数说明:pass 为 true 表示通过,false 表示拒绝;auditorId 记录审核人。

4. 前端 Vue 页面:领养列表与申请表单怎么串起来

4.1 宠物列表页的数据请求与分页

Vue 这边我用 Vue3 + Element Plus 的组合居多,列表页核心就是调后端分页接口。请求参数带上 pageNum、pageSize、categoryId、adoptStatus 四个,后端用 PageHelper 或 MyBatis-Plus 的分页插件返回。

// 宠物列表页的请求封装 import request from '@/utils/request' export function fetchPetList(params) { return request({ url: '/api/pet/list', method: 'get', params: { pageNum: params.pageNum || 1, pageSize: params.pageSize || 10, categoryId: params.categoryId || null, adoptStatus: params.adoptStatus !== undefined ? params.adoptStatus : null } }) }

逻辑说明:adoptStatus 用!== undefined判断而不是直接||,因为 0 是有效状态(待领养),用||会被当成 falsy 丢掉。参数说明:pageNum 从 1 开始,pageSize 默认 10,categoryId 和 adoptStatus 为 null 时后端不拼查询条件。

4.2 申请弹窗的表单校验规则

申请领养是个弹窗,里面一个 textarea 填理由,加一个提交按钮。校验规则至少两条:理由不能为空、长度不超过 200 字。用 Element Plus 的 Form 组件自带规则就行。

const rules = { reason: [ { required: true, message: '请填写领养理由', trigger: 'blur' }, { min: 10, max: 200, message: '理由长度在 10 到 200 字之间', trigger: 'blur' } ] } const submitApply = async () => { await formRef.value.validate() await applyAdoption({ petId: currentPetId.value, reason: form.reason }) ElMessage.success('申请已提交,请等待审核') dialogVisible.value = false refreshList() }

逻辑说明:validate 不通过会抛异常,后面的代码不执行。提交成功后关闭弹窗并刷新列表,让用户看到宠物状态变成“审核中”。参数说明:currentPetId 是当前操作的宠物 id,form.reason 绑定 textarea。

4.3 审核状态在前端的展示与轮询刷新

用户提交申请后,怎么知道审核结果?简单做法是在“我的申请”页面加一个手动刷新按钮,进阶做法是轮询。我一般用setInterval每 30 秒拉一次申请列表,页面卸载时清掉定时器。

onMounted(() => { loadMyApplies() timer = setInterval(loadMyApplies, 30000) }) onUnmounted(() => { clearInterval(timer) })

逻辑说明:定时器必须清理,否则页面切走后还在请求,属于典型内存泄漏。参数说明:30000 毫秒即 30 秒,太短会给后端压力,太长用户觉得卡。

5. 避坑与排查:答辩前一定要自己跑一遍的 5 个点

5.1 跨域配置写了但 OPTIONS 请求 403

现象:前端调接口浏览器报 CORS 错误,后端日志显示 OPTIONS 请求被拦截。原因:SpringBoot 的拦截器把 OPTIONS 预检请求也拦了,没放行。解决:在拦截器的 preHandle 里判断if (HttpMethod.OPTIONS.matches(request.getMethod())) return true;,或者在 WebMvcConfigurer 里配 addCorsMappings 时允许所有方法和头。

5.2 领养状态更新了但列表没变

现象:审核通过后数据库里宠物状态是 2,但前端列表还显示“待领养”。原因:前端用了缓存或者没重新请求,也可能是后端返回的是旧对象。解决:审核成功后前端主动调一次列表接口;后端在更新后重新 select 一次再返回,别直接返回更新前的实体。

5.3 时间字段前后端差 8 小时

现象:数据库存的是正确时间,前端显示少了 8 小时。原因:后端返回的是 Date 对象,Jackson 默认按 UTC 序列化。解决:在 application.yml 里配spring.jackson.time-zone=GMT+8和date-format=yyyy-MM-dd HH:mm:ss,或者实体字段上加@JsonFormat注解。

5.4 图片上传后访问 404

现象:宠物图片上传成功,但前端 img 标签加载不出来。原因:上传目录不在静态资源映射路径下。解决:在 WebMvcConfigurer 里加addResourceHandlers,把上传目录映射到/upload/**,前端用/upload/xxx.jpg访问。注意路径结尾要带斜杠,这是血泪经验。

5.5 答辩时被问“为什么用 Vue 不用 Thymeleaf”

现象:老师觉得前后端分离没必要。原因:这是选型问题,不是技术问题。解决:从三个角度答——前后端分离便于分工、Vue 的组件化适合管理后台、接口可复用给小程序或 App。别硬说 Thymeleaf 不好,说场景不同就行。

6. 答辩前最后一公里:用 Postman 把主流程串一遍

代码写完不等于系统能跑。我一般会在答辩前一天用 Postman 把“注册 → 登录 → 发布宠物 → 提交申请 → 审核通过 → 查询结果”整条链路手动跑一遍,确认每个接口的返回结构和状态码都对。这一步能提前暴露 80% 的演示翻车风险。

具体做法是建一个 Collection,把六个接口按顺序排好,用环境变量存 token 和 petId。登录接口的返回里取 token 存到环境变量,后续接口在 Header 里带Authorization: Bearer {{token}}。发布宠物接口的返回里取 id 存成 petId,申请接口直接引用。这样点一次 Run 就能全链路跑通。

// Postman 登录接口的 Tests 脚本,自动提取 token const res = pm.response.json() if (res.code === 200) { pm.environment.set('token', res.data.token) pm.test('登录成功', () => pm.expect(res.code).to.eql(200)) } else { pm.test('登录失败', () => pm.expect.fail(res.msg)) }

逻辑说明:这段脚本在登录请求返回后执行,把 token 写进环境变量供后续请求使用。参数说明:res.code 是后端统一返回结构的业务码,200 表示成功;res.data.token 是 JWT 令牌。

跑通之后,把每个接口的请求参数和返回示例截图放进 PPT 的“系统测试”章节,比放一堆代码截图有说服力得多。老师问“你怎么证明系统能用”,你直接翻到这一页,说“这是完整链路的接口测试记录”,比任何解释都管用。

最后一个习惯:答辩前把数据库清空,重新造一批演示数据,别用开发时乱七八糟的测试数据。宠物名字起得像样点,别叫“测试1”“aaa”,老师看到这种数据会默认你系统没认真做。这个细节我踩过坑,后来每次演示前都花十分钟重新造数据,效果立竿见影。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询