
1. 宠物领养管理系统到底要解决什么问题做了这么多年开发经手的项目里宠物领养这个方向我见过太多次了。高校的毕业设计爱选它因为它功能边界清晰、技术点覆盖全面社会上的宠物救助机构也需要它因为线下领养流程确实乱得让人头疼。先说一个我在实际对接救助站时看到的真实场景救助站的登记表还是纸质表格领养人信息、宠物健康记录、回访情况全部靠 Excel 甚至手写本维护。有人想领养一只猫要先翻半天表格问有没有问到了再约时间到现场看看完之后填一份领养协议后续是否定期回访完全看工作人员记不记得。信息断层、流程不透明、宠物状态更新滞后这三个问题几乎每个救助站都存在。所以基于 Spring Boot 的宠物领养管理系统核心要解决的是一件事把宠物从登记到被领养再到回访的完整生命周期管起来让救助站工作人员、领养申请人和潜在领养者三方的信息在同一个平台上流动。具体拆开来讲系统至少要覆盖这几个角色和场景访客/准领养人浏览待领养宠物列表查看宠物详情照片、健康状态、性格描述提交领养申请查看申请进度。管理员救助站工作人员发布宠物信息审核领养申请登记领养结果录入回访记录管理公告和用户。宠物状态待领养 → 审核中 → 已预约 → 已领养 → 回访中每一个状态变更都要有操作记录。如果你是为了做毕业设计选这个方向还有一个很实际的好处Spring Boot 本身的技术覆盖面足够撑起一篇论文。它天然集成 MyBatis/Spring Data JPA 做持久层Spring Security 或 Shiro 做权限控制文件上传做宠物图片管理再加上一个前端页面Bootstrap/Vue/小程序展示数据整个项目从数据库设计到接口开发再到前后端联调是一条非常完整的链路。这篇文章我会按照我实际开发这套系统的顺序来写先讲数据模型怎么设计再讲核心功能怎么落地然后补充小程序端和 Python 数据分析的扩展思路最后把我在开发过程中踩过的坑集中列出来。全文内容都基于 Spring Boot 2.x 版本JDK 1.8 环境这套组合在目前的毕业设计和技术实践中兼容性最好。2. 数据模型设计宠物、领养人、审核记录的关系梳理数据库表结构是一套系统的地基地基没打好后面写代码会处处被掣肘。我做这个项目第一版的时候就犯过错误——把领养申请直接做成宠物表的一个状态字段结果后来要加申请历史记录功能改表改到怀疑人生。2.1 核心表结构与字段取舍宠物领养管理系统的核心表有五张我建议你按照下面这个结构去设计用户表user字段类型说明idbigint主键自增usernamevarchar(50)登录用户名唯一passwordvarchar(255)加密后的密码BCryptroletinyint角色0管理员 1普通用户nicknamevarchar(50)昵称phonevarchar(20)联系电话emailvarchar(100)邮箱created_atdatetime注册时间密码必须加密存储这一点没有任何商量余地。Spring Security 自带的 BCryptPasswordEncoder 直接用就完事不要自己写 MD5 加盐没必要重复造轮子。宠物表pet字段类型说明idbigint主键自增namevarchar(50)宠物名称categoryvarchar(20)类别猫/狗/其他breedvarchar(50)品种gendertinyint性别0公 1母ageint年龄月health_statusvarchar(100)健康状态描述personalityvarchar(200)性格特征storytext救助故事/背景介绍cover_imagevarchar(255)封面图片URLstatustinyint状态0待领养 1审核中 2已预约 3已领养 4下架created_bybigint发布人IDpublished_atdatetime发布时间领养申请表adoption_application字段类型说明idbigint主键自增pet_idbigint宠物ID关联pet表user_idbigint申请人ID关联user表reasontext领养理由experiencevarchar(200)养宠经验说明statustinyint状态0待审核 1已通过 2已拒绝 3已取消apply_timedatetime申请时间review_timedatetime审核时间reviewer_idbigint审核人ID这张表是整个系统的精髓。它不只是一个简单的申请记录它是宠物状态流转的驱动源头。回访记录表follow_up_record字段类型说明idbigint主键自增adoption_idbigint领养记录IDrecord_datedate回访日期contenttext回访内容pet_conditionvarchar(200)宠物状况recorder_idbigint记录人ID公告表announcement字段比较简单标题、内容、发布时间、发布人这四件套即可。2.2 领养状态流转的设计思路我在前面的表格里把宠物状态拆成了五个状态你别小看这个设计它直接决定了领养审核逻辑怎么写。宠物状态与领养申请状态是联动的用户提交申请时新建一条 adoption_application 记录并将宠物状态改为1审核中管理员审核通过后申请状态改为1已通过宠物状态改为2已预约管理员线下确认领养完成后宠物状态改为3已领养同时生成一条领养记录后续回访数据全部挂在领养记录下。为什么要把状态分得这么细因为状态字段不只是给列表页展示用的它决定了每一步操作按钮的显示条件和可执行的操作集合。宠物在审核中时不能再有人提交新的申请宠物在已预约时其他申请要自动进入候选队列宠物在已领养后详情页要显示已找到新家而不是立即申请。2.3 一对多与多对多的落库实践从类目设计上就能看出来一个宠物对应多条申请记录一条领养记录对应多条回访记录这是典型的一对多关系在数据库里用外键关联字段落库。但实际开发中我建议你不要只靠外键约束而是在业务层对数据一致性做管控。举个例子查询宠物详情时要同时展示宠物基本信息、最新申请进度、历史回访记录这一条数据如果做三次单表查询再在 Java 层拼接代码会非常啰嗦。我习惯一次性查出列表数据后通过 Map 分组解决 N1 问题。MyBatis 的 collection 嵌套查询虽然好写但在列表页一条宠物一条 SQL 的循环查询压力很大我后来都改成连表查出所有数据后在内存中按 petId 做分组。这里给出一段宠物列表联查代码的核心逻辑你可以参考这个思路// 查出宠物列表 ListPet petList petMapper.selectList(queryWrapper); if (CollUtil.isEmpty(petList)) { return new ArrayList(); } // 收集宠物id集合批量查询关联数据 ListLong petIds petList.stream().map(Pet::getId).collect(Collectors.toList()); // 批量查询领养申请仅查待审核和审核中的数据减少数据传输量 ListAdoptionApplication applications applicationMapper.selectBatchByPetIds(petIds); MapLong, ListAdoptionApplication appMap applications.stream() .collect(Collectors.groupingBy(AdoptionApplication::getPetId)); // 组装VO ListPetVO result petList.stream().map(pet - { PetVO vo new PetVO(); BeanUtils.copyProperties(pet, vo); vo.setApplicationList(appMap.getOrDefault(pet.getId(), new ArrayList())); return vo; }).collect(Collectors.toList());3. 核心功能落地从环境搭建到领养流程闭环表结构设计好了接下来就是实际写代码跑通流程。这个系统我前后做了两个版本第一个版本用的是简单的 JSP Servlet后来重构成 Spring Boot 才真正体会到框架带来的效率提升。如果你正在做毕业设计或者接手类似项目我强烈建议直接上 Spring Boot别走 Servlet 的老路。3.1 工程初始化与分层架构创建一个 Spring Boot 工程核心依赖只需要四个dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency分层架构我建议采用经典的 Controller-Service-Mapper 三层Controller 层负责接收请求参数、校验参数基本格式、调用 Service、封装统一返回结果。不要在这一层写任何业务逻辑我看到很多新手把状态判断写在 Controller 里后面代码根本没法维护。Service 层业务逻辑的核心。领养审核的流程控制、状态变更的事务管理、权限校验都在这一层。Mapper 层数据库操作MyBatis 的 XML 文件或注解 SQL。统一返回结果类我建议自己封装一个简单的 Resultpublic class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ... } public static T ResultT error(String message) { ... } }统一的返回格式说不上高深但实际开发中能让前后端对接省掉大量沟通成本这个习惯值得从第一个项目就开始养成。3.2 宠物发布与列表检索接口实现宠物发布这个接口是整个系统最容易被忽略但又最关键的部分。它涉及到图片上传、数据持久化、信息完整性校验建议看一下这个上传控制器的实现思路RestController RequestMapping(/api/pet) public class PetController { Autowired private PetService petService; PostMapping(/publish) public Result? publish(RequestBody PetDTO petDTO) { // 参数校验包括必填字段和字符串长度限制 // 图片上传走独立接口 petService.publish(petDTO); return Result.success(null); } PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(文件不能为空); } String url petService.uploadImage(file); return Result.success(url); } GetMapping(/list) public ResultPageResultPetVO list(PetQueryDTO queryDTO) { return Result.success(petService.queryPage(queryDTO)); } }列表检索接口我建议支持这几个筛选参数宠物类别、性别、年龄范围、状态。分页用 PageHelper 插件一行代码搞定PageHelper.startPage(pageNum, pageSize); ListPetVO list petMapper.selectPageList(queryDTO); PageInfoPetVO pageInfo new PageInfo(list);这里有两点值得注意。第一是图片上传的存储路径问题。开发环境从本地上传到项目所在磁盘没问题但生产部署时应用路径和磁盘路径是不一致的上传路径不要写死成相对路径建议在 application.yml 中用配置项控制同时提供静态资源映射。第二是列表查询不要直接查大字段。比如宠物故事 story 字段如果是 text 类型列表页用不到查询时应该从 select 列表中排除或者改成单独的详情接口返回。3.3 领养申请、审核与状态变更的完整链路这条链路是整个系统的业务核心开发时我建议画一张时序图再动手别用 mermaid手画或者纸笔画就行。从用户点提交申请开始到管理员完成审核整个流程涉及四张表的联动更新必须开启事务。Transactional(rollbackFor Exception.class) public void applyAdoption(Long petId, Long userId, String reason, String experience) { // 1. 锁定宠物记录防止并发申请 Pet pet petMapper.selectByIdForUpdate(petId); if (pet null) { throw new BusinessException(宠物不存在); } // 2. 状态校验仅在待领养状态可以提交申请 if (!pet.getStatus().equals(PetStatus.PENDING)) { throw new BusinessException(该宠物当前不可申请领养); } // 3. 校验该用户是否已经申请过这只宠物 int count applicationMapper.countByPetIdAndUserId(petId, userId); if (count 0) { throw new BusinessException(您已提交过申请请勿重复操作); } // 4. 创建申请记录 AdoptionApplication application new AdoptionApplication(); application.setPetId(petId); application.setUserId(userId); application.setReason(reason); application.setExperience(experience); application.setStatus(ApplicationStatus.PENDING); application.setApplyTime(LocalDateTime.now()); applicationMapper.insert(application); // 5. 更新宠物状态为审核中 pet.setStatus(PetStatus.REVIEWING); petMapper.updateById(pet); }这个事务方法里有几个细节你细品selectByIdForUpdate这是悲观锁。为什么需要因为并发场景下两个用户可能同时看到同一个宠物是待领养状态同时点了申请。如果没有行锁两个人都能成功入库但宠物被领养的条件就被破坏了。加上 for update 后后进入的事务会阻塞等待这样业务逻辑就串行化了。重复申请校验同一用户对同一宠物只能申请一次。这个校验光靠业务代码不够数据库层面应该在 pet_id user_id 上建唯一索引双重保障。事务边界这个逻辑必须整体在一个事务里。如果申请记录插入成功而宠物状态更新失败两边会出现数据不一致。管理员的审核接口逻辑刚好相反审核通过时将申请状态改为已通过宠物状态改为已预约审核拒绝时申请状态改为已拒绝宠物状态改回待领养。两个操作都必须判断当前状态是否符合预期不能从已领养直接改回待领养这种跳状态的情况出现在业务里这些规则都写在 Service 层。3.4 管理员后台与权限控制后台管理的权限控制如果你是毕业设计用拦截器判断角色就够了不需要引入全套 Spring Security那会让配置复杂几倍。但拦截器方案有一个问题你需要在每个接口手动判断角色代码重复且容易遗漏。我建议的做法是写一个自定义注解 拦截器Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { String[] value(); }拦截器里读取请求头中的 token解析用户信息和角色再和注解声明的角色比对。这种方案做到了权限逻辑集中管理每个需要权限的接口只要加一行注解比在每个 Controller 里写判断简洁得多。token 的生成和管理用 JWT 或者简单 UUID Redis 都行量小的话本地内存也够但重启会丢登录状态有条件的还是上 Redis。4. 小程序端与 Spring Boot 的对接思路现在不少毕业设计做这个系统会在 Web 端之外再加一个小程序端这个组合在答辩时很加分。而且从实际使用场景看救助站的访客更愿意扫一下用完即走小程序天然的轻量属性确实更适合领养信息的浏览场景。4.1 小程序端接口协议设计小程序端和后端交互接口协议尽量保持简洁。我建议统一使用 POST JSON 格式不用 GET 带复杂参数小程序端的 request 封一层公共方法就行function request(url, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: POST, data: data, header: { Content-Type: application/json, X-Token: wx.getStorageSync(token) || }, success: (res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { wx.navigateTo({ url: /pages/login/index }); } else { wx.showToast({ title: res.data.message, icon: none }); } }, fail: (err) reject(err) }); }); }4.2 图片上传与资源访问小程序端上传图片用的是 wx.uploadFile这个和后端 MultipartFile 接收参数在协议上不太一样。常见的一个坑是前端传文件名后端拿不到。wx.uploadFile 的 name 要和后端接口接收参数的字段名一致这个是前端最容易出问题的地方。4.3 登录鉴权与用户体系打通小程序登录走 wx.login 获取 code然后传给后端换取 openid。这里最容易踩的坑是正常用户信息如昵称、头像从 getUserInfo 拿到的信息在小程序最新版本中已经发生了很大变化。建议后端把登录拆成两步先静默登录换取 openid再让用户主动填写昵称和手机号。手机号获取也需要和微信生态的能力对接这部分需要企业主体的小程序个人主体无法调用开发前要先确认自己的账号权限。小程序端的页面按实际需求做三到四个就够了首页宠物列表、宠物详情含领养申请按钮、个人中心查看我的申请、管理员端可以单独做一个简单的审核页面。不用做得太复杂功能闭环最重要。5. 从管理系统到数据辅助决策Python 与大数据视角的扩展很多做这个项目的同学会纠结一件事系统做出来之后只是一个 CRUD 管理工具论文好像没什么深度。如果你也有这个困惑我建议在系统稳定运行的基础之上增加一个数据分析模块。技术栈不需要多复杂哪怕只是一个独立的数据分析服务都能让整个项目在答辩时呈现出完全不一样的层次感。5.1 用 Python 做领养数据的统计分析从 2025 年大环境来看Python 数据分析几乎是信息类学科的标配技能。宠物领养系统运行一段时间后数据库里沉淀了不少数据宠物类别分布、领养申请数量趋势、审核通过率、回访满意度、不同品种的平均领养时间等。这些数据单独看没有价值用 Python 的 pandas 和 matplotlib 做一次统计分析后报告的价值就出来了。举个例子用 Python 写一个脚本读取 MySQL 数据做领养趋势分析import pandas as pd import pymysql from pyecharts.charts import Bar # 建立数据库连接 conn pymysql.connect(host127.0.0.1, port3306, userroot, passwordpassword, dbpet_adoption, charsetutf8mb4) # 读取领养申请数据 sql SELECT DATE_FORMAT(apply_time, %Y-%m) AS month, status FROM adoption_application df pd.read_sql(sql, conn) # 按月统计申请量 monthly_count df.groupby(month).size().reset_index(namecount) # 可视化 bar Bar() bar.add_xaxis(monthly_count[month].tolist()) bar.add_yaxis(申请数量, monthly_count[count].tolist()) bar.render(adoption_trend.html)这份分析报告可以作为系统论文里的应用案例章节也可以作为辅助功能集成到管理后台的数据看板页面里。5.2 大数据维度Spark/Flink 实时统计的可能性再往上走一层就是真正的大数据方向。救助站数量多的话每个救助站产生领养数据的频率并不高从数据量上看根本不需要上大数据组件。但作为技术方案的完整展示可以做架构层面的设计——比如基于 Spark Streaming 或 Flink CDC 把 MySQL 的增量日志同步到数仓再通过 ClickHouse 做 OLAP 分析。架构图画出来再说明技术选型的原因为什么用 Flink 而不是 Spark Streaming、为什么 ClickHouse 不选 Doris把这些思考写清楚论文的技术深度就完全不一样了。5.3 可视化看板的实现方向我不建议你在这个阶段去搞什么实时大屏那是另一个工作量。做一个简单的数据统计看板途径有很多种可以在管理后台嵌入 ECharts 图表用接口返回聚合后的 JSON 数据也可以后端只提供原始数据前端做聚合汇总。更简单的是直接在管理端页面里用 Jquery 或者 Vue 的图表库渲染每天的申请数量和领养数量趋势图。如果你决定用 Spring Boot 聚合数据SQL 写起来是关键。日期按周分组、状态按条件聚合这几条 SQL 写好了数据口径就准确了。平时多积累别等答辩前一周发现统计数据对不上。6. 实操中容易踩的坑与排查经验最后这部分我把自己做这个项目过程中踩过的、帮别人排查过的坑集中列一列大部分都是查了半天资料才发现的问题希望你能避开。6.1 日期时间与数据库时区问题现象系统部署到服务器上查出来数据的创建时间比北京时间晚了 8 个小时或者在本地跑没问题上了服务器就不对。原因MySQL 连接串里没有设置 serverTimezone 参数默认用了服务器的系统时区。服务器如果是 UTC存进去的时间就是 UTC查询出来转换回东八区时因为 JDBC 驱动版本不同可能出各种问题。解决在 JDBC 连接串中强制指定时区jdbc:mysql://localhost:3306/pet_adoption?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai同时建议数据库中时间字段统一用 datetime 类型Java 实体类用 LocalDateTime 映射避免 Date 类型在序列化时出现格式错乱。6.2 文件上传文件大小限制现象宠物图片上传时超过 1MB 就报 500 错误控制台提示 MaxUploadSizeExceededException。原因Spring Boot 默认的上传文件大小限制是 1MB宠物图片特别是手机拍摄的照片轻松超过这个限制。这个限制不是为了为难你而是防止接口被恶意上传超大文件拖垮服务器所以不建议你直接调成无限大。解决在 application.yml 中合理改大spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB如果是自定义了拦截器处理 multipart 请求要注意拦截器的 CommonsMultipartResolver 配置和 Spring Boot 自动配置会冲突这个坑比较隐蔽建议直接使用 Spring Boot 默认配置只改参数不要手动 new 一个 resolver。6.3 并发审核导致的状态错乱现象两个管理员同时审核同一个领养申请一个通过一个拒绝数据库最终状态取决于谁后执行但实际上先通过后拒绝这个操作顺序在业务上是不允许的。原因审核操作没有校验申请当前状态。需要根据当前状态决定是否允许做目标状态变更。解决审核的 update SQL 中带上状态条件UPDATE adoption_application SET status #{targetStatus}, review_time NOW(), reviewer_id #{reviewerId} WHERE id #{applicationId} AND status #{currentStatus}通过修改受影响行数为 0 来判断状态已被其他人变更然后抛出提示该申请已被处理请刷新后查看。这种方式比先查询再判断更原子化避免并发问题。6.4 Spring Boot 版本过高带来的配置差异现象按照网上的教程配置代码一模一样但项目启动就报错。原因Spring Boot 从 2.x 到 3.x 有很多配置项发生了破坏性变更。特别是 Spring Boot 3 要求 JDK 17很多毕业设计用的 JDK 1.8 根本跑不起来。包括 javax.* 包名变成了 jakarta.*MyBatis 的 Starter 也要换成对应版本。建议你从零搭建项目时尽量锁定 Spring Boot 2.7.x 这个版本JDK 1.8它是目前兼容性最好的组合。如果你的项目只能用 Spring Boot 3那要注意 jakarta 命名空间、Spring Security 配置类写法都变了网上很多教程是 2.x 的不能直接照抄。用 2024 年之后的新教程更稳妥。如果你发现自己没法确定公司到底用哪个版本那就看 pom 文件里 spring-boot-starter-parent 的版本号一切以它为准。6.5 分页查询中 PageHelper 的一个坑现象分页查询查出来的数据总是多出来一条或者 total 一直不对。原因PageHelper 是线程本地变量机制如果调用 startPage 之后不是紧接着执行查询语句分页参数就会残留污染下一个查询。比如 startPage 后面做了一次非查询操作或者多数据源的查询顺序不对。解决严格遵循 PageHelper.startPage 之后立即跟着一个 SELECT 的规则。如果你在 Service 层做了其他操作再查询大概率会出现这个问题。必要时在 finally 中显式清理PageHelper.clearPage();7. 最后分享一点个人体会做了几版宠物领养管理系统之后我最大的体会是这类管理系统真正难的地方从来不是某个单独的技术点而是业务流程的合理性和数据状态的一致性。你从网上随便找一个开源的项目抄一遍数据库表结构跑通 CRUD 很容易但一旦业务规则复杂起来比如并发的领养申请、审核状态反转、回访记录的追溯表结构不合理、事务边界不清晰的代码就会开始给你找麻烦。所以如果你打算做这个题目我强烈建议你把前面第 2 节的数据模型和第 3 节的领养流程反复琢磨透把这两块做好了整个系统的骨架就立住了。至于小程序端、Python 统计分析这些都是在这个骨架上长出来的血肉有了互联网资源完全可以自己摸索。还有一个小技巧整个项目完成后把核心接口的调用链路、数据库表关系图、状态流转图这三张图画出来放在论文和答辩 PPT 里评审老师最喜欢看到这种清晰的工程化表达。别想着从网上下载一份随便改改自己亲手做过一遍的系统讲起来的状态是完全不一样的。