ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

宠物领养救助管理系统:Spring Boot+MyBatis毕业设计实战

宠物领养救助管理系统:Spring Boot+MyBatis毕业设计实战

简介:为计算机相关专业学生度身定制的宠物领养救助管理系统毕业设计项目,涵盖救助信息登记、领养申请、审核流转、通知记录等功能场景,适合用于课程设计、毕设答辩或就业作品。从数据库建模到前后端联调,从接口设计到身份权限校验,系统完整展示了项目各层面开发要点;通过宠物档案、领养人、救助记录等核心表结构,以及Java后端和JSP/JS/CSS前端界面,可帮助读者快速搭建可运行原型并理解业务闭环。压缩包共236个文件,包含49个java源码、25个jsp页面、29个css样式、23个js脚本、53张项目截图,以及xml配置、属性文件、启动说明等,整体约15.61MB,目录按前端、后端、配置和文档归类,便于按需查阅。若用于毕设,可将宠物信息管理、申请审核、通知触发等模块拆解为独立的分析章节,配合截图展示界面与流程。目前已有297人学习,适合需要参考完整项目结构、扩展业务模块或撰写毕设文档的开发者。

1. 宠物领养救助管理系统:一份能跑起来的毕业设计,先解决“能不能落地”

如果只是想在 GitHub 上找一个“能跑”的毕业设计,很多项目会让人失望:要么依赖装不上,要么数据库脚本和代码对不上。这份宠物领养救助管理系统不一样。它不追求炫技,而是用 Spring Boot + MyBatis + MySQL 的组合,把“宠物展示—领养申请—救助登记—后台审核”这条主流程老老实实走通了。功能上覆盖角色登录、宠物档案、申请管理、救助记录和公告发布,是典型的 Java 单体毕业设计形态。对想搞懂 CRUD 怎么写、业务状态怎么流转的同学来说,它是一份可以直接抄作业的完整样例,而不是一段让人看完就忘的演示代码。

2. 技术选型与架构设计:Spring Boot 单体应用的模块划分和表结构

拿到源码包,我习惯先看两个东西:pom.xml 里引了哪些依赖,sql 目录里建了哪些表。这两处看明白,项目的技术方向和业务边界基本就清楚了。

2.1 为什么选 Spring Boot 单体:技术栈与适用场景分析

这个项目的技术栈很克制,没有引入微服务、消息队列这些毕业设计里用不上的组件。核心依赖只有 Spring Boot Web、MyBatis、MySQL 驱动、Thymeleaf 和 Lombok,前端用 Bootstrap 4 + jQuery。这个组合意味着:只要本机有 JDK 8+ 和 Maven,几乎不会遇到环境层面的拦路虎。

组件版本选型用途说明
Spring Boot2.x项目骨架,内嵌 Tomcat,省去独立部署
MyBatis3.5.x数据访问层,SQL 写在 XML 里方便调优
MySQL5.7 / 8.0 均可数据存储,脚本兼容两种版本
Thymeleaf3.0.x服务端渲染页面,天然适合单体应用
Bootstrap 43.4.x 内置后台管理页面样式,不额外引入前端工程

选 Thymeleaf 而不是前后端分离,是毕业设计场景下很务实的选择。前后端分离意味着要同时维护 Vue 工程和接口工程,跨域、Token、打包都是额外成本。单体模板渲染则简单得多:浏览器请求一个 URL,Controller 返回一个页面,数据在模板里直接取。对“演示完整功能”这个目标来说,完全够用。

2.2 项目结构:从入口到 Mapper 的分层组织方式

源码包解压后,目录结构是标准的 Maven 布局,包名按业务垂直划分。Controller 层只管参数接收和页面跳转,Service 层处理业务逻辑,Mapper 层对应数据库操作。这样的分层对二次开发很友好——改领养流程不需要动页面,改页面也不需要碰 SQL。

