
简介《宠物领养救助管理系统》是一套面向计算机及相关专业学生的毕业设计/课程设计项目围绕宠物救助中心的领养登记、救助记录、健康追踪、申请处理等业务搭建起完整的信息化管理方案且已通过严格测试验证。压缩包共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 传统单体SSMSpring SpringMVC MyBatisJSP 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里的数据库连接配置。下面这份配置是我在这个题目里反复用的基准版本重点关注serverTimezoneMySQL 8.x 缺了它会在连接时直接报时区异常。spring: datasource: url: jdbc:mysql://localhost:3306/pet_adoption?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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.entitymapper-locations指定了 XML 文件的位置如果解压出来的项目把 mapper XML 放在别的目录这里就要对应改。检查清单再重复一遍SQL 脚本齐不齐、数据源配置对不对、pom.xml里依赖能不能拉下来、前端目录下有没有package.json。这四样确认完再往后走才有意义。3.2 宠物发布与列表分页图片路径和查询条件的实现细节宠物发布接口很简单但有两个细节决定后面好不好用图片路径必须由上传接口返回、宠物初始状态必须在后端强硬指定不能信任前端传值。// PetController.java —— 发布宠物 PostMapping(/api/pet) public ResultLong 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 idselectPetPage resultTypecom.example.pet.entity.Pet SELECT id, name, species, breed, gender, age_months, health_status, image_url, status, create_time FROM pet where if testspecies ! null and species ! AND species #{species} /if if teststatus ! null and status ! AND status #{status} /if if testkeyword ! null and keyword ! AND (name LIKE CONCAT(%, #{keyword}, %) OR breed LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /selectwhere标签会自动去掉第一个条件的多余 AND这是 MyBatis 的常规技巧手写WHERE 11也能达到同样效果但答辩时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 idupdateStatusByCondition 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 ResultVoid 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_FORMATGROUP 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 statusAPPLYING WHERE id? AND statusWAITING判断影响行数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_deleted1历史申请记录还能继续通过 JOIN 查出用户名。逻辑删除还有附带好处救助记录的reporter_id在用户“删除”后依然能关联出上报人姓名不会变成一串孤儿 ID。5.4 时间字段显示成一串数字JSON 序列化格式没配现象前端看到createTime: 1730000000000而不是2025-01-01 12:00:00。原因后端返回的字段类型是 DateJackson 默认序列化成时间戳毫秒数前端没有做格式化。解决在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 idpetQueryCondition里select 和 selectCount 都引用它。改一个地方两边同步生效。5.6 换电脑演示连不上数据库连接配置与环境的差异现象在自己电脑跑得好好的答辩教室的笔记本上起服务页面全部报 500看日志是数据库连接超时或用户名密码错误。原因application.yml里的数据库地址写死了localhost密码写的是你本机的换机器自然失效。老师的电脑上如果没有提前装 MySQL问题更直接。解决把配置按环境拆成application-dev.yml和application-prod.yml默认激活 dev答辩前提前在演示机器上装好 MySQL 并导入 SQL 脚本或者干脆租一台云服务器把项目部署上去现场只演示线上地址。用--spring.profiles.activeprod指定激活配置。这条坑一旦当场炸掉前面所有准备都白费务必提前踩点。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.activeprod检查单三条导出的 SQL 文件在当前机器能重新导入一次、前端静态资源已经 build 并放进后端资源目录、数据库连接配置里的密码不是初始弱口令。这套项目带我走过好几轮最深的教训是状态机字段千万别临时加值而不更新接口校验答辩前最后一天改表结构是大忌改成什么样都可能带出隐藏问题。把数据模型先想稳代码量其实不大但每一步都能讲出道理。希望帮到你。本文还有配套的精品资源点击获取