ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

宠物领养救助管理系统毕设设计与实现全攻略

宠物领养救助管理系统毕设设计与实现全攻略

简介:《宠物领养救助管理系统》是一套面向计算机及相关专业学生的毕业设计/课程设计项目,围绕宠物救助中心的领养登记、救助记录、健康追踪、申请处理等业务,搭建起完整的信息化管理方案,且已通过严格测试验证。压缩包共237个文件,大小约15.6MB,包含49个Java源文件、25个JSP页面、23个JS脚本、29个CSS样式及16个XML配置,是典型的Java Web应用;附带的Bootstrap样式和图片素材,让界面可直接适配。项目还内置数据分析与报表能力,可统计领养率、健康报告等关键指标,辅助管理者决策;目前已有142人学习下载。对使用者而言,这份资源提供了完整源码、数据库配置与技术说明,可用于理解数据库设计、前后端交互、逻辑处理等知识点,也能直接为毕业设计或课程作业提供可运行的参照。

1. 宠物领养救助管理系统:毕设选题的性价比与真实工作量

宠物领养救助管理系统是计算机毕业设计和课程设计里出现频率最高的那类题目——不炫技,但刚好踩中段位。常见的做法是围绕“流浪动物发现—救助—收容—领养—回访”这条业务链做一套网页系统:用户能注册登录、浏览待领养宠物、提交领养申请;管理员能审核申请、管理救助工单、看统计数据。它被反复选中,是因为既有标准 CRUD 的基础操作,又带着一条完整的业务状态机,演示时有故事可讲,答辩时不容易被问倒。适合两类人:一是要短平快完成毕设、想稳妥交差的学生;二是打算以它为骨架做二次开发,往小程序端或地图定位方向延伸的进阶者。下面按数据模型、核心接口、救助闭环、避坑、答辩收尾的顺序,把通用实现路径和最容易翻车的地方讲清楚,照着能复现,改一改就是自己的版本。

2. 先从数据模型入手:领养与救助业务的表设计与技术选型

动手写代码前,先把技术栈和数据库定下来。我见过太多人上来就写接口,写到领养申请才发现宠物状态没地方存、救助记录和宠物对不上号,最后整张表推倒重来。这个题目业务不算复杂,但至少涉及用户、宠物、领养申请、救助上报、回访记录五张有关系的表,状态有多处流转,数据模型设计得好不好,直接决定后面写代码和写论文的顺畅程度。

2.1 技术栈怎么搭:三类主流方案的优缺点对比

毕设答辩老师最常问的第一句话是“为什么选这个技术栈”,所以选型理由要想清楚,不能只回答“大家都用”。我经手过的这个题目,主流方案基本是下面三选一。

方案后端前端适合场景主要风险
A 前后端分离Spring Boot 2.x + MyBatis-PlusVue 2.x + Element UI答辩展示效果好,论文分模块好写要同时启动两个服务,部署稍微复杂
B 传统单体SSM(Spring + SpringMVC + MyBatis)JSP + Bootstrap老模板多、照抄快,时间紧的首选页面写法旧,可能被问为什么不前后端分离
C 轻量模板Spring Boot + Thymeleaf服务端渲染只想快速跑通,单包启动最简单前端交互弱,做不了太好的看板效果

我的建议是:如果你有三个月以上的完整时间,选方案 A,前后端分离在答辩时观感最好;如果只剩两三周,选方案 C,少一条前端构建链路,能省出大量排错时间。方案 B 现在不建议新开,除非你手头有一套能直接改的现成模板。

技术栈本身不是分数来源,分数来自你对它的解释。比如选了前后端分离,就要能说清“为什么要用 Token 而不是 Session”——核心原因是后端只做接口、不维护会话状态,前端和移动端可以共用一套接口,这个理由在答辩里几乎不会被追问倒。

数据库统一用 MySQL,字符集记得建库时指定 utf8mb4,否则宠物描述里一旦有 emoji 表情,写入直接报错。ORM 选 MyBatis 还是 MyBatis-Plus 都行,区别不大;如果项目里有大量联表查询,MyBatis 的 XML 写起来更直白,我下面的示例全部按 MyBatis XML 风格给。

