简介:一份基于Spring Boot与Vue的宠物救助及领养平台全套源码毕业设计资源,面向Java方向毕业设计、课程设计及需要快速搭建前后端分离项目的开发者。平台围绕管理员与救助者两类角色展开,覆盖用户、流浪动物、领养信息、救助信息、论坛及系统管理等完整业务模块,可直接作为项目原型或二次开发基础。压缩包共676个文件,包含148个Java后端文件、109个Vue前端文件、63个JS脚本、数据库SQL脚本及说明文档,可支撑从环境配置到运行调试的完整流程;整体大小36.58MB,目录结构清晰。已有62人学习浏览,适合正在完成毕设或希望理解Spring Boot与Vue整合实践的用户。附带数据库设计与项目说明文档,能帮助理解表结构、业务逻辑与部署要点,PPT便于汇报展示,有效降低从零搭建平台的学习成本。
1. 宠物救助及领养平台是什么:一套三件套毕设能解什么实际问题
拿到标注着「宠物救助及领养平台」的 java 毕业设计源码包,里面基本就是一套标准组合:springboot 写后端、vue 写管理后台与用户端、mysql 存业务数据,外加说明文档和 LW(论文相关文档)。它解决的是救助站线下流程的痛点——流浪宠物信息靠朋友圈转发、领养申请靠微信私聊、审核与回访全靠纸质登记,信息一多就乱,宠物是否被领养、回访做没做没人说得清。平台把这些动作搬上线:管理员发布待领养宠物,用户在线提交领养申请,状态从「待审核」一路走到「已领养」,全程留痕。对正在做毕设的人,最有价值的不是代码量,而是整条业务链路能讲明白;对想低成本搭内部管理系统的从业者,这是一个能直接二次开发的起点。多数人拿到手先点启动,然后卡在数据库连接和前端代理上半小时,这篇按真实复现顺序把路走通。
2. 技术选型与源码结构:SpringBoot、Vue、MySQL 在救助领养场景里各司其职
2.1 后端:SpringBoot + MyBatis-Plus,用最少代码把 CRUD 和审核流程立住
SpringBoot 在这一类项目里的位置不用多解释:内嵌 Tomcat、starter 机制、约定优于配置,java 毕设选它省掉一堆 XML 配置,跑起来就是一个独立 jar。数据访问层常见做法是 MyBatis-Plus,单表 CRUD 几乎不用写 SQL,QueryWrapper 一拼就出结果;但你要清楚它的边界——一旦涉及多表联查,它反而不方便,我一般直接在 Mapper 里写@Select注解或 XML 映射,别硬用它的 Wrapper 拼 join。
源码结构通常是三层:controller 接收请求、service 写业务规则、mapper 做数据访问,resource 目录下放着 mapper XML 和 application.yml。这一层里最该先看的是数据表设计,因为它决定了你能改出什么功能。以宠物信息表为例,核心字段是「状态」,不要存字符串,用 tinyint 枚举:
DROP TABLE IF EXISTS pet_info; CREATE TABLE pet_info ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT '宠物昵称', species varchar(20) NOT NULL COMMENT '物种:dog/cat/other', breed varchar(50) DEFAULT NULL COMMENT '品种', age varchar(20) DEFAULT NULL COMMENT '月龄或年龄范围', gender tinyint(1) DEFAULT NULL COMMENT '0未知 1公 2母', health_status varchar(200) DEFAULT NULL COMMENT '健康状态描述', status tinyint(2) NOT NULL DEFAULT 0 COMMENT '0待救助 1救助中 2待领养 3已领养 4已下架', cover_image varchar(255) DEFAULT NULL COMMENT '封面图URL', detail_images text COMMENT '多图URL,逗号分隔', description text COMMENT '救助经历/性格描述', shelter_id bigint(20) DEFAULT NULL COMMENT '救助站或发布者用户ID', create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宠物信息表';这张表有四个参数值得细看。status用 tinyint 而非 varchar,业务里既要做列表筛选又要做状态流转,数字枚举比字符串更省空间且不会拼错;detail_images用逗号分隔字符串存多图,适合毕设这种图片量不大的场景,不需要再建一张图片子表;cover_image存相对路径而非完整 URL,这样部署时改一个前缀就能换图片访问地址;shelter_id指向用户表,把「发布者」和「救助站」统一成用户维度,比单独建救助站表省事,这也是源码里最常见的做法。
2.2 前端:Vue + Element UI,把它当成拆好的页面模板而不是负担
Vue 侧的源码包通常结构是 src/api 封装请求、src/router 定义路由、src/views 放页面,管理后台的长相千篇一律:el-table 列表加 el-pagination 分页,点按钮弹 el-dialog 表单,提交后刷新列表。对毕设来说这不是缺点,反而是最容易改、最不容易翻车的部分。vue 入门阶段的同学最该关注两个点:路由守卫和 axios 拦截器,它们决定了「谁登录后才能进哪个页面」。
axios 拦截器是前后端联动的关键,很多源码里已经写好,但你要看得懂才能改:
import axios from 'axios' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API || '/api', timeout: 10000 }) // 请求拦截器:把登录后存的 token 放进请求头 service.interceptors.request.use(config => { const token = localStorage.getItem('pet_token') if (token) { config.headers['Authorization'] = token } return config }, error => Promise.reject(error)) // 响应拦截器:code=401 时清掉 token 回登录页 service.interceptors.response.use(res => { const code = res.data.code if (code === 401) { localStorage.removeItem('pet_token') window.location.href = '/login' return Promise.reject(new Error('登录已过期')) } return res.data }, error => Promise.reject(error))这里两个参数最容易和后端打架:请求头名字Authorization,后端拦截器取的时候叫token还是Authorization必须对齐;baseURL取的是环境变量VUE_APP_BASE_API,如果 .env.development 里把它写成了绝对地址http://localhost:8080/api,下面第 5 章讲的跨域问题就会找上门。响应拦截器对 code 的约定也是,后端返回的字段是code还是status,要打开后端统一返回类确认一遍再动手改页面。
2.3 说明文档与 LW:源码包里的「解释层」,答辩和二次开发都靠它
拿到源码先别急着启动,把说明文档和 LW(论文文档)翻开看一遍。毕设级的说明文档通常由三块构成:需求分析与功能模块图、数据库设计(ER 图加核心表说明)、核心流程与测试记录,刚好对应答辩时老师爱问的「为什么做、数据怎么存、流程怎么跑」。对从业者来说它更像一份交接文档——接手别人代码时最缺的就是这层解释。
我的习惯是倒着读:先看文档里的表清单,再去 SQL 里核对表名,最后回到实体类和 controller。如果文档里的表名和源码 SQL 对不上,说明这份代码可能是多版本合并的,后面踩坑概率会高不少。这一步花二十分钟,能帮你省下后面两小时的排错时间。
3. 本地跑通这套 java 毕设源码:数据库初始化、后端配置与前端联调的四个可复现步骤
3.1 第一步:建库建表,字符集必须和表定义一致
源码包里的 SQL 脚本是整个项目的地基,最稳的顺序是先建一个空库,再导入表结构和初始化数据。直接用 root 账号执行:
# 创建数据库,字符集和表定义保持一致,避免中文乱码 mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS pet_adopt_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 导入源码包里的 SQL 脚本,路径以你解压的位置为准 mysql -u root -p pet_adopt_db < sql/pet_adopt_db.sql # 确认表都建出来了,顺便看一下有多少张 mysql -u root -p -e "USE pet_adopt_db; SHOW TABLES;"utf8mb4不是可选项:业务里有用户填写的救助经历描述和领养理由,emoji 和生僻字在旧版 utf8 下会直接变成问号,这是最容易在演示时翻车的细节。导入前先打开 SQL 文件看第一行,如果脚本里自带CREATE DATABASE和USE,就不需要前面的建库步骤,直接导入即可,但要注意脚本里的库名可能和源码 application.yml 里写的不一致,等下配后端时要改对齐。
3.2 第二步:SpringBoot 侧 application.yml,四个参数决定能不能起来
MySQL 就绪后,打开后端的 application.yml,这几乎是每次排错的第一现场。典型的 SpringBoot 配置长这样:
spring: datasource: url: jdbc:mysql://localhost:3306/pet_adopt_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB server: port: 8080 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl file: upload-dir: ./upload/逐个说明:3306/pet_adopt_db里的库名要和 3.1 建的一致;serverTimezone=Asia/Shanghai是为了避 MySQL 8 时区报错;driver-class-name在 MySQL 8.0 下必须写成com.mysql.cj.jdbc.Driver,如果是 5.7 环境则要改回com.mysql.jdbc.Driver,这一行错了启动必挂;allowPublicKeyRetrieval=true是 MySQL 8 用 caching_sha2_password 插件时常见的坑,不加会连不上。log-impl配成 StdOutImpl 是为了让 SQL 打到控制台,排错时能看清每步执行了什么语句,跑通后可以删掉。
改完密码和库名,在 backend 目录执行mvn spring-boot:run,看到Started开头的日志说明后端已经起来。如果秒退,别急着查代码,先看第 5 章的第一条坑。
3.3 第三步:前端依赖安装与 devServer 代理,vue 路由别动错
前端这边步骤固定:进 frontend 目录装依赖、配代理、起服务。依赖安装卡住时,常见做法是换 npm 镜像源,这个不展开。关键是 vue.config.js 里的 devServer.proxy,它决定浏览器请求怎么转发到后端:
// vue.config.js const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '/api' } } } } })target必须指向后端实际端口,和 application.yml 里的server.port对齐;pathRewrite这里故意写成了原样替换,因为后端接口本身就带/api前缀。如果你发现后端接口不带前缀,这里才需要把^/api替换成空字符串。前端端口我用 3000 是为了和后端 8080 区分,避免「页面打开的是后端接口文档」这种混淆。同时检查 .env.development 里的VUE_APP_BASE_API,正确值是/api,不是完整的http://localhost:8080/api。
依赖装好后执行npm run serve,浏览器访问http://localhost:3000看到登录页就说明前端这半边通了。
3.4 第四步:用 curl 验证前后端联通,别急着登录
前端起来后,先用最粗暴的方式确认后端接口真的能访问,避免把「后端没起来」误判成「前端写错了」。在另一个终端执行:
# 不登录也能访问的公开接口,通常是宠物列表或公告 curl "http://localhost:8080/api/pet/list?page=1&size=10" # 再验证一次带 /api 前缀的路径是否经过代理 curl "http://localhost:3000/api/pet/list?page=1&size=10"第一条命令直接打后端,返回 JSON 数组说明后端服务正常;第二条打前端端口,如果能返回同样 JSON,说明 vue.config.js 的代理生效。两次都返回空白或 HTML 时,去后端控制台看有没有请求日志,没有日志就是端口或代理问题。到这里,前后端联通这条路就算打通了,接下来才能进入业务功能的调试。
4. 核心业务实现:从宠物登记到领养回访的表结构与状态机
4.1 角色与权限:管理员、救助站、普通用户三条线的数据模型
这类平台的角色一般分成三种:管理员负责审核和整体管理,救助站负责发布待领养宠物、录入救助和回访记录,普通用户提交领养申请。在数据模型上的体现就是 user 表加一个 role 字段,而不是拆三张表。权限控制在后端用拦截器做:按 URL 前缀和角色判断能否访问。
一个简单但实用的拦截器规则是:/api/admin/**只允许 role=admin,/api/shelter/**允许 admin 和 shelter,/api/user/**登录即可,/api/pet/list这类公开接口直接放行。前端 vue 路由再配一套按角色渲染菜单的逻辑,双端校验。如果你的源码包把救助站做成了独立表,也能跑,只是查询宠物时要多 join 一次,改起来稍麻烦。
4.2 救助与领养的宠物状态流转:为什么需要「已下架」这个状态
宠物信息从被发现到被领养,生命周期至少有四个阶段:待救助、救助中、待领养、已领养。很多毕设只做到这四步,但我建议保留第 5 个状态「已下架」——宠物因病去世、找回原主人或不适合开放领养时,不能直接删数据,否则申请记录和历史回访就对不上了。
下面这张表是我常用的一套流转关系,你拿到源码后可以对照看它实现了几个状态:
| 当前状态 | 触发操作 | 目标状态 | 说明 |
|---|---|---|---|
| 0 待救助 | 救助站登记接收 | 1 救助中 | 记录救助时间与地点 |
| 1 救助中 | 完成驱虫/绝育,信息完善 | 2 待领养 | 平台列表可见 |
| 2 待领养 | 管理员审核通过领养申请 | 3 已领养 | 更新领养人 |
| 2 待领养 | 下架处理 | 4 已下架 | 保留履历,不再展示 |
| 任意状态 | 数据修正 | 4 已下架 | 异常数据兜底 |
状态建议统一存在 pet_info.status 字段里,用常量类或枚举类管理,不要在 service 里写魔法数字。列表页永远只展示status=2的数据,这个查询条件会在第 6 章验收时用到。
4.3 领养申请状态机:审核、回访、拒绝的核心表与 Java 实现
领养申请是整个平台业务密度最高的部分,申请单至少要经过:提交申请(待审核)、管理员初审(待回访或已拒绝)、志愿者线下回访(已领养或已拒绝)。对应 ad 表我一般这样建:
DROP TABLE IF EXISTS adopt_application; CREATE TABLE adopt_application ( id bigint(20) NOT NULL AUTO_INCREMENT, pet_id bigint(20) NOT NULL COMMENT '宠物ID', user_id bigint(20) NOT NULL COMMENT '申请人ID', applicant_name varchar(50) NOT NULL COMMENT '申请人姓名', phone varchar(20) NOT NULL, address varchar(255) DEFAULT NULL, reason varchar(500) DEFAULT NULL COMMENT '领养理由', status tinyint(2) NOT NULL DEFAULT 0 COMMENT '0待审核 1初审通过待回访 2已领养 -1已拒绝', reject_reason varchar(200) DEFAULT NULL COMMENT '拒绝原因', create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_pet_id (pet_id), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='领养申请表';三个索引不是摆设:查「某只宠物收到哪些申请」走 idx_pet_id,查「某人申请过什么」走 idx_user_id,列表筛选待审核走 idx_status。审核逻辑的核心是状态迁移的合法性,service 里绝不能无脑 update:
@Service public class AdoptServiceImpl implements AdoptService { @Autowired private AdoptApplicationMapper adoptMapper; @Autowired private PetInfoMapper petMapper; @Transactional(rollbackFor = Exception.class) public boolean audit(Long applicationId, Integer targetStatus, String rejectReason, Long operatorId) { // 1. 先查申请单,防止对不存在的记录操作 AdoptApplication app = adoptMapper.selectById(applicationId); if (app == null) { throw new BizException("申请单不存在"); } // 2. 校验状态迁移合法性:只有待审核能进入待回访或已拒绝 if (app.getStatus() != 0) { throw new BizException("当前状态不允许审核"); } // 3. 更新申请单状态,拒绝时记录原因 app.setStatus(targetStatus); app.setRejectReason(targetStatus == -1 ? rejectReason : null); adoptMapper.updateById(app); // 4. 审核通过时才把宠物置为已领养 if (targetStatus == 2) { PetInfo pet = petMapper.selectById(app.getPetId()); pet.setStatus(3); petMapper.updateById(pet); } return true; } }@Transactional保证申请单和宠物状态两个更新要么都成功要么都回滚,这是审稿老师最容易追问的点。入参targetStatus只允许 1 或 -1 两个值,如果传 2 进来,说明客户端直接把状态跳到了终态,绕过了回访环节,在真实的项目里要再校验一次「是否存在已完成的回访记录」。这一步也是并发问题的集中区,第 4.4 会展开。
4.4 LW 里审稿老师追问最多的三个设计点
第一,为什么用状态字段而不是多张表。对这个规模的项目,工作流引擎是过度设计,用 status 字段加 update_time 足够,但你要能说清楚状态迁移的合法路径。第二,同一宠物收到多条申请怎么处理。常见做法是允许多条申请同时存在,但审核通过前要校验宠物还是「待领养」状态,通过后将其余申请批量置为已拒绝,并给申请人回写一条拒绝理由。这里有个并发坑:两个管理员同时审核同一宠物,都通过了校验再写库,会出现一宠两主。用乐观锁UPDATE pet_info SET status=3 WHERE id=? AND status=2就能兜住。第三,回访记录怎么和领养关联。我习惯建一张 follow_up 表,字段是 adopt_application_id、回访时间、回访结论,一次领养对应多条回访记录,最终审核员凭回访结论决定是否把申请置为已领养。文档里如果只有需求描述没有讲到这个层面,你可以自己补上,答辩时这是一个明显的加分项。
5. 避坑排查:跑这套 SpringBoot + Vue 毕设最容易翻车的五处
这一章写的都是我在帮人调毕设源码时反复遇到、也自己踩过的坑。每条按「现象 → 原因 → 解决」三步整理,你照着排查比盲试快得多。
5.1 启动即秒退:端口占用、数据库连不上、密码不对
现象:执行mvn spring-boot:run后几秒钟进程退出,控制台只有几行日志,甚至没有完整的异常堆栈;或者后端显示 Started 但浏览器访问 8080 一直转圈。
原因:优先级最高的是端口被占,本地跑过其他 SpringBoot 或 Tomcat 占了 8080;其次是数据库连接失败,url 里的库名不存在、MySQL 服务没启动、root 密码和 yml 里对不上。SpringBoot 启动时数据源是懒加载的,很多时候错误要等第一个请求进来才暴露。
解决:先做两道检查再怀疑代码。在终端执行lsof -i:8080(Windows 用netstat -ano | findstr :8080),看端口被哪个进程占着,占着就换端口或杀掉旧进程;再执行mysql -u root -p -e "SELECT 1"确认 MySQL 本身活着。然后把 yml 里的密码复制粘贴到命令行里试一次,排除肉眼看不到的空格和中文引号。日志没打异常时,把log-impl配成 StdOutImpl 再跑一次,SQL 和连接错误会直接刷出来。这一套下来,八成启动问题都能定位。
5.2 登录后一直跳回登录页:拦截器白名单与 token 名不一致
现象:登录接口返回成功,接口也能调到数据,但只要刷新页面就跳回 /login,或者登录后访问列表一直 401。
原因:三个嫌疑。后端拦截器没放行登录接口和静态资源,导致拿 token 的请求本身被拦截,前端拿不到 token 自然进不去;前端请求头传的字段名和后端拦截器取的名字不一致,后端每次都认为 token 不存在;token 过期时间被源码作者设得太短,比如 30 分钟,演示到一半就失效。
解决:先开后端日志,看被拦截的 URL 是哪一个。然后在 WebMvcConfigurer 的拦截器注册处,把登录注册接口、用户注册、宠物列表放行:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns( "/api/user/login", "/api/user/register", "/api/pet/list", "/api/pet/detail/**", "/upload/**", "/error" ); } }再核对前端 axios 拦截器里config.headers['Authorization']和后端取 token 的字段名,两边必须完全一致。前端路由守卫里如果只判断 token 存在与否,还要注意 localStorage 的 key 和后端过期时间对得上。改完清一次浏览器 localStorage 再重新登录,别让旧 token 干扰排查。
5.3 前端页面白屏、Network 请求全部 ERR_CONNECTION_REFUSED
现象:npm run serve 正常,浏览器打开 3000 端口能看到页面框架,但数据区空白,打开开发者工具 Network 面板,请求地址指向 http://localhost:8080 且连接被拒绝。
原因:最典型的是 .env.development 里的VUE_APP_BASE_API被写成了http://localhost:8080/api这个绝对地址。axios 拿它拼完整请求 URL 后,浏览器直接跨源请求后端,devServer 的 proxy 完全被绕过。这就是跨域问题最常被说成「玄学」的原因——其实根源只有一个,请求发往的来源和后端不是一个源。
解决:把环境变量改回相对路径/api,让请求先打到前端 3000 端口,再由 vue.config.js 的 proxy 转发到 8080。顺便确认 target 里的端口和 application.yml 的 server.port 一致。改完环境变量要重新执行npm run serve,因为 .env 文件只在启动时读取一次。
// 错误示例 axios.defaults.baseURL = 'http://localhost:8080/api' // 正确示例:.env.development 里写 VUE_APP_BASE_API=/api5.4 图片上传成功但页面裂图
现象:上传接口返回 URL,数据库也有路径,但 img 标签的 src 打开是 404,或者地址栏出现了C:\...这种本地路径。
原因:图片保存在本地磁盘./upload/目录,但 SpringBoot 默认不把upload路径映射成可访问的静态资源;另一种是存储时用了绝对路径,Windows 下的反斜杠被拼进 URL,导致浏览器解析失败。数据库里存相对路径、访问时拼前缀,才是正经做法。
解决:加一个静态资源映射配置,把/upload/**指到磁盘目录:
@Configuration public class WebConfig implements WebMvcConfigurer { @Value("${file.upload-dir:./upload/}") private String uploadDir; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadDir); } }注意uploadDir结尾要带/,file:前缀不能少,否则路径拼接会错位。改完重启后端,访问http://localhost:8080/upload/xxx.jpg能打开图片就通了。另一个隐性坑是热重启后图片文件还在但映射丢失,检查一下运行目录下的 upload 文件夹是否被 IDE 重建过。
5.5 MySQL 5.7 与 8.0 的差异:驱动、时区与 SQL 模式
现象:报ClassNotFoundException: com.mysql.jdbc.Driver;或者The server time zone value ...一长串乱码;或者 SQL 语句报only_full_group_by相关的错误。
原因:MySQL 版本切换时,驱动类名从 5.7 的com.mysql.jdbc.Driver变成 8.0 的com.mysql.cj.jdbc.Driver,url 里还要加serverTimezone;only_full_group_by是 8.0 默认开启的 SQL 模式,旧源码里SELECT * ... GROUP BY的写法会让查询直接报错。
解决:驱动类和时区按 3.2 的配置改。only_full_group_by的报错要看具体 SQL——把 select 里除分组字段外的列都加进 GROUP BY,或者用MAX(create_time)这类聚合函数包起来。比如查「同一宠物被多次申请」:
-- 错误:8.0 下会报 only_full_group_by SELECT pet_id, COUNT(*) FROM adopt_application GROUP BY pet_id; -- 正确:非聚合字段显式处理 SELECT pet_id, COUNT(*) AS cnt, MAX(create_time) AS last_time FROM adopt_application GROUP BY pet_id HAVING cnt > 1;如果不想改源码里的一堆 SQL,也可以在 my.cnf 里关掉only_full_group_by,但这是治标不治本,答辩时老师问起来反而说不清。这个坑也提醒你:拿到源码先确认它的建表 SQL 是在哪个 MySQL 版本下写的,再决定本地装 5.7 还是 8.0。
6. 答辩验证与二次开发:把「能跑」变成「讲得清」,再加两个加分功能
6.1 一条能讲明白的验收主线
跑通不叫完成,能按业务主线走一遍并说出每一步对应的表和接口,才算真的掌握了这套源码。我建议按下面这条路径做验收:
| 步骤 | 操作角色 | 预期结果 | 关联表/接口 |
|---|---|---|---|
| 1 | 注册普通用户 | 登录成功,未登录访问受限 | user / user/login |
| 2 | 管理员发布宠物(带图) | 宠物状态为待领养,列表可见 | pet_info / pet/add |
| 3 | 用户提交领养申请 | 申请状态为待审核 | adopt_application / apply/save |
| 4 | 管理员初审通过 | 申请变为待回访 | adopt_application / apply/audit |
| 5 | 录入一次回访记录 | 回访列表出现记录 | follow_up / followup/save |
| 6 | 管理员置为已领养 | 宠物状态变已领养,列表不再展示 | pet_info status=3 |
每走一步,打开 Navicat 看一眼对应表的 status 字段变化,再用数据库的更新时间和控制台 SQL 日志对照。答辩时老师问你「数据从哪来、状态怎么变」,你能把这条链路讲顺,就已经赢过一半拿着源码却不看库的同学。
6.2 两个低成本加分改造:重复申请校验与导出领养记录
第一个改造是重复申请校验。用户对同一只宠物反复提交申请,会让审核列表出现大量垃圾数据。在提交申请的服务里加一道查询:同用户同宠物存在状态为 0 的申请就拒绝,代码量很小,但能体现你对业务的理解:
Long count = adoptMapper.selectCount(new LambdaQueryWrapper<AdoptApplication>() .eq(AdoptApplication::getPetId, petId) .eq(AdoptApplication::getUserId, userId) .eq(AdoptApplication::getStatus, 0)); if (count > 0) { throw new BizException("你已提交过申请,请等待审核"); }第二个改造是导出领养记录。用 EasyExcel 或 POI 给审核列表加一个「导出」按钮,后端按状态和时间范围筛选后生成 Excel,几百行代码就能实现。这个功能在答辩演示时非常直观,而且说明文档里可以顺理成章加一章「报表导出模块」的设计描述。如果想把演示环境收敛成一个进程,把前端npm run build的产物复制到后端src/main/resources/static目录下,SpringBoot 会同时托管页面和接口,一个 jar 包就能跑完整套系统,这也是「vue 打包放进 springboot」最常见的部署方式。
我接手过的毕设源码不少,栽过的跟头基本都没逃出第五章那五类。现在的习惯是:拿到任何 springboot 项目,第一件事先看 SQL 和 application.yml,确认库能连、端口没冲突,再点启动。磨刀的时间永远比排错的时间便宜,希望这篇能把你的启动成本压到最低,答辩顺利。
本文还有配套的精品资源,点击获取