src/main/java/com/petadopt/ ├── controller/ # 页面路由与接口入口 │ ├── AdminController.java │ ├── PetController.java │ └── AdoptController.java ├── service/ # 业务逻辑层 │ ├── PetService.java │ ├── AdoptService.java │ └── RescueService.java ├── mapper/ # MyBatis 数据访问接口 │ ├── PetMapper.java │ └── AdoptMapper.java ├── entity/ # 实体类,对应数据库表 │ ├── Pet.java │ ├── User.java │ └── AdoptApply.java ├── config/ # 静态资源映射等配置 └── PetAdoptApplication.java # Spring Boot 启动类

src/main/resources 下有两个关键目录:mapper 存放 MyBatis 的 XML 文件,templates 存放 Thymeleaf 页面。XML 和 Java 接口分开,是我比较喜欢的方式。SQL 语句集中在 XML 里,改动时不用重新编译 Java 代码,也方便后期做 SQL 审查。

2.3 数据库设计:6 张表如何支撑领养与救助两条业务线

数据库是这个项目的核心资产。一共 6 张表,围绕两条业务线展开:领养线和救助线。领养线是用户浏览宠物、提交领养申请、管理员审核;救助线是用户上报流浪动物信息、管理员登记处理。两条线都依赖用户表,状态字段控制流程流转。

-- 宠物档案表:领养业务的核心实体 CREATE TABLE t_pet ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, category_id INT COMMENT '关联宠物分类表', age INT, gender VARCHAR(10), health VARCHAR(200) COMMENT '健康状况描述', photo VARCHAR(255) COMMENT '图片URL', status INT DEFAULT 0 COMMENT '0可领养 1申请中 2已领养', create_time DATETIME );
-- 领养申请表:记录每次申请动作和审核状态 CREATE TABLE t_adopt_apply ( id INT PRIMARY KEY AUTO_INCREMENT, pet_id INT COMMENT '申请哪只宠物', user_id INT COMMENT '谁在申请', reason VARCHAR(500) COMMENT '申请理由', contact VARCHAR(50) COMMENT '联系方式', status INT DEFAULT 0 COMMENT '0待审核 1已通过 2已拒绝', apply_time DATETIME, audit_time DATETIME );

这两张表的设计有个值得注意的点:t_pet 的 status 和 t_adopt_apply 的 status 是两个独立状态位。也就是说,宠物被申请后,它的状态要等管理员审核通过才从“可领养”变成“已领养”,申请单本身有自己的审核进度。这样的设计避免了“用户提交申请后宠物立刻下架”的尴尬——万一申请被拒绝,宠物状态不需要回滚。另外 4 张表分别是用户表、宠物分类表、救助登记表和公告表,救助表的核心字段是 report_time、location 和处理状态,公告表则用于后台发布领养须知。

3. 核心功能拆解:一次领养申请从提交到审核通过的完整调用链

这一章是全文的重头戏。我会跟着一次真实的领养申请,从用户点击“申请领养”开始,到管理员后台审核通过,把整个调用链路的代码逐一拆开讲。

3.1 一次领养申请的完整链路:Controller → Service → Mapper

用户在前台页面看到一只宠物,点击“申请领养”,浏览器会提交一个表单到 /adopt/apply 这个地址。请求首先到达 AdoptController。