2.2 核心表设计:用户表、宠物表、领养申请表

三张核心表先定下来,其余表围绕它们扩展。用户表要区分普通用户和管理员,用 role 字段而不是拆两张表,因为两边的基本信息完全一样。

CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(100) NOT NULL COMMENT '密码,建议 BCrypt 加密', real_name VARCHAR(50) COMMENT '姓名', phone VARCHAR(20) COMMENT '手机号', role VARCHAR(20) DEFAULT 'USER' COMMENT 'USER普通用户/ADMIN管理员', avatar VARCHAR(255) COMMENT '头像相对路径', is_deleted TINYINT DEFAULT 0 COMMENT '0正常 1逻辑删除', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' );

字段注释要写全,这会直接挪进论文的数据库设计章节。role 用字符串不用数字,可读性好,接口里判断也直观;is_deleted 是逻辑删除标记,后面讲到删除用户时你会明白它多重要。

宠物表是整系统的核心,状态字段的取值直接对应业务流转,必须用枚举化的字符串,不要用 0/1/2 这种魔法数字。

CREATE TABLE pet ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '宠物ID', name VARCHAR(50) NOT NULL COMMENT '宠物昵称', species VARCHAR(20) NOT NULL COMMENT '物种:cat/dog/other', breed VARCHAR(50) COMMENT '品种', gender TINYINT DEFAULT 0 COMMENT '0未知 1公 2母', age_months INT COMMENT '月龄,-1表示未知', health_status VARCHAR(200) COMMENT '驱虫/疫苗/绝育情况描述', image_url VARCHAR(255) COMMENT '图片相对路径,如 /upload/pet_1.jpg', status VARCHAR(20) DEFAULT 'WAITING' COMMENT 'WAITING待领养/APPLYING申请中/ADOPTED已领养/OFFLINE已下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '入库时间', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' );

image_url 存相对路径而不是完整链接,这是个关键决策:本地开发时完整路径是file:///D:/upload/xxx.jpg,部署到服务器就全失效;存相对路径,前端只拼一个 base 地址就行。后面避坑章节会展开说。

领养申请表把用户和宠物关联起来,并记录审核过程。注意它和宠物表、用户表都不建物理外键,只在应用中维护逻辑关系,后面讲删除用户时会解释为什么。

CREATE TABLE adoption_apply ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '申请单ID', pet_id BIGINT NOT NULL COMMENT '宠物ID,逻辑关联pet.id', adopter_id BIGINT NOT NULL COMMENT '申请人ID,逻辑关联user.id', reason VARCHAR(500) COMMENT '领养理由', status VARCHAR(20) DEFAULT 'PENDING' COMMENT 'PENDING待审核/APPROVED已通过/REJECTED已驳回/CANCELED已取消', reject_reason VARCHAR(255) COMMENT '驳回原因', review_admin_id BIGINT COMMENT '审核管理员ID,逻辑关联user.id', adopt_time DATETIME COMMENT '确认领养时间', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '提交时间', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' );

2.3 状态机设计:把审核流程变成可穷举的字段取值

这个题目最有答辩价值的设计就是状态机。宠物状态和申请单状态是两套状态、互相联动,画成一张流转表,论文里能放,答辩 PPT 里也能放,还能现场讲清楚“每一条线是由哪个接口触发的”。

当前状态触发动作下一状态
WAITING 待领养用户提交领养申请APPLYING 申请中
APPLYING 申请中管理员驳回 / 用户取消WAITING 待领养
APPLYING 申请中管理员审核通过并确认交接ADOPTED 已领养
WAITING / APPLYING管理员下架OFFLINE 已下架

申请单状态独立流转:PENDING 待审核、APPROVED 已通过、REJECTED 已驳回、CANCELED 已取消。两条状态机的联动点是:宠物进入 APPLYING 时,同一只宠物不能再被其他人申请;申请单被驳回时,宠物要回滚到 WAITING。这个联动逻辑是后面接口实现的核心,也是最容易被问到的点。

