又到了一年一度毕设选题的季节,后台收到好几个同学问同一个问题:想做Java方向的毕设,但又不想做烂大街的图书管理系统、学生管理系统,有没有既能体现技术栈能力、又有社会价值和创新点的题目?我给的答复一直很一致:可以考虑做一个基于SpringBoot+Vue的宠物领养系统。
为什么推荐这个题?先说结论:宠物领养系统的业务链路足够完整,能覆盖用户注册登录、宠物信息管理、领养申请、审核流转、回访记录、公告发布这些模块,天然适合前后端分离架构。更关键的是,"流浪动物救助匹配"这个点,能做出一套有逻辑深度的推荐匹配规则,这在答辩时是一个非常能打的创新点。这篇内容我尽量把整个系统从选题理由、表设计、匹配算法到部署上线的完整链路讲透,只要你能跟着搭完,不管是用作毕设还是自己想做点公益相关的项目,都能直接落地。
1. 为什么是"宠物领养"这个题目:选题价值与真实需求拆解
1.1 流浪动物救助站的信息断层:这个题目解决的现实问题
很多同学选毕设题目的时候,习惯从"技术好实现"这个角度出发,但忽略了更重要的一个问题:你做的系统到底解决了什么现实场景。宠物领养系统背后的真实场景,其实是救助站、宠物医院、大学校园里普遍存在的信息断层。
我见过不少校园里的流浪猫救助组织,它们的日常是:志愿者在微信群发一条宠物照片,配上几句手打的文字描述,然后靠群里的人接力转发。领养人看到了,私聊志愿者,聊几天觉得合适,约个时间线下看猫,合适就带走。看着好像能跑通,但实际上一旦宠物数量多起来,问题就全暴露了:哪只猫打过疫苗、哪只狗还需要复查、这个领养人以前有没有领养记录、送的猫粮是什么牌子容易过敏——所有信息都散落在不同人的聊天记录和Excel表格里,根本没法追溯。
宠物领养系统要做的,就是把这些分散的信息收拢到一个平台里。流浪动物救助匹配平台这个概念,核心不只是"发帖—申请"这种简单的信息发布逻辑,而是要把救助站最关心的三件事做成线上流程:宠物档案可查、领养申请可审、领养后回访可追。这一个业务闭环,恰好能撑起一个结构完整的毕设系统,而且每一环都有对应的技术落点。
1.2 从毕设评分角度反推:哪些功能模块最容易拿分
以我接触过的不少Java方向毕设答辩情况来看,评分维度通常稳定在四块:功能完整度、技术难度、创新点、文档与代码规范。宠物领养系统在这四块上分别能拿到什么分,我拆开说一下。
功能完整度上,这个题目天然具备"用户端+管理端"双向结构。用户端有宠物列表浏览、宠物详情、领养申请提交、个人中心、申请进度查看;管理端有宠物档案管理、领养审核、用户管理、回访管理、公告管理。就这一套,已经算是一个模块非常完整的单体前后端分离项目了,比那种只有一个CRUD的题目厚实得多。
技术难度上,SpringBoot + Vue 本身就是当前Java方向毕设的主流组合,但难点通常藏在细节里:比如文件上传的静态资源映射、登录鉴权的Token方案、领养申请的状态流转控制、以及后面会细讲到的匹配推荐算法。这些点做好了,在答辩现场是实打实的技术加分项。
创新点上,这是这个题目最占便宜的地方。普通的管理系统哪里有什么创新,无非是数据增删改查换个壳。但宠物领养系统里加了"救助匹配"这个业务概念后,整个高度就不同了——你可以根据宠物特征和领养人偏好做一个推荐匹配,这部分完全可以写成论文里的"系统创新点"。
文档与代码规范上,因为这个系统模块多、表关系清晰,ER图、用例图、流程图都有东西可以画,写开题报告和论文的时候材料非常充足。
1.3 同类系统里常见的"画蛇添足"与正确取舍
做毕设最容易翻车的地方,不是功能不够,而是功能过多。我见过不少人为了显得系统"高大上",硬往里塞商城模块、秒杀模块、论坛模块,结果论文写得痛苦,答辩被追问得更痛苦。
宠物领养系统要守住边界。哪些模块不建议加?第一,在线支付。领养本身大部分是公益性质,最多涉及押金或疫苗费用,引入支付会带来很大的安全合规复杂度,毕设阶段完全没必要碰。第二,即时聊天。用户和救助站之间的沟通,用站内消息或者公告+联系电话就能解决,不要去碰WebSocket聊天,那不是这个系统的核心。第三,社交动态流。容易把系统做成一锅乱炖,冲淡"领养"这个主线。
反过来,有哪些东西值得保留?领养回访记录就非常值得做。一只宠物被人领养走,并不是故事的结束,救助站通常要求定期回访了解宠物在新家的状况。这个环节做一个"回访任务提醒"的功能,不仅业务上说得通,技术上还能用定时任务解决,论文素材又多一项。后面我会详细讲怎么用SpringBoot的@Scheduled实现回访提醒。
2. SpringBoot+Vue的前后端分离设计:从用例图到API契约
2.1 三种角色三种权限:角色权限体系怎么设计才不"玩具"
很多毕设系统的权限设计就一句话:管理员能进后台,用户不能。这个在答辩时其实很脆弱,评委只要追问一句"你后台里哪些操作是普通用户可以做的?"就容易卡壳。宠物领养系统的现实业务里,角色应该拆成三个:普通用户(领养申请人)、救助站管理员(负责审核和宠物管理)、系统管理员(负责用户和数据的全局管理)。
角色权限矩阵,我建议按这个来设计:
| 功能模块 | 普通用户 | 救助站管理员 | 系统管理员 |
|---|---|---|---|
| 浏览宠物列表/详情 | 允许 | 允许 | 允许 |
| 提交领养申请 | 允许 | 不允许 | 不允许 |
| 宠物档案新增/编辑 | 不允许 | 允许 | 不允许 |
| 领养申请审核 | 不允许 | 允许 | 不允许 |
| 回访记录管理 | 不允许 | 允许 | 允许 |
| 用户账号管理 | 不允许 | 不允许 | 允许 |
| 公告发布 | 不允许 | 允许 | 允许 |
这个矩阵背后的逻辑是贴近真实救助站运营的:救助站管理员是系统里最忙的角色,他们在线下负责救助动物,在线上负责维护宠物档案和处理领养申请。系统管理员则更偏运维性质,不需要参与日常业务。普通用户的权限边界很清楚——只能看、只能申请,不能修改宠物数据,这样就能挡住"用户篡改宠物信息"这类数据安全风险。
技术实现上,SpringBoot里我建议用拦截器(HandlerInterceptor)配合自定义注解来实现接口粒度的权限控制,把角色校验从业务代码里抽出来,而不是在每个Controller里写if判断。注解名可以叫@RequireRole,取值是"USER"、"ADMIN"之类的角色标识。这样代码清爽,答辩讲起来也更有架构感。
2.2 核心数据表:宠物档案、领养申请、回访记录的字段设计
表设计是毕设系统里最能体现基本功的地方。宠物领养系统的核心表,我认为至少是这四张:用户表、宠物表、领养申请表、回访记录表,外加一张公告表做辅助。
先看用户表。除了常规的用户名、密码、手机号、邮箱之外,要特别注意加一组"偏好字段",这是后面做匹配推荐的数据基础。比如:偏好宠物类型(猫/狗)、可接受的宠物体型、是否有养宠经验、家庭空间大小、空闲时间。这些字段在设计用户表的时候就预留好,否则后面做推荐匹配的时候,你会发现数据模型不支持,非常被动。
宠物表要记录的关键信息更多:宠物名称、种类(猫/狗/其他)、品种、年龄、性别、是否绝育、是否驱虫、疫苗情况、性格描述、救助站编号、健康状态、照片URL、状态。这里的"状态"非常关键,我建议用枚举:待领养、审核中(已被申请)、已领养、已下架。每一次申请提交,宠物的状态流转都要和领养申请表联动,后面会讲状态机。
领养申请表是这个系统的"订单中心"。字段包括:申请编号、用户ID、宠物ID、申请时间、申请状态(待审核/已通过/已拒绝/已完成)、申请理由、居住情况说明、养宠经验说明。这里要记住,一张表承担的是"业务流转"职责,而不是简单的记录。
回访记录表相对独立:回访编号、领养申请编号、回访时间、回访方式(线上/上门)、宠物当前状况、回访人意见。这张表的价值在业务闭环上,能体现出系统对"领养不是一时冲动"这个公益理念的支撑。
2.3 API契约先行:把这些接口定了再写前端
前后端分离项目里,最大的灾难往往是前后端各写各的,最后联调时接口对不上。所以我在这个项目里提倡"API契约先行"——先把接口路径、请求方法、请求参数、响应结构都定义清楚,再开始写代码。
接口风格采用RESTful,统一响应结构是:
{ "code": 200, "message": "success", "data": {} }几个核心接口清单如下:
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/user/register | 用户注册 |
| POST | /api/user/login | 登录,返回JWT |
| GET | /api/pet/list | 宠物列表,支持关键字和筛选参数 |
| GET | /api/pet/detail/{id} | 宠物详情 |
| POST | /api/adoption/apply | 提交领养申请 |
| GET | /api/adoption/my | 我的申请列表 |
| GET | /api/adoption/pending | 管理端待审核列表 |
| PUT | /api/adoption/audit/{id} | 审核通过/拒绝 |
| POST | /api/visit/record | 新增回访记录 |
| POST | /api/pet/add | 新增宠物档案 |
这套API设计遵循一个原则:能表达"做什么",而不只是"操作什么数据"。比如领养申请审核接口用PUT /api/adoption/audit/{id}而不是POST /api/adoption/update,因为前者在语义上更清晰,答辩时也没人会用"你这个是Restful风格吗"来纠结了。
3. 流浪动物"救助匹配"到底怎么做:推荐逻辑的三种实现方案
3.1 方案一:基于标签的匹配(最推荐毕设用)
"流浪动物救助匹配平台"这个关键词,是整个题目里最值得深入挖掘的技术点。怎么理解这里说的"匹配"?对于一个领养人来说,不是任何一只流浪宠物都适合带走。有人住单身公寓没阳台,那就不太适合领养一只精力旺盛的大型犬;有人第一次养宠物,那最好从性情温顺、已经完成基础疫苗的成年猫入手;有人家里已经有原住民宠物,那新来的宠物性格是否合群就很重要。
基于标签的匹配,就是把这套线下经验转化成可计算的规则。实现思路是:给每只宠物打上预设标签,比如"温顺""粘人""适合新手""已绝育""已驱虫""亲人""活泼"等;用户在个人资料里或者申请时,选择自己偏好的标签;系统计算宠物标签与用户偏好标签的重合度。
具体计算可以这样:宠物标签集合记为P,用户偏好标签集合记为U,匹配度 = |P ∩ U| / |U|。这样算出来的值在0到1之间。例如宠物打了4个标签,用户偏好中命中了3个,匹配度就是0.75。
有个地方要格外提醒:分母不能用宠物标签总数,必须用用户偏好标签总数。原因很简单,如果用户只选了2个偏好且都命中,匹配度是100%;如果用宠物标签数做分母,可能一只标签多的宠物永远算不出高匹配度,这不合理。
3.2 方案二:地理位置优先(涉及坐标计算)
救助站和领养人之间的地理距离,在线下场景里是一个非常现实的问题。一只在上海的流浪猫,被一个在北京的用户申请领养,这里面包含的舟车劳顿和宠物运输压力,远大于大多数人的想象。所以地理位置可以作为匹配推荐的第二维度。
实现上有两种做法。简单做法是用户和救助站都存一个城市字段,匹配时只比较城市是否相同,这个过滤逻辑最简单,但比较粗糙。进阶做法是存经纬度坐标,然后通过Haversine公式计算两个坐标点之间的距离:
private double getDistance(double lat1, double lon1, double lat2, double lon2) { double R = 6371.0; // 地球半径,单位公里 double radLat1 = Math.toRadians(lat1); double radLat2 = Math.toRadians(lat2); double dLat = Math.toRadians(lat2 - lat1); double dLon = Math.toRadians(lon2 - lon1); double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; }距离值出来后,同样做归一化处理,比如规定50公里以内距离得分为1,超过200公里得分为0,中间线性递减。这个方案的优势是业务说服力很强,答辩时简直是被追问的加分项;缺点是用户端需要授权获取位置信息,或者至少要允许用户填写城市和区域,从纯技术角度会略增加前端工作量。
3.3 方案三:申请行为权重评分(体现"更懂用户"的进阶版)
如果觉得标签匹配和距离匹配还不够有深度,可以在它们的基础上叠加一层"行为权重"。用户浏览过哪些宠物详情页、收藏过哪些宠物、对哪些宠物发起了申请,这些行为信息其实非常有价值。它们不是用户说的偏好,而是用户用行为投票表达出的真实偏好。
做法是:用户每浏览一只宠物详情页,就给对应的宠物标签计数加1;收藏加3;提交领养申请加5。然后定期或实时汇总用户的标签偏好权重。这个方案的好处是完全动态,能根据用户的行为不断校正推荐结果;坏处是数据累积需要时间,而且如果用户行为稀疏,计算出的权重偏差会很大。
3.4 我的实际选择与参数设定
作为毕设项目,我的建议是"方案一为主,方案二为辅,方案三作为扩展点写在论文里"。推荐列表的排序公式可以定为:
recommendScore = 0.7 * labelMatchScore + 0.3 * distanceScore
这个权重比例不是拍脑袋定的,它背后是一个业务判断:在流浪动物领养场景里,宠物是否适合这个人是第一位的,距离是第二位的。而且0.7和0.3这个比例好解释,答辩时一句话就能说清楚"为什么不是五五开"——因为性格不合的宠物领养回去,很容易造成二次弃养,这是流浪动物救助组织最害怕的事。
技术上,这个匹配计算可以做成一个@Service里的方法,宠物列表查询完成后在Java内存里做排序,数据量不大时性能完全够。如果真想做得工程化一点,可以引入Redis做评分缓存,但毕设阶段没必要,反而增加了答辩时被追问缓存一致性的风险。
4. 前后端核心代码实现的落地细节
4.1 后端:宠物档案发布与领养申请状态机
后端代码这块,很多同学喜欢把业务逻辑全写在Controller里,一个方法几百行,感觉很省事,但答辩一被追问就露怯。我建议至少把Service层做厚。
先看宠物档案发布。Controller只做参数接收和结果返回,业务逻辑下沉到Service。伪代码逻辑链是:接收前端传来的宠物表单数据(包括照片文件)→ 把照片保存到服务器磁盘并生成访问URL → 构建Pet对象并写入数据库 → 返回宠物ID和详情。重点在于照片保存,我用本地磁盘存储,路径规范如下:
@Override public Pet addPet(PetDTO petDTO, MultipartFile file) throws IOException { // 1. 判断文件类型,只允许jpg/png/jpeg/webp String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); if (!ALLOWED_EXTENSIONS.contains(ext.toLowerCase())) { throw new BusinessException("图片格式不支持"); } // 2. 生成唯一文件名,防止重名覆盖 String newFileName = UUID.randomUUID().toString().replace("-", "") + ext; // 3. 按日期分目录存储,避免单个目录文件过多 String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); File dir = new File(UPLOAD_DIR + datePath); if (!dir.exists()) { dir.mkdirs(); } // 4. 保存文件并构建访问路径 file.transferTo(new File(dir, newFileName)); String fileUrl = "/files/" + datePath + "/" + newFileName; // 5. 落库 Pet pet = new Pet(); BeanUtils.copyProperties(petDTO, pet); pet.setPhotoUrl(fileUrl); pet.setStatus(PetStatus.AVAILABLE); return petMapper.insert(pet); }领养申请的状态流转,是另一个容易出乱子的地方。一只宠物刚被一个用户申请,如果状态还停留在"待领养",那么另一个用户在同一时间也能申请同一只宠物,这样就会产生"一猫多主"的冲突。解决办法是状态机加数据库约束双保险。
状态机在代码里用一个枚举维护:
public enum AdoptionStatus { PENDING(0, "待审核"), APPROVED(1, "已通过"), REJECTED(2, "已拒绝"), COMPLETED(3, "已完成"), CANCELLED(4, "已撤销"); }提交申请时,Service层先做幂等校验:查一下这只宠物当前状态是否是AVAILABLE,再用数据库唯一索引兜底(pet_id + applicant_id做联合唯一索引,防止同一用户重复申请同一只宠物)。这两个校验都能通过,才允许把宠物状态改为"审核中"并插入申请表。
4.2 后端:文件上传与静态资源映射的坑
文件上传我上面已经写了核心逻辑,这里专门说一下SpringBoot里对应的静态资源映射配置。如果你把文件保存到了本地磁盘,但访问URL返回404,排查方向一定是资源映射没配。要在配置类里加入:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**") .addResourceLocations("file:" + FILE_UPLOAD_DIR + "/"); } }这个坑我印象特别深,因为保存文件和访问文件用的是两套路径逻辑,一旦配置写错或者路径末尾少了斜杠,前端的宠物图片就是一片空白。另外还有一个编码上的坑:如果文件名里带中文,访问URL需要做URL编码,最好的规避方法就是像我那样用UUID重命名,彻底绕开中文文件名问题。
4.3 前端:Vue路由守卫与axios拦截器的登录控制
前端Vue这块,登录状态控制的代码虽然不复杂,但设计思路是关键。我用的是JWT方案:用户登录成功后,后端返回一个Token,前端存在localStorage里,之后每个请求在请求头里带上Authorization字段。
路由守卫的作用是"页面级别的登录控制"。它是Vue Router提供的一个钩子,在每次路由跳转前执行:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }); } else { next(); } })这里把需要登录才能访问的页面路由都打上meta.requiresAuth标记。你可能会想,如果别人知道接口地址,不经过前端直接调接口怎么办?那就需要axios拦截器做第二层控制。
axios拦截器统一在发出请求前添加Token,同时对返回的响应做统一错误处理:
service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); service.interceptors.response.use( response => { const res = response.data; if (res.code !== 200) { if (res.code === 401) { localStorage.removeItem('token'); router.push('/login'); } return Promise.reject(new Error(res.message)); } return res; }, error => { return Promise.reject(error); } );这套拦截器逻辑的好处是,后端接口的鉴权逻辑只要写一遍,所有前端请求自动带上凭证,登录过期时也会被统一弹回登录页,不需要在每个页面手写判断。
4.4 前端:宠物卡片组件与领养申请表单
一个能撑起"平台感"的前端,宠物卡片组件和申请表单是必须要写好的两个组件。
宠物卡片组件我用Vue的单文件组件实现,接收宠物对象作为props。卡片上展示照片、名称、品种标签、性别、年龄和状态标签。核心思路是:卡片组件本身不关心数据从哪来,只负责展示和派发事件。比如点击"申请领养"按钮,组件内部emit一个事件,由父组件弹窗或跳转去处理。这样宠物列表页、推荐页、搜索页都可以复用同一个组件。
领养申请表单要做的是"把决策权交给表单",它应该引导用户填写有助于救助站审核的信息。字段至少包括:居住环境(租房/自有)、是否有其他宠物、家庭平均在家人数、领养理由。前端用Element UI的Form组件做校验,比如领养理由必填,而且长度不得少于20字,这个校验规则不是为了难为用户,而是为了筛掉那些一时冲动的申请,顺便还能在答辩里说一句"系统通过表单规则设计,降低宠物被二次弃养的风险"。
5. 我从本地到服务器的真实部署经历
5.1 打包过程:SpringBoot的jar包与Vue的dist目录
到部署环节,很多人拿着项目在本地上跑得好好的,一上服务器就各种404、白屏。这里我建议先把部署流程想清楚:前端打包后是一堆静态文件,后端打包后是一个可执行的jar包,两者需要协同工作。
后端打包用的是Maven,在项目根目录执行mvn clean package,生成target目录下的jar文件。启动命令是:
java -jar pet-adoption-system.jar --spring.profiles.active=prod我习惯在application-prod.yml里维护生产环境配置,数据库地址、文件上传目录、JWT密钥都放在这个配置文件里,和开发环境的application-dev.yml隔离开,避免本地调试时误连生产库。
前端打包用的是npm run build,产物是dist目录,里面包含index.html、static等静态资源。然后把dist目录上传到服务器的Nginx静态目录里。
5.2 Nginx反向代理配置:解决前端访问后端的跨域
我强烈建议在部署层面就用Nginx解决跨域问题,而不是在前端代码里开代理。原因很简单:开发环境用Vite代理是为了一时方便,生产环境如果前端直接请求后端API地址,浏览器会因为跨域拦截掉请求,非常难受。
在服务器上,我的Nginx配置长这样:
server { listen 80; server_name your-domain.com; # 前端静态资源 location / { root /opt/pet-adoption/dist; index index.html; try_files $uri $uri/ /index.html; } # 后端API反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 上传图片的访问 location /files/ { proxy_pass http://127.0.0.1:8080/files/; } }配置里有两个非常容易踩的坑。第一个是location /的try_files,必须带上回退到index.html的规则。因为你用的是Vue Router的history模式,如果不加try_files、而用户直接访问某个子路由(比如刷新了一下/pet/3这个页面),Nginx会拿着这个路径去找对应的静态文件,找不到就报404。第二个是proxy_pass末尾的斜杠,location /api/和proxy_pass http://127.0.0.1:8080/组合时,/api/login会被转发成/login,如果末尾不写斜杠,就会变成/api/login直接转发到后端,后端接口没这个路径就404了。
5.3 数据库初始化脚本与模拟数据
数据库这块,很多人就是建几张表就结束了,但我建议做两件事:一是写一个完整的初始化SQL脚本,包括建库、建表、插入基础数据,二是在脚本里塞一批贴近真实的模拟数据,方便开发时调试和答辩时现场演示。
初始化脚本的建表顺序要严格注意:先建用户表,再建宠物表(外键引用救助站用户),然后是领养申请表(外键引用用户表和宠物表),最后是回访记录表。顺序反了会导致外键约束报错。模拟数据这部分我建议宠物表插8到12条,每只宠物都要有真实的品种、年龄、疫苗情况和性格描述,不要用一个"cat"字段应付。用户插入20到30个,其中一部分用户填了偏好标签,方便展示匹配推荐效果。我见过有人答辩现场演示时,因为模拟数据太少,推荐列表全是空的,非常尴尬。
5.4 最容易忘记的安全清单
作为毕设项目,不需要做到企业级安全标准,但最基本的几件事必须做,否则答辩评委随便试几下就能发现漏洞。
第一,密码不能明文存储,必须用BCrypt哈希。Spring Security Crypto里自带BCryptPasswordEncoder,用法非常简单,但就是有人图省事直接存明文。
第二,JWT密钥不要硬编码在代码里,更不要提交到Git仓库。放到配置文件里,用环境变量注入:
jwt: secret: ${JWT_SECRET:default-dev-secret-key}第三,上传文件要做类型白名单校验。我上面的代码里已经写了,只允许jpg、png、jpeg、webp这几种格式。这里我再补充一个细节:不要只看前端传过来的Content-Type,要用MultipartFile对象的getOriginalFilename()去取后缀,再结合文件头去判断真实类型,否则攻击者可以伪造Content-Type上传恶意文件。
第四,所有SQL操作必须用参数化查询。MyBatis的#{}语法天然防SQL注入,但有人喜欢用${}拼接排序字段或者表名,这就要注意了,${}是不做预编译的,千万不能让用户输入的内容走${}。
6. 毕设答辩时最容易被追问的三个方向
6.1 "你的匹配算法实现原理是什么?"
这个问题可以说是必问的。只要你的系统标题里带了"匹配平台"三个字,评委就会盯上这个点。回答思路要分成三层:先讲业务背景,再讲数据基础,最后讲计算公式。
业务背景一定要落到"降低二次弃养率"上。流浪动物被领养后又被退回甚至遗弃,核心原因就是领养人和宠物不匹配,所以平台的价值在于让合适的人找到合适的宠物。数据基础是宠物标签和用户偏好标签两个维度。计算公式就是把匹配度公式、距离公式和加权求和公式写在答辩PPT里,让评委看到你的推荐逻辑是有数学依据的,不是随便排序。
最容易被追问的细节是"权重为什么是0.7和0.3"。我的建议回答是:如果匹配度权重过低,可能会出现推荐列表里全是距离近但不适合用户的宠物,违背了降低弃养率的初衷。这个权重可以通过后续的用户行为反馈数据去做调整,比如用户点击率高但领养完成率低的标签组合,说明推荐逻辑有偏差,这时候要调整。
6.2 "领养审核流程怎么保证不流于形式?"
这个问题表面上问流程,实际上问的是你系统的业务深度。我的回答路径是:领养审核不是一个简单的通过/拒绝按钮,而是有状态流转和数据佐证的系统过程。
支撑这个回答的核心是三块:第一,宠物状态和申请状态是联动的,一只宠物被申请后,其他人就不能再申请了,避免重复领养;第二,领养申请表上有详细的问卷字段,包括居住环境、养宠经验、家人同意情况,这些字段会推送给管理员作为审批参考;第三,领养成功后系统会生成定期回访任务,管理员需要填写回访记录,这就形成了"申请—审核—领养—回访"的完整闭环。
如果评委继续追问"回访任务怎么触发",就可以顺理成章引出定时任务。在SpringBoot中用一个@Scheduled注解,每隔一段时间扫描所有已完成的领养记录,找出距离上次回访超过30天的,自动生成一条待办回访记录,并给对应管理员发送站内通知。这段代码量不大,但体现的工程意识很足。
6.3 "并发申请下怎么办?这个系统的数据安全怎么保证?"
这个问题的答案是"乐观锁思路 + 数据库唯一约束"的组合。以宠物领养系统中"两个人同时申请同一只宠物"的场景来说,如果代码里只做Service层的状态判断,高并发下会有竞态条件问题。两个用户同时读到宠物状态为AVAILABLE,同时判断"可以申请",然后同时执行插入,就会产生脏数据。
正确做法有两道防线。防线一:数据库层面用乐观锁,在宠物表加一个version字段,更新时用update pet set status='PENDING', version=version+1 where id=1 and version=oldVersion,影响行数为0就说明数据已被别人改过,当前申请失败。防线二:申请表上建立普通用户ID和宠物ID的联合唯一索引,保证同一个用户不能重复申请同一只宠物。
数据安全部分,主要回答上面提到的BCrypt密码加密、JWT鉴权、上传文件白名单、SQL参数化。这四点回答完,基本就覆盖了评委关心的几个主要风险点。
6.4 有精力的话,这些扩展点也可以写进论文
如果你的论文需要一点"未来展望"的素材,可以考虑这几个方向,但注意不要真的在毕设阶段实现,写了反而增加答辩风险。
一是引入Elasticsearch做宠物搜索,解决宠物特征模糊搜索和标签组合查询的性能问题。二是引入Redis缓存热点宠物信息和推荐列表,降低数据库压力。三是把图片存储从本地磁盘换成MinIO,做对象存储。这三个方向都是Java后端生态里常见的进阶方案,写论文时作为"系统未来的优化方向"比较自然。
我自己的体会是,这个题目在毕设里属于"看起来不复杂、但真正深入做进去处处有内容"的类型。难度上限完全取决于你想做到哪一层:基础版能跑通CRUD和领养流程,进阶版能做出匹配推荐和回访闭环,再往上还能做缓存和检索优化。无论做到哪一层,至少比那些千篇一律的增删改查系统要耐看得多,也能让评委觉得你是真的理解了一个真实业务场景并把它工程化落地了。
如果你正在做这个方向的毕设,从表设计开始动手之前,一定先把第2章的API契约和第3章的匹配规则想清楚,后面写代码会顺很多。祝顺利。