@Controller @RequestMapping("/adopt") public class AdoptController { private final AdoptService adoptService; public AdoptController(AdoptService adoptService) { this.adoptService = adoptService; } @PostMapping("/apply") public String apply(@RequestParam Integer petId, @RequestParam String reason, @RequestParam String contact, HttpSession session) { // 从会话中取出当前登录用户 User user = (User) session.getAttribute("loginUser"); if (user == null) { return "redirect:/user/login"; } adoptService.submitApply(petId, user.getId(), reason, contact); return "redirect:/pet/detail?id=" + petId; } }

这段代码的逻辑很直白:从 RequestParam 拿到表单三个字段,从 Session 拿当前用户,然后交给 Service 层。这里有个容易忽略的细节,apply 方法返回的是重定向而不是直接渲染页面,这样做的好处是避免用户刷新页面时重复提交表单。参数说明:petId 是宠物主键,reason 和 contact 都是用户填写的文本内容,这三者缺一不可,模板表单里都做了必填校验。

真正干活的是 AdoptService.submitApply 方法。这里要先检查宠物状态——如果宠物已经被申请或者已领养,就不能再提交申请了。

@Service public class AdoptService { private final AdoptMapper adoptMapper; private final PetMapper petMapper; public AdoptService(AdoptMapper adoptMapper, PetMapper petMapper) { this.adoptMapper = adoptMapper; this.petMapper = petMapper; } @Transactional public void submitApply(Integer petId, Integer userId, String reason, String contact) { // 1. 查询宠物当前状态 Pet pet = petMapper.selectById(petId); if (pet.getStatus() != 0) { throw new RuntimeException("该宠物当前不可领养"); } // 2. 插入领养申请记录 AdoptApply apply = new AdoptApply(); apply.setPetId(petId); apply.setUserId(userId); apply.setReason(reason); apply.setContact(contact); apply.setStatus(0); // 待审核 adoptMapper.insert(apply); // 3. 将宠物状态改为申请中,防止他人重复申请 petMapper.updateStatus(petId, 1); } }

注意第 1 步和第 3 步之间的竞争条件——如果两个用户同时申请同一只宠物,理论上会有一瞬间的并发问题。毕业设计里不做分布式锁,靠 @Transactional 保证“查状态”和“改状态”要么都成功要么都失败,这一层保障在单机部署下已经足够。参数说明中 pet.getStatus() 的取值规则是 0 可领养、1 申请中、2 已领养,这个状态位贯穿整个领养流程。

3.2 宠物状态机:可领养、申请中、已领养的流转逻辑

宠物状态是整个项目中设计得最有章法的部分。它不只是简单的字段更新,而是一个小型状态机。我画不出流程图,但用文字描述很清楚:宠物创建时状态为 0,用户提交申请后变为 1,管理员审核通过后变为 2,管理员拒绝则回到 0。这个流转逻辑全部集中在 AdoptService 的 audit 方法里。

@Transactional public void auditApply(Integer applyId, Integer auditResult) { // 1. 查询申请单 AdoptApply apply = adoptMapper.selectById(applyId); if (apply == null || apply.getStatus() != 0) { throw new RuntimeException("申请单不存在或已审核"); } // 2. 更新申请单状态 adoptMapper.updateStatus(applyId, auditResult); // 1通过 2拒绝 // 3. 更新宠物状态 if (auditResult == 1) { petMapper.updateStatus(apply.getPetId(), 2); // 已领养 } else { petMapper.updateStatus(apply.getPetId(), 0); // 回到可领养 } }

这段代码的关键在于第 1 步的防御性判断。申请单状态必须是 0 才允许审核,杜绝了“已经审核通过的申请单被再次操作”的可能。第 3 步的 if else 对应状态机的两条分支。我见过一些实现把申请单状态和宠物状态混在一起,结果拒绝领养后宠物永远变成“已领养”,这个项目把两个状态位分开,是值得借鉴的地方。参数说明:auditResult 由后台管理页面的两个按钮传入,分别是“通过”和“拒绝”。

3.3 文件上传与图片回显:磁盘存储方案与访问映射

宠物档案里的照片是另一个容易翻车的点。项目里采用了最常见的磁盘存储方案:图片上传到本地目录,数据库只存相对路径。这个方案的好处是不需要额外引入对象存储服务,缺点是应用重启后路径要保证一致。

@PostMapping("/upload") public String uploadPhoto(@RequestParam("file") MultipartFile file, @RequestParam Integer petId) { // 文件名用时间戳+随机数,避免中文名乱码和重名 String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename(); String savePath = uploadDir + "/" + fileName; file.transferTo(new File(savePath)); // 数据库里只存相对路径 petMapper.updatePhoto(petId, "/upload/" + fileName); return "redirect:/admin/pet/edit?id=" + petId; }

上传本身没什么难度,真正的坑在“回显”。默认情况下 Spring Boot 只会映射 classpath:/static 下的静态资源,上传到本地磁盘的文件不在这个范围内,直接访问会 404。所以项目里必须加一个配置类,把 /upload/** 映射到实际磁盘目录,这一步漏了,图片就会全部裂开。这是后文避坑章节要重点讲的内容。

4. 本地运行指南:从数据库初始化到浏览器访问的完整步骤

这一章的目标是让你在 30 分钟内把项目跑起来。我会按照拿到源码包后的操作顺序逐步走一遍,不省略中间任何一步。

4.1 环境准备:JDK、Maven、MySQL 的版本选择

先确认本机环境。项目基于 Spring Boot 2.x,要求 JDK 1.8 或 11,Maven 3.6 以上,MySQL 5.7 或 8.0。我建议直接用 MySQL 8.0,因为 5.7 在很多新系统上反而不容易装。JDK 和 Maven 的安装不做展开,命令行里输入 java -version 和 mvn -v 确认版本即可。

4.2 数据库初始化:执行 SQL 脚本与修改连接配置

源码包内的 sql 目录下一般会有一个完整建库脚本,包含建库、建表和初始数据。在 MySQL 里执行它只需要一行命令:

mysql -u root -p < sql/init.sql

执行完毕后,打开 src/main/resources/application.yml,把数据库连接信息改成你自己的。这块配置直接决定项目能不能启动,最常见的错误是时区报错和密码不匹配。

spring: datasource: url: jdbc:mysql://localhost:3306/pet_adopt?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver

url 里的 serverTimezone=Asia/Shanghai 必须保留。MySQL 8.0 对时区很敏感,不设置时区直接启动会报 CST 相关错误,但 5.7 下同样的配置也能正常跑,所以建议统一带上。driver-class-name 在 MySQL 8.0 下必须用 com.mysql.cj.jdbc.Driver,用 5.7 的 com.mysql.jdbc.Driver 会报 ClassNotFound。

4.3 启动项目:Maven 命令与浏览器访问链路

在项目根目录打开命令行,执行下面两个命令之一即可:

mvn spring-boot:run

或者先打包再运行:

mvn clean package -DskipTests java -jar target/petadopt-1.0.0.jar

启动成功后,控制台会打印 Tomcat 启动端口,默认是 8080。浏览器访问 http://localhost:8080,看到首页的宠物列表,说明项目已经跑起来了。如果端口被占用,在 application.yml 里加一行 spring.server.port 配置就行。

4.4 验证一把:用测试账号走通领养全流程

项目初始化时一般会预置两个账号:管理员和普通用户,密码通常都是 123456。先用普通用户登录前台,浏览宠物列表,选一只状态为“可领养”的宠物提交申请。然后用管理员登录后台,进入领养审核菜单,应该能看到刚才提交的申请单,点击通过。再回到前台看那只宠物,状态应该已变成“已领养”。这一套流程走完,整个系统的核心功能就算验证通过了。

5. 避坑记录:跑这个项目最容易踩的 5 个坑

这里记录的每个问题,都是我自己折腾这份源码时真实遇到过的。按“现象 → 原因 → 解决”的顺序写,方便你直接对照排查。

5.1 项目启动直接报 ClassNotFoundException

现象:mvn spring-boot:run 执行后,控制台抛出 ClassNotFoundException: com.mysql.jdbc.Driver。原因:根目录 pom.xml 里的 MySQL 驱动版本老,驱动类名在 MySQL 8.0 下已经改成 com.mysql.cj.jdbc.Driver。解决:修改 application.yml 里的 driver-class-name 为 com.mysql.cj.jdbc.Driver,这是最省事的做法;或者升级 pom.xml 的 mysql-connector-java 版本到 8.0 以上。

5.2 前后台页面中文全部显示为问号

现象:浏览器显示宠物名称、公告内容全是???。原因:数据库连接串没有指定 characterEncoding,MySQL 默认按 latin1 读取客户端传来的数据。解决:application.yml 的 url 里加上 useUnicode=true&characterEncoding=utf8,并且确认数据库本身是 utf8mb4 编码。改完重启项目,问题消失。

5.3 上传宠物图片后,页面上图片裂开

现象:图片上传提示成功,但前台详情页的 img 标签显示空白。原因:Spring Boot 默认不映射用户本地磁盘目录到 URL,上传的文件虽然写入了磁盘,但浏览器访问不到。解决:在 config 包里增加一个 WebMvcConfigurer 配置类,重写 addResourceHandlers,把 /upload/** 映射到本地磁盘路径。映射路径注意写绝对路径,写相对路径会受启动目录影响。

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 将 /upload/** 映射到磁盘中的 uploadDir 目录 registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadDir + "/"); } }

5.4 Thymeleaf 模板报错,整个页面直接 500

现象:访问某个页面时,控制台堆栈指向 templates 下的 HTML 文件,提示语法错误。原因:Thymeleaf 对标签闭合要求严格,比普通 HTML 更敏感。比如 input 标签少写了斜杠,或者用了 th:text 但表达式写错变量名。解决:看控制台的错误行号,定位到具体 HTML 文件的那一行修掉。建议使用 IDEA 打开模板文件,有语法高亮的情况下这类错误能提前暴露。

5.5 点击“申请领养”没反应,但页面没报错

现象:前台点了申请按钮,URL 变了但数据库没有新记录。原因:多半是当前用户没有登录,Session 里的 loginUser 为 null,Controller 里做了重定向到登录页。但有些模板页面里把异常吞掉了,导致看起来像“没反应”。解决:检查浏览器的网络请求面板,确认 /adopt/apply 实际返回的是 302 到登录页。这也提醒你,二次开发时不要随意去掉登录检查。

6. 二次开发:三个值得动手的改造方向

项目跑通之后,自然的下一步是思考怎么改成自己的东西。这里给出三个改造方向,难度从左到右递增,每个都能作为答辩时的亮点功能讲。

方向一:把单级审核改成多级审核。现有流程里管理员一个人就能通过领养申请。你可以加一个“初审→终审”的状态流转:t_adopt_apply 表加 audit_stage 字段,0 表示初审,1 表示终审;后台拆成两个管理员角色,初审通过后状态变为 1,终审通过后宠物才变成已领养。这个改动只需要动 AdoptService 的 audit 方法和后台管理页面,半小时能完成。

方向二:给宠物列表加搜索和分页。现在的宠物列表是一次性查全量,数据多了以后页面会卡。改造方式是用 MyBatis 的 PageHelper 插件,或者手写 LIMIT 分页。手写更简单:PetMapper.xml 加一个 selectPage 方法,接收 offset 和 size 两个参数,Controller 里接收 pageNum 参数,计算 offset,模板里渲染页码。这个改动能体现你对查询性能的基本认知。

方向三:预留小程序端 API。如果想把项目扩展成移动端,不需要重写整个后台。在 controller 包下新建一个 api 子包,写几个 @RestController 接口,返回 JSON 格式的宠物列表和领养申请结果。前端小程序用 wx.request 请求这些接口即可。注意要把接口路径放在 /api/** 下,并且做和页面路由不同的权限控制。这个方向能让你的毕业设计从“管理系统”变成“全栈应用”,含金量提升明显。

从我自己的习惯来说,每次改动领养流程,我都会强制走一遍完整链路——从用户提交申请开始,到管理员审核结束,中途检查宠物状态是否按预期流转。原因很简单,申请流程里逻辑分支多,改了 Service 层最容易出现状态不一致的问题,而这类问题靠眼睛看代码很难发现,跑一遍就能暴露。希望这些分析能帮到你,祝二次开发顺利。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进