状态取值为什么要用字符串而不是数字?两个原因。第一,接口返回给前端时,字符串本身就是可读的,前端不用维护一张 0/1/2 的映射表;第二,打印日志和排查问题时,status = 'PENDING'一眼能看懂,status = 0还得翻代码。代价是多占几个字节,对毕设规模的系统完全无所谓。

我一般会把所有状态值抽成一个常量类,比如PetStatus.WAITING、ApplyStatus.PENDING,避免代码里到处写裸字符串。时间允许的话再写一个状态校验工具方法,合法性集中在同一处维护。这一小节的设计工作看起来不写业务代码,但它能让你后续每个接口都少写一半的防御性判断。

3. 让代码先跑起来:压缩包验证、宠物发布与领养申请的核心接口

一套毕设项目的压缩包,无论从哪来的,第一步不是改代码,而是验证它能不能在当前机器上跑通。我一般按固定顺序做:解压看结构、建库导数据、改配置、起服务,跑通了再谈改功能。这个顺序能过滤掉八成“代码没问题,环境有问题”的翻车现场。

3.1 拿到压缩包后的第一步:检查 SQL 脚本与配置齐不齐

先解压,用 tree 看目录结构,一个规范的毕设包通常包含 sql 目录、后端代码、前端代码和论文文档。缺 SQL 脚本的压缩包可以直接放弃,因为数据库表结构靠手写还原太耗时。

# 1) 解压并查看目录结构 unzip 宠物领养救助管理系统.zip -d pet_system cd pet_system && tree -L 2 # 2) 创建数据库并导入 SQL 脚本(按文件名排序逐个导入) mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS pet_adoption DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -p pet_adoption < sql/pet_adoption.sql # 3) 启动后端(以 Maven 项目为例) cd backend && mvn spring-boot:run

导入 SQL 后用SHOW TABLES;确认表数量和你预期的模块对得上,用DESC pet;抽查核心表字段。建库时显式指定utf8mb4和排序规则,这一条能避免后面中文乱码和 emoji 写入失败的连锁问题。

后端能启动不代表能连上数据库,还要检查application.yml里的数据库连接配置。下面这份配置是我在这个题目里反复用的基准版本,重点关注serverTimezone,MySQL 8.x 缺了它会在连接时直接报时区异常。

spring: datasource: url: jdbc:mysql://localhost:3306/pet_adoption?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.pet.entity

mapper-locations指定了 XML 文件的位置,如果解压出来的项目把 mapper XML 放在别的目录,这里就要对应改。检查清单再重复一遍:SQL 脚本齐不齐、数据源配置对不对、pom.xml里依赖能不能拉下来、前端目录下有没有package.json。这四样确认完,再往后走才有意义。

3.2 宠物发布与列表分页:图片路径和查询条件的实现细节

宠物发布接口很简单,但有两个细节决定后面好不好用:图片路径必须由上传接口返回、宠物初始状态必须在后端强硬指定,不能信任前端传值。

// PetController.java —— 发布宠物 @PostMapping("/api/pet") public Result<Long> createPet(@RequestBody Pet pet) { // 图片已由 UploadController 单独处理,这里只接收返回的相对路径 if (pet.getImageUrl() == null || pet.getImageUrl().isEmpty()) { throw new RuntimeException("请先上传宠物照片"); } pet.setStatus("WAITING"); // 新发布宠物默认待领养 pet.setAgeMonths(pet.getAgeMonths() == null ? -1 : pet.getAgeMonths()); petMapper.insert(pet); return Result.success(pet.getId()); }

setStatus("WAITING")是必须的一行,否则前端可以随意指定状态,把一只“已领养”的宠物塞进列表里。age_months用 -1 表示未知,是为了统计平均月龄时不会被 0 干扰。上传接口和发布接口拆开是前后端分离的常见做法,前端先传图片拿路径,再带着路径提交宠物信息。

