每年三四月份都会有一大批计算机类毕业生开始对着“基于javaspringboot的流浪猫狗救助系统”这种题目发愁。这个题名的含金量其实被很多人低估了——乍一看就是宠物信息的增删改查实际上它牵涉到用户角色权限、宠物生命周期状态、领养申请审核、文件上传、搜索筛选、数据统计几乎把Spring Boot生态里最常用的能力都串了一遍。这篇文章我结合平时帮同学调试这类项目的经验把从选题拆解、数据库设计、核心模块实现到部署说明和演示视频录制的全流程讲清楚。无论你手里是完整源码还是半成品看完这一篇至少能知道每张表为什么这么建、每个接口为什么这么写答辩的时候也能讲出点真东西。1. 选题冷启动把“救助系统”从题目变成需求清单1.1 先分清角色再思考权限很多同学拿到题目的第一反应是打开IDE开始建表这个顺序其实错了。一个“流浪猫狗救助系统”先要回答的不是“有哪几张表”而是“谁在用、每个角色能做什么”。我见过不少版本的源码用户角色这一块设计得五花八门但本质上都可以归成三类。游客/未登录用户只能浏览宠物列表、查看公告、查看养宠知识不能提交领养申请。注册用户能浏览宠物详情、收藏宠物、提交领养申请、查看自己的申请进度、填写个人信息。管理员/救助站工作人员发布宠物信息、编辑宠物状态、审核领养申请、管理公告和用户、查看统计数据。这三类角色在系统里对应的就是用户表里的一个role字段普通做法是用0表示管理员、1表示普通用户。再加上 Spring Boot 的拦截器或者安全框架就能实现“游客能访问哪些 URL、登录用户能访问哪些 URL、管理员能访问哪些 URL”的权限控制。如果有余力还可以扩展一个“志愿者”角色专门负责回访和线下审核。这个角色不是必须的但它能成为论文里的一个亮点说明你考虑到了真实救助站的协作场景。角色划分清楚以后论文里的用例图、需求分析、功能模块列表都能直接生成出来不用憋字。1.2 宠物和领养申请各自的状态流这套系统里最容易被忽视、但恰恰最有技术含量的是状态流转。宠物不是永远“可领养”的它从进入救助站到被领养至少要经历下面几个状态待审核/暂未上架救助站刚收容还没有完成驱虫、免疫等整理工作。待领养信息已完善可以对外展示用户可以提交申请。申请审核中有用户提交了申请宠物被临时锁定避免多人同时申请同一只。已领养审核通过领养流程完成宠物下架。已下架因病死亡、走失或其它原因不再接受领养。领养申请本身也有自己的状态待审核→初审通过→复审/回访通过→已完成中间还可能变成已拒绝或已取消。真正走一次完整流程就会意识到宠物的状态和申请的状态是相互联动的。用户提交申请后宠物从“待领养”变成“申请审核中”管理员拒绝后宠物又回到“待领养”管理员最终确认领养完成后宠物变成“已领养”。这串状态变化就是答辩时最值得展开的“业务深度”。不少同学做毕设只会照着模板敲代码状态字段随手写个String今天传“待审核”、明天传“1”程序跑通就算结束了。真正要让老师觉得你做过思考必须把这套状态机梳理清楚并且保证代码里的每个入口都在同一个 Service 层统一处理状态变化。2. 功能边界与模块清单救助站需要的不只是宠物表2.1 九个核心功能模块把角色和状态理清之后功能模块基本就浮出水面了。一个完整、能支撑论文篇幅的救助系统通常包含下面这些模块。每个模块对应的技术点我在括号里标了出来方便你对照自己的源码检查有没有覆盖。模块核心功能点对应技术点用户模块注册、登录、个人信息、密码加密存储Token、拦截器、密码加密宠物模块发布宠物、编辑信息、图片上传、多条件检索分页查询、文件上传、条件拼接领养申请模块提交申请、审核、拒绝、取消、进度查询状态机、防重复提交公告模块管理员发布公告、置顶、用户查看权限控制、排序查询养宠知识/救助指南模块文章发布、分类浏览、详情展示富文本、文章表设计评论互动模块用户对宠物或文章留言一对多关联查询个人中心模块我的申请、我的收藏、修改资料关联查询、关联表设计数据统计模块统计用户数、宠物数、领养完成率聚合查询、图表组件后台管理模块以上所有模块的管理端入口角色校验、统一返回结果看到这张表你就明白了“流浪猫狗救助系统”绝对不是一张pet表加一张user表就能糊弄的。哪怕是简化版宠物、领养申请、用户、公告这四张主表也必须有否则论文第三章画 ER 图时你会无图可画。2.2 功能优先级怎么排功能不是越多越好但优先级要合理。我给不同基础的同学一个排序建议P0 必须有用户注册登录、宠物发布与列表、领养申请提交与审核、管理员后台登录。没有这四个答辩老师会觉得你的系统“干什么都缺一块”。P1 最好有图片上传、公告管理、个人中心、申请进度查询。这些能撑起论文的功能测试章节也能让演示视频内容更丰富。P2 加分项数据统计、评论互动、知识文章。有精力就加没有精力就放在论文“系统扩展”里口头描述。很多网上流传的源码其实已经把这些模块都做进去了。拿到源码先不要急着跑打开数据库看表数量、打开项目看 Controller先对照上面这份清单确认你的系统覆盖了哪些缺了哪个模块这样你后面修改、写论文的时候才心中有数。3. 技术选型与数据建模先想清楚数据再写代码3.1 主流版本组合与选择理由Spring Boot 项目的版本组合是毕设里最容易踩坑的地方。我在帮同学排查的时候遇到最多的报错不是业务逻辑问题而是 JDK 版本和 Spring Boot 版本不匹配。这里直接给两套稳妥组合。组件组合一稳妥组合二较新JDKJDK 8JDK 17Spring Boot2.7.x3.2.xMyBatis-Plus3.5.x3.5.xMySQL5.7 或 8.08.0前端Vue 2 / Element UIVue 3 / Element Plus认证方案Sa-Token 或 JWTSa-Token 或 JWT选 JDK 8 Spring Boot 2.7 的原因很简单网上的资料最多遇到报错几乎都能搜到解决方案。如果你手里的源码是 Spring Boot 3 写的那就必须用 JDK 17不要拿 JDK 8 硬跑启动时会直接报ClassNotFoundException或者版本不兼容错误。用户认证方案我特别说一下。很多毕设源码选的是 Spring Security但 Spring Security 配置门槛偏高新手容易在过滤器链上卡住。相比之下Sa-Token 或者自己写一个简单的 Token 拦截器更直观也更容易讲清楚。答辩老师问“你怎么做登录控制”时你完全可以说“登录成功后生成一个随机 Token 存到 Redis前端每次请求带上 Token我用拦截器统一校验。”这个思路普通、清晰、又有技术含量。3.2 数据库表设计的核心关系数据建模的核心是把真实业务关系翻译成表结构。这套系统里最核心的关系有三组用户和宠物管理员发布宠物一个管理员可以发布多只宠物一对多。用户和领养申请一个用户可以提交多个申请但针对同一只宠物在未完成或未拒绝之前不应该重复提交。宠物和领养申请一只宠物可以被多个用户申请但同一时间只能有一个“生效中”的待审核申请。表之间的关系确定后字段设计要服从两个原则一是查询方便二是状态清晰。比如宠物表里的status字段用tinyint存数字而不是直接存“待领养”这样的中文因为数字可以加索引、查询快而且在代码里可以用枚举类统一管理避免到处写魔法值。另外大部分毕设源码都会用“逻辑删除”。所谓逻辑删除就是删除时不是物理DELETE而是把deleted字段改成1。这样做的价值在于管理员误删宠物后可以恢复统计数据也不会因为删除而失真。MyBatis-Plus 原生支持这个功能配置好以后所有查询会自动过滤已删除数据。3.3 三张核心表的建表 SQL直接给可以落地的建表语句。先把三张主表建出来剩下公告、文章、收藏等表结构都是类似套路。用户表CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT 加密后的密码, phone varchar(20) DEFAULT NULL COMMENT 手机号, role tinyint NOT NULL DEFAULT 1 COMMENT 0-管理员 1-普通用户, avatar varchar(255) DEFAULT NULL COMMENT 头像路径, deleted tinyint NOT NULL DEFAULT 0 COMMENT 逻辑删除0-正常 1-删除, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT用户表;宠物表CREATE TABLE pet ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 宠物昵称, species tinyint NOT NULL COMMENT 1-猫 2-狗, breed varchar(50) DEFAULT NULL COMMENT 品种, age int DEFAULT NULL COMMENT 月龄, gender tinyint DEFAULT NULL COMMENT 0-未知 1-公 2-母, vaccinated tinyint NOT NULL DEFAULT 0 COMMENT 0-未免疫 1-已免疫, sterilized tinyint NOT NULL DEFAULT 0 COMMENT 0-未绝育 1-已绝育, description text COMMENT 救助故事或性格描述, cover_image varchar(255) DEFAULT NULL COMMENT 封面图片路径, status tinyint NOT NULL DEFAULT 0 COMMENT 0-暂未上架 1-待领养 2-申请审核中 3-已领养 4-已下架, deleted tinyint NOT NULL DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status (status), KEY idx_species (species) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物信息表;注意这里给status和species都加了索引。原因是系统首页和列表页最常用的查询就是“按品种筛选”和“按状态筛选”高频查询列加上索引之后数据量增长时性能不会明显下降。这个细节写在论文“数据库设计”或“性能优化”里非常加分。领养申请表CREATE TABLE adoption_apply ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 申请人ID, pet_id bigint NOT NULL COMMENT 宠物ID, reason varchar(500) DEFAULT NULL COMMENT 领养理由, address varchar(255) DEFAULT NULL COMMENT 居住地址, experience text COMMENT 养宠经验, status tinyint NOT NULL DEFAULT 0 COMMENT 0-待审核 1-初审通过 2-复审通过 3-已完成 4-已拒绝 5-已取消, reject_reason varchar(255) DEFAULT NULL COMMENT 拒绝原因, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_pet_id (pet_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT领养申请表;这里我特别加了reject_reason字段不要觉得多余。管理员拒绝用户的申请时如果完全不留原因用户会一头雾水这个字段在演示的时候也很能体现业务细节答辩时可以说“这是为了提升审核的可追溯性”。4. 核心流程代码拆解宠物发布、图片上传与领养状态流转4.1 项目分层与启动配置拿到源码先看包结构。一个标准的 Spring Boot 项目应该是类似下面的分层com.example.petadoption ├── controller // 接口层只做参数接收和结果返回 ├── service // 业务逻辑层状态机、事务都在这里处理 ├── mapper // 数据访问层MyBatis-Plus 的 Mapper 接口 ├── entity // 表实体 ├── dto // 前端传入参数对象 ├── vo // 返回给前端的结果对象 ├── config // 配置类如拦截器、静态资源映射、跨域配置 └── common // 公共类如统一返回结果、异常处理、枚举类这个分层注意两点。第一Controller 里尽量不要写业务逻辑比如不要直接在接口里判断宠物状态是不是“可领养”这类判断必须放到 Service 层否则会给后续的状态流转埋坑。第二dto和vo不要混在一起接收前端参数的类里如果有返回给前端的字段接口文档会非常混乱。启动类的代码非常简单但它决定了项目能不能跑起来package com.example.petadoption; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class PetAdoptionApplication { public static void main(String[] args) { SpringApplication.run(PetAdoptionApplication.class, args); } }真正容易出问题的是application.yml配置。这里给出一份带注释的完整配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_adoption?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 30MB mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case的意思是数据库的create_time自动映射到 Java 实体类的createTime属性这样就不用手写一堆结果映射。logic-delete-field: deleted是 MyBatis-Plus 逻辑删除的全局开关配置好之后所有自带方法执行DELETE时会自动变成UPDATE deleted 1。这段配置里最容易报错的两个坑是数据库连接串里没加serverTimezone连接 MySQL 8 时会提示时区错误driver-class-name用了旧版com.mysql.jdbc.Driver而 MySQL 8 必须用com.mysql.cj.jdbc.Driver。4.2 宠物发布接口与图片上传宠物发布接口看起来是“把表单数据存进表里”实际上有两个细节不能漏图片上传的路径规划以及封面图 URL 的正确返回。先看上传文件的工具逻辑。假设你选择把图片存到服务器本地目录public String uploadFile(MultipartFile file) { // 1. 校验扩展名 String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); ListString allowExts Arrays.asList(.jpg, .jpeg, .png, .gif); if (!allowExts.contains(ext.toLowerCase())) { throw new RuntimeException(图片格式不支持); } // 2. 重命名避免文件名冲突 String newFileName UUID.randomUUID().toString().replace(-, ) ext; // 3. 按日期分目录避免一个文件夹文件过多 String datePath LocalDate.now().toString(); // 4. 保存到本地目录 File dir new File(uploadRootPath / datePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir.getAbsolutePath() / newFileName)); return /upload/ datePath / newFileName; }这里有两个经验性提醒。第一文件名必须用 UUID 重命名否则不同用户上传两张都叫photo.jpg的图片会相互覆盖。第二按日期分目录不是为了好看是为了后期清理和备份方便一个目录文件过多时系统读取速度会下降。图片保存到本地后数据库里只存相对路径/upload/2025-06-01/xxx.jpg前端展示时拼上服务器地址即可。这样做的价值是数据库里不会存一堆base64大字段表体积小、查询快而且将来换存储服务器时只需要把整个目录拷贝走。不过光有上传还不够你还要让这个 URL 能被访问到。Spring Boot 里需要注册静态资源映射Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }很多同学图片上传成功、但浏览器打不开图片99% 的原因就是漏了这层静态资源映射。我在检查源码时经常看到本地磁盘有文件、数据库有路径就是页面 404加上这个配置立刻就好了。4.3 领养申请的状态机防重复申请与双向状态更新领养申请是这套系统的心脏。它的核心逻辑不是“插入一条申请记录”而是要保证业务规则只有待领养的宠物才能被申请同一用户对同一只宠物不能重复提交申请提交后宠物状态要同步更新申请被拒绝后宠物恢复可领养状态。把这几条落实到代码才算真正实现了一个可用的状态机。提交领养申请的 Service 代码Override public Result submitApply(AdoptionApplyDTO dto) { Pet pet petService.getById(dto.getPetId()); if (pet null) { return Result.error(宠物不存在); } // 只有待领养状态的宠物才能被申请 if (!PetStatusEnum.ADOPTABLE.getCode().equals(pet.getStatus())) { return Result.error(当前宠物不可领养); } // 同一用户同一宠物的待处理申请只能有一条 int count this.lambdaQuery() .eq(AdoptionApply::getUserId, dto.getUserId()) .eq(AdoptionApply::getPetId, dto.getPetId()) .in(AdoptionApply::getStatus, Arrays.asList( ApplyStatusEnum.PENDING.getCode(), ApplyStatusEnum.FIRST_PASS.getCode(), ApplyStatusEnum.SECOND_PASS.getCode() )) .count(); if (count 0) { return Result.error(您已提交过该宠物的领养申请请勿重复提交); } AdoptionApply apply new AdoptionApply(); BeanUtils.copyProperties(dto, apply); apply.setStatus(ApplyStatusEnum.PENDING.getCode()); this.save(apply); // 双向状态更新宠物进入申请审核中防止其他用户继续申请 pet.setStatus(PetStatusEnum.APPLYING.getCode()); petService.updateById(pet); return Result.success(apply.getId()); }这段代码有三个地方建议你在答辩前反复理解。第一为什么先查宠物状态因为所有不可领养的宠物都必须被拦截在 Service 层而不是让前台按钮隐藏就算完事。第二为什么用in(status, 0,1,2)判断重复申请因为如果用户之前已经拒绝过他是可以再次申请的只有存在“待处理”的申请时才要拦住。第三为什么this.save(apply)之后还要petService.updateById(pet)因为这个流程涉及两张表的联动更新必须放在同一个事务里否则会出现“申请已提交但宠物状态没变”的脏数据。再看管理员拒绝申请的逻辑它正好和提交是反向操作Override public Result rejectApply(Long applyId, String rejectReason) { AdoptionApply apply this.getById(applyId); if (apply null) { return Result.error(申请不存在); } // 状态非待审核/初审通过时不能再执行拒绝操作 if (!canReject(apply.getStatus())) { return Result.error(当前申请状态无法拒绝); } apply.setStatus(ApplyStatusEnum.REJECTED.getCode()); apply.setRejectReason(rejectReason); this.updateById(apply); // 宠物状态从“申请审核中”回滚为“待领养” Pet pet petService.getById(apply.getPetId()); if (PetStatusEnum.APPLYING.getCode().equals(pet.getStatus())) { pet.setStatus(PetStatusEnum.ADOPTABLE.getCode()); petService.updateById(pet); } return Result.success(); }这里canReject就是状态机里“允许的状态转移规则”。有些同学图省事直接在 Controller 里写apply.setStatus(4); updateById(apply)这样做的问题是没有检查当前状态比如用户都已经取消申请了管理员还能再去“拒绝”一次逻辑就乱了。毕设质量高不高往往就是看这种边界状态有没有处理。关于并发问题再补充一句如果两个用户同时提交同一只宠物的领养申请先查单再更新的模式在高并发下可能出现“两个请求都查到待领养都提交成功”。稳妥一点的方案是对pet表的status更新加条件判断比如UPDATE pet SET status2 WHERE id? AND status1返回影响行数为 0 说明被别人抢先了。这块内容论文里可以写面试时也可以提它是从“能跑”到“考虑过并发”的分水岭。4.4 用户权限控制拦截器与角色校验对于毕设来说常见的做法是在 Service 或 Controller 上做角色校验。我比较推荐自定义拦截器因为逻辑直观、便于演示。核心思路是除了登录、注册、宠物列表等公开接口外其余接口全部走拦截器拦截器解析 Token 后把用户信息放进请求上下文有管理员权限要求的接口再额外判断role字段。一个简化版的配置类大概长这样Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(token); if (token null || token.isEmpty()) { writeError(response, 未登录); return false; } // 这里从 Redis 或数据库查询 token 对应用户 User user userService.getUserByToken(token); if (user null) { writeError(response, 登录已过期); return false; } request.setAttribute(currentUser, user); return true; } }然后是注册到 Spring MVC 并配置哪些路径放行Configuration public class WebConfig implements WebMvcConfigurer { Resource private AuthInterceptor authInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/**) .excludePathPatterns( /login, /register, /pet/page, /pet/detail/**, /upload/**, /error ); } }这段配置里最容易忽略的是/upload/**放行否则前端图片 URL 会被当成受保护接口拦截最终结果就是宠物列表接口正常但图片全裂。我排查这个问题的时候花过不少时间当时页面报错信息也不直观后来才发现是拦截器把图片请求给拦了。管理员接口的角色校验可以写成一个注解也可以简单地在 Controller 开头取用户再判断。官方一点的做法是加一个RequireAdmin注解配合 AOP 实现复杂度更高。但毕设阶段用“拦截器放行登录接口 方法内checkAdmin()判断”就足够稳妥先把整条链路跑通再考虑花活。5. 部署上线与演示录制最容易翻车的三个环节5.1 本地部署四件事无论你拿到的源码是 IDEA 直接跑还是前端后端分离部署本地把项目跑起来的步骤都可以归纳成四件事。导入数据库用 Navicat 或命令行执行项目里附带的.sql文件。修改配置把application.yml里的数据库账号密码改成自己本机的。启动后端IDEA 直接运行主类或在项目根目录执行mvn spring-boot:run。启动前端如果是前后端分离项目进入前端目录执行npm install然后npm run dev如果后端集成了页面后端启动后直接访问http://localhost:8080即可。这四件事看着简单但我见过有同学卡了三天。最常见的现象是数据库导入时报错因为.sql文件的字符集默认是utf8mb4而本机 MySQL 版本较老或配置不一致导致中文字段变成乱码。建议导入前在文件头部加上SET NAMES utf8mb4;导入后立刻查一下某张表的中文数据是否正常。5.2 最容易翻车的三个细节部署阶段的细节排错我总结成三个高频问题基本能覆盖绝大多数情况。第一个是时区问题。连接 MySQL 8 时连接串里没有serverTimezoneAsia/Shanghai启动会直接报The server time zone value异常。有的同学在本地连数据库工具没问题但服务起不来就是这个原因。第二个是文件上传路径。Windows 上很多人习惯写D:/upload但 Linux 服务器部署时没有 D 盘路径必须改成类似/home/pet/upload。如果源码里把路径写死部署到服务器必然 404。建议把上传根路径抽到配置文件里file: upload-path: ${UPLOAD_PATH:./upload}默认用相对路径./upload部署时可以通过环境变量覆盖这样本地和服务器都不用改代码。第三个是前端接口地址。前后端分离时前端页面请求的接口默认是http://localhost:8080本地演示没问题打包部署后如果前端跑在 80 端口、后端跑在 8080 端口浏览器会报跨域或者 404。这个问题的处理思路是要么前端统一配置环境变量VITE_API_BASE_URL要么后端配置跨域CorsFilter允许指定域名。直接现场改代码往往手忙脚乱最好的办法是预算好演示环境到底用哪个端口提前把配置写好。5.3 演示视频脚本录什么、讲什么、多久合适题目里带了“演示视频”说明交付物里这是很重要的一部分。录制视频不是为了把系统点一遍而是让老师沿着你的讲述看到完整的业务闭环。我的建议是总时长控制在 10 到 12 分钟太长容易暴露问题太短又讲不清。时间操作内容讲解重点0-1分钟项目介绍页系统定位、技术栈一句话概括1-3分钟游客浏览宠物列表和详情列表分页、条件查询、宠物图片展示3-5分钟注册并登录普通用户密码加密、Token 登录、个人信息5-8分钟提交领养申请展示申请进度领养规则、宠物状态变化、防重复提交8-11分钟管理员登录审核申请拒绝/通过权限控制、状态流转、拒绝原因填写11-12分钟查看数据统计或公告管理统计分析、后台能力录制之前一定要把数据库恢复到“干净状态”。我见过有同学演示时宠物列表里一条数据都没有或者已完成领养的宠物状态混乱观感非常差。正确做法是先导入一遍初始数据把演示要用的几只宠物、一个测试账号准备好正式录之前先跑一遍流程确认每一个点击位置都正常。录屏工具可以用系统自带的录屏或开源工具格式转成 MP4。录制时分辨率建议选 1920×1080窗口不要太小否则老师手机上看得费劲。最重要的是录视频时不要只动鼠标不出声每一段操作配一句说明比如“现在用户提交了申请我们看数据库里这条记录的 status 变成了 2宠物也被锁定”。6. 论文写作与答辩问答把CRUD讲出业务深度6.1 论文各章节直接从这些内容里来随项目一起交付的文档大家习惯叫 LW 的那个 Word 文档不是拼字数而是把你已经做过的设计、代码、测试重新组织成一篇技术说明。最省力、最容易过的写法是让论文每一章都对应你实际做过的事情。论文章节内容来源绪论从流浪动物现状引出系统的社会价值需求分析第 1 章的角色表和功能清单配合用例图系统设计第 3 章的 ER 图、核心流程图、技术架构图系统实现第 4 章的代码片段 页面截图系统测试功能测试用例表 测试结果写论文时最容易犯的毛病是把大段代码直接贴上去。老师要看的不是你复制了几百行代码而是你能否讲清楚“为什么这里要这样设计”。比如贴领养申请代码之前先写一段业务规则宠物只有处于待领养状态才能被申请存在待处理申请时禁止重复申请拒绝申请后宠物状态恢复。这段文字就是第 5 章“业务逻辑实现思路”比代码有价值得多。测试章节也不需要写得神乎其神做成一张“测试用例表 预期结果 实际结果”的表就够了。比如用例操作预期结果实际结果提交领养申请-重复提交同一用户对同一宠物连续提交两次第二次返回“请勿重复提交”通过提交领养申请-宠物状态宠物处于已下架状态提交申请返回“当前宠物不可领养”通过管理员拒绝申请待审核状态拒绝并填写原因申请状态变为已拒绝宠物恢复待领养通过6.2 答辩高频问题与回答思路答辩是很多同学最紧张的一环其实老师问来问去就那么几个方向。这里把高频问题整理一下你按这个思路准备基本能稳住。问题一你这个系统有什么难点不要回答“很多”也不要回答“没什么难点”。可以抓住“状态流转和防重复申请”展开用户提交申请后宠物状态同步变化两个用户同时申请同一只宠物时需要通过状态锁定避免一宠多许拒绝申请后状态要回滚。这三点讲清楚老师就会觉得你的业务设计到位了。问题二为什么用 MyBatis-Plus 而不是 JPA回答可以从两个角度说一是 MyBatis-Plus 的分页插件和逻辑删除功能适配这套系统二是代码可读性强、方便自己写复杂 SQL。千万别贬低 JPA说“JPA 太难”会显得基础薄弱。更好的说法是“JPA 也很优秀但 MyBatis-Plus 在复杂多表查询的写法上更符合我的习惯”。问题三两个用户同时申请同一只宠物怎么办这是一个非常经典的并发问题。先诚实地说基础实现会有风险然后说你做了两步处理第一步是提交前检查宠物状态第二步是更新宠物状态时用带条件的UPDATE确保状态只能从“待领养”改为“申请审核中”如果更新影响行数为 0说明有人抢先一步直接提示“该宠物已被他人申请”。这个回答已经达到毕设优秀答辩的水平。问题四图片上传之后怎么管理按“重命名 分目录 相对路径 本地映射”四步回答。说明文件名用 UUID 防止覆盖、按日期分目录便于后续运维、数据库存路径而不是 Base64 是为了控制表体积、静态资源配置是为了前端能直接访问。问题五系统有什么可以扩展的地方准备好一两个扩展点但要说和现有系统有关联。比较自然的是增加志愿者角色承担线下回访和终审增加地理围栏按救助站坐标展示附近可领养宠物增加公告到期自动下架的定时任务。这些扩展点已经在本系统的角色、宠物、公告模块上有了基础拓展起来在业务上是顺畅的。6.3 讲项目时最忌讳的一件事最后一个经验也是很多同学答辩翻车的根源不要背代码。把整个 Controller 贴在 PPT 上、从头念到尾老师听了两分钟就会打断。更好的做法是准备一段 20 秒的项目综述类似于“这套系统给社区救助站使用解决流浪动物领养流程繁琐、信息公开度低的问题。我的重点工作是领养申请的状态流转和权限控制。以领养流程为例用户提交申请后宠物状态会锁定管理员审核通过后自动进入回访环节拒绝后状态回滚。下面我演示核心流程。”这段话一开口老师就知道你已经想明白了项目。最后再说一点我比较坚持的看法。毕设项目的价值不在于代码有多炫而在于你能否把一条完整业务链路讲顺。拿到这套基于 Spring Boot 的流浪猫狗救助系统别急着改需求加花活先把“用户提交领养申请”这条链路逐行走一遍再想扩展。主动重写一两个模块的代码比如把申请状态判断从 Controller 抽到 Service、给宠物状态更新加上条件防并发比背十页 PPT 管用。答辩现场老师问的问题九成会落在状态变化和权限处理上这两块你亲手改过自然就答得出来。
阅读完成 · 觉得有帮助?