列表查询是这个题目使用频率最高的接口,分页加条件筛选。用 XML 写动态 SQL 比注解方式清晰,条件多了也好维护。

<!-- PetMapper.xml —— 条件分页查询 --> <select id="selectPetPage" resultType="com.example.pet.entity.Pet"> SELECT id, name, species, breed, gender, age_months, health_status, image_url, status, create_time FROM pet <where> <if test="species != null and species != ''"> AND species = #{species} </if> <if test="status != null and status != ''"> AND status = #{status} </if> <if test="keyword != null and keyword != ''"> AND (name LIKE CONCAT('%', #{keyword}, '%') OR breed LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} </select>

<where>标签会自动去掉第一个条件的多余 AND,这是 MyBatis 的常规技巧,手写WHERE 1=1也能达到同样效果,但答辩时<where>的解释更好看。LIKE 用CONCAT('%', #{keyword}, '%')而不是直接在 Java 里拼好再传,是为了避免参数里带%或_时产生意外的通配行为。offset 由前端传pageNum和pageSize计算得出,比如第二页 10 条,offset 就是 10。

3.3 领养申请与审核接口:条件更新解决并发申请

领养申请是全系统最考验基本功的接口,核心不是插入申请单,而是防止同一只宠物被多人同时申请。最保险的方案不是“先查状态再插入”,因为两步之间有时间差,两个人同时查到 WAITING 就能同时插入申请单。正确做法是用一条条件更新语句抢占宠物状态。

// AdoptionApplyService.java —— 提交领养申请 @Transactional(rollbackFor = Exception.class) public Long submitApply(ApplyDTO dto) { // 条件更新:只有 WAITING 状态才能被改成 APPLYING int rows = petMapper.updateStatusByCondition( dto.getPetId(), "APPLYING", "WAITING"); if (rows == 0) { // 说明宠物已被申请、已领养或已下架 throw new RuntimeException("该宠物当前不可申请领养"); } // 状态抢占成功后,插入申请单 AdoptionApply apply = new AdoptionApply(); apply.setPetId(dto.getPetId()); apply.setAdopterId(dto.getUserId()); apply.setReason(dto.getReason()); apply.setStatus("PENDING"); applyMapper.insert(apply); return apply.getId(); }

updateStatusByCondition的 SQL 是核心:更新条件里同时带上id和旧状态,MySQL 的行锁保证同一时刻只有一个事务能更新成功,谁先执行谁拿到行锁,另一个事务要么等待后更新 0 行,要么直接报错。@Transactional保证状态更新和插入申请单要么都成功、要么都回滚,不会出现“宠物状态改了但申请单没插进去”的脏数据。

<!-- PetMapper.xml —— 条件更新状态 --> <update id="updateStatusByCondition"> UPDATE pet SET status = #{newStatus} WHERE id = #{petId} AND status = #{oldStatus} </update>

审核接口和提交接口是对称的。审核通过只是把申请单从 PENDING 改成 APPROVED,宠物继续停留在 APPLYING;真正把宠物置为 ADOPTED 的是“确认交接”这一步,由管理员在线下完成领养交接后触发。

// AdoptionApplyService.java —— 确认交接 @Transactional(rollbackFor = Exception.class) public void confirmAdoption(Long applyId, Long adminId) { AdoptionApply apply = applyMapper.selectById(applyId); if (apply == null || !"APPROVED".equals(apply.getStatus())) { throw new RuntimeException("只有审核通过的申请单才能确认交接"); } applyMapper.updateToAdopted(applyId, new Date()); // 写 adopt_time int rows = petMapper.updateStatusByCondition( apply.getPetId(), "ADOPTED", "APPLYING"); if (rows == 0) { throw new RuntimeException("宠物状态异常,请检查数据"); } }

驳回时则相反:申请单改成 REJECTED,同时把宠物从 APPLYING 回滚到 WAITING,这样宠物能重新进入可领养列表。这个“申请—审核—交接”三步链路完整走完,就是答辩时状态机讲解的最好素材。

4. 救助闭环与后台管理:让系统在答辩时有故事可讲

很多同学的毕设把领养模块做得很完整,救助模块却只有一张表和一个列表,答辩时老师一问“救助完的动物去哪了”就卡住了。这个题目的亮点恰恰在闭环:救助上报、受理处置、救助动物入库、最终进入领养池,每一步都要有地方可查。

4.1 救助上报到受理完成:两张表完成的任务流转

救助模块用两张表,一张记上报单,一张记处置记录。上报单管“这件事当前处理到哪一步”,处置记录管“救助过程中做了什么动作”。

CREATE TABLE rescue_report ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '上报单ID', reporter_id BIGINT NOT NULL COMMENT '上报人ID,逻辑关联user.id', pet_type VARCHAR(20) COMMENT '动物类型:cat/dog/other', location_desc VARCHAR(255) COMMENT '发现位置描述', situation VARCHAR(500) COMMENT '现场情况描述', photo_url VARCHAR(255) COMMENT '现场照片相对路径', status VARCHAR(20) DEFAULT 'PENDING' COMMENT 'PENDING待受理/RESCUING救助中/DONE已完成/CLOSED已关闭', handler_id BIGINT COMMENT '处理人ID,逻辑关联user.id', handle_note VARCHAR(500) COMMENT '处理备注', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
CREATE TABLE rescue_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '处置记录ID', report_id BIGINT NOT NULL COMMENT '关联rescue_report.id', pet_id BIGINT COMMENT '救助后入库的宠物ID,关联pet.id', action_type VARCHAR(20) COMMENT 'FEED投喂/MEDICAL送医/ADMIT收容/RELEASE放归', action_desc VARCHAR(500) COMMENT '动作描述', operator_id BIGINT COMMENT '操作人ID', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

流转逻辑是:普通用户提交上报单,状态 PENDING;管理员点“受理”,状态变 RESCUING 并绑定 handler_id;处置过程每做一个动作写一条 rescue_record;如果救助的动物恢复健康且适合领养,就在 pet 表插入一条记录,状态 WAITING,并把 report_id 记到宠物表的一个字段上;最后上报单置为 DONE。这条链路里,救援动物进入领养池的动作要特别在答辩时强调,它把两个模块串成了一个整体。

受理动作同样用条件更新防止重复处理。两个管理员同时点“受理”,只有一个人能成功,另一个人拿到 0 行影响数,直接被提示“该上报已被处理”。

// RescueController.java —— 管理员受理救助上报 @PostMapping("/api/rescue/accept") public Result<Void> accept(@RequestBody AcceptDTO dto) { int rows = rescueReportMapper.updateStatusByCondition( dto.getReportId(), "RESCUING", "PENDING", dto.getHandlerId()); if (rows == 0) { throw new RuntimeException("该上报已被处理"); } return Result.success(null); }

4.2 回访记录与领养完成:闭环数据怎么查

领养不是终点,回访才是真正让数据闭环的动作。常见做法是建一张 follow_up_record 表,记录领养完成后的回访结果,比如宠物健康状况、是否仍然在养。这张表的查询价值在于和领养申请单联查,能筛选出“领养完成超过七天但没有回访记录”的宠物。

-- 待回访列表:领养完成超过 7 天且没有回访记录的宠物 SELECT a.id AS apply_id, p.id AS pet_id, p.name AS pet_name, u.name AS adopter_name, a.adopt_time FROM adoption_apply a JOIN pet p ON a.pet_id = p.id JOIN user u ON a.adopter_id = u.id LEFT JOIN follow_up_record f ON f.apply_id = a.id WHERE a.status = 'ADOPTED' AND a.adopt_time < DATE_SUB(NOW(), INTERVAL 7 DAY) AND f.id IS NULL

这条 SQL 用LEFT JOIN加IS NULL找出“没有回访记录”的领养单,是典型的排除式查询,答辩时能讲清楚“什么是 LEFT JOIN 的常见用途”。回访记录录入后,管理员就能在后台看到完整链路:谁上报、谁救助、谁领养、回访结果如何。这条链路在论文的用例图和时序图里都能直接复用。

4.3 管理端统计:三条 SQL 撑起看板

统计看板是答辩演示的最后一张牌,不需要复杂的报表工具,三条聚合 SQL 加一个 ECharts 柱状图就足够撑场面。统计接口单独写一个 DashboardController,返回 JSON 给前端。

-- 1) 按月份统计新增宠物数,前端画趋势图 SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, COUNT(*) AS cnt FROM pet GROUP BY month ORDER BY month DESC; -- 2) 领养转化漏斗:宠物总数、申请总数、成功领养数 SELECT (SELECT COUNT(*) FROM pet) AS pet_total, (SELECT COUNT(*) FROM adoption_apply) AS apply_total, (SELECT COUNT(*) FROM adoption_apply WHERE status = 'ADOPTED') AS adopted_total; -- 3) 待受理救助单数,前端显示徽标 SELECT COUNT(*) AS pending_count FROM rescue_report WHERE status = 'PENDING';

DATE_FORMAT+GROUP BY是按月聚合的标准写法,注意排序用ORDER BY month DESC,月份是字符串,排序结果就是正确的年月序。二级联动不需要,三条查询互不依赖,后端组装进一个 Map 返回即可。答辩老师问“统计口径是什么”,你就能答出每一行 SQL 的过滤条件,这就比单纯贴一张截图有说服力得多。

5. 常见问题与避坑指南:毕设项目最容易翻车的六个地方

这个题目我前后带人改过好几轮,踩过的坑集中在六个方面。每一条都是真实出现过的,按“现象—原因—解决”写,你对号入座。前四条是代码层面的,后两条是演示与部署层面的。

5.1 图片上传后页面不显示:路径拼接的经典失误

现象:上传接口返回成功,图片在浏览器直接访问 404,或者只在本地能看、换个环境就全裂。

原因:把图片路径存成了完整本地路径,比如D:/upload/pet_1.jpg,前端拿它直接当src用。浏览器是客户端,它没法读取你服务器的 D 盘;部署到 Linux 更直接失效。

解决:数据库只存相对路径/upload/pet_1.jpg,后端通过一个静态资源映射把该路径指向实际磁盘目录。常见做法是写一个配置类,映射/upload/**到file:D:/upload/,部署时把 D 盘路径换成服务器绝对路径。前端展示时统一拼接后端地址前缀。

5.2 同一只宠物被重复提交领养:并发状态校验缺失

现象:两个人同时打开一只宠物的详情页一起点“申请领养”,两个申请单都插入成功,宠物同时被两个人“申请中”。

原因:先查宠物状态,判断是 WAITING 再插入申请单,这个流程不是原子的。两次查询都发生在任何一次更新之前,自然都通过了校验。

解决:把状态更新变成条件更新,UPDATE pet SET status='APPLYING' WHERE id=? AND status='WAITING',判断影响行数,0 行就直接拒绝。这正是 3.3 节的做法。条件更新让数据库帮我们做并发控制,不需要引入分布式锁这类重武器。

5.3 删除用户报外键错误:子表数据没处理

现象:管理员在后台删一个用户,报Cannot delete or update a parent row,用户删不掉。

原因:user 表被 adoption_apply 表用物理外键引用,删除时数据库拒绝。毕设里常见的两种解法是删掉物理外键只保留逻辑关系,或者做逻辑删除。

解决:表结构里不建FOREIGN KEY约束,只保留pet_id、adopter_id这些字段,由代码保证关联数据的完整性;同时给 user 表加is_deleted字段,删除操作变成UPDATE user SET is_deleted=1,历史申请记录还能继续通过 JOIN 查出用户名。逻辑删除还有附带好处:救助记录的reporter_id在用户“删除”后依然能关联出上报人姓名,不会变成一串孤儿 ID。

5.4 时间字段显示成一串数字:JSON 序列化格式没配

现象:前端看到createTime: 1730000000000,而不是2025-01-01 12:00:00。

原因:后端返回的字段类型是 Date,Jackson 默认序列化成时间戳毫秒数,前端没有做格式化。

解决:在application.yml里配spring.jackson.date-format和spring.jackson.time-zone,全局生效;如果个别字段要不同的格式,用@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解单字段覆盖。配合 3.1 节的配置示例,这块不会出问题。注意 time-zone 必须同时配,否则格式对了但时间会差 8 小时。

5.5 分页查询 total 和列表对不上:计数条件和查询条件不一致

现象:筛选“猫”时,列表只显示几条,但 total 显示的还是所有宠物数量;或者翻到第三页突然没有数据。

原因:列表查询带了条件,但统计总数的 SQL 是另写的,两个 SQL 的 where 条件漏了筛选条件。这种错在改需求时特别容易发生,加了一个筛选条件忘了同步到 count 语句。

解决:列表和总数用同一套条件拼接。MyBatis-Plus 的Page对象会主动生成 count 查询,能规避这个问题;如果你用原生 MyBatis,就把<where>条件抽到一个可复用 SQL 片段<sql id="petQueryCondition">里,select 和 selectCount 都引用它。改一个地方,两边同步生效。

5.6 换电脑演示连不上数据库:连接配置与环境的差异

现象:在自己电脑跑得好好的,答辩教室的笔记本上起服务,页面全部报 500,看日志是数据库连接超时或用户名密码错误。

原因:application.yml里的数据库地址写死了localhost,密码写的是你本机的,换机器自然失效。老师的电脑上如果没有提前装 MySQL,问题更直接。

解决:把配置按环境拆成application-dev.yml和application-prod.yml,默认激活 dev;答辩前提前在演示机器上装好 MySQL 并导入 SQL 脚本,或者干脆租一台云服务器把项目部署上去,现场只演示线上地址。用--spring.profiles.active=prod指定激活配置。这条坑一旦当场炸掉,前面所有准备都白费,务必提前踩点。

6. 从能跑到能答辩:演示脚本、论文对照与打包部署的收尾

系统能跑只是拿到基本盘,想拿高分,还得把演示节奏和论文结构对齐。最后这三件事,是我觉得投入产出比最高的收尾动作。

6.1 十分钟演示脚本怎么排

答辩演示不是把页面挨个点一遍,而是讲一条业务故事线。建议按下面的节奏排,每个环节都能引出对应模块。

时间段演示操作讲解点
0-2 分钟注册一个普通用户并登录密码加密、角色权限
2-4 分钟管理员登录并发布一只宠物图片上传、状态初始化
4-6 分钟普通用户提交领养申请,管理员审核通过状态机、条件更新防并发
6-8 分钟确认交接新用户提交救助上报,管理员受理救助闭环、两张表的流转
8-10 分钟打开统计看板聚合 SQL、数据可视化

6.2 论文关键章节与代码的对应

论文最怕写成“代码搬运工”。把论文章节和项目里的具体成果对应起来,写起来快,答辩时也能直接指给对方看。

论文章节对应内容
需求分析功能模块图、普通用户/管理员用例
系统设计前后端架构图、技术选型理由
数据库设计核心表结构、状态机流转表
系统实现领养申请接口逻辑、条件更新 SQL
系统测试功能测试用例表、并发申请测试

6.3 打包部署检查单

答辩前用 Maven 打一个完整 jar,配合前端打包后的静态资源一起部署。下面这套命令我每次演示前都会走一遍。

# 后端打包(跳过测试,避免测试用例卡住打包) mvn clean package -DskipTests # 启动并指定生产配置 java -jar target/pet-adoption-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod

检查单三条:导出的 SQL 文件在当前机器能重新导入一次、前端静态资源已经 build 并放进后端资源目录、数据库连接配置里的密码不是初始弱口令。这套项目带我走过好几轮,最深的教训是状态机字段千万别临时加值而不更新接口校验,答辩前最后一天改表结构是大忌,改成什么样都可能带出隐藏问题。把数据模型先想稳,代码量其实不大,但每一步都能讲出道理。希望帮到你。

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

返回列表