先坦白说一句:流浪动物领养这个题目,在计算机毕设里属于典型的“业务清晰、功能明确、技术栈常规”的项目。它不像AI、大数据那种需要理论深度的课题,也不像嵌入式、物联网那样依赖硬件环境。它的核心价值在于——把一套标准的信息管理流程做扎实,把CRUD写规范,把领养这种带状态流转的业务逻辑理清楚。正因如此,它非常适合用Springboot来落地,也特别适合作为毕设选题。
这篇内容我会完整拆解这个系统的设计思路、技术选型、数据库建模、核心代码实现,以及从开题到答辩全流程的实操经验。无论你是准备拿这个题目做毕设,还是想自己练手完整做一套前后端项目,都能直接照着做。
1. 项目整体设计与需求拆解
1.1 流浪动物领养系统到底要解决什么问题
先想清楚业务本质,而不是急着写代码。流浪动物领养系统的核心矛盾是:救助站/收容所有动物资源,潜在领养人有领养意愿,但两者之间缺乏高效、可信的信息通道。
线下场景的问题很典型:信息分散在微信群、朋友圈、公告栏,无法统一检索;领养人资质没人审核,冲动领养后弃养率居高不下;动物被领养后缺乏回访跟踪,救助站无法确认动物是否被善待。这些问题映射到系统里,就是三个核心需求:
- 信息透明化:动物档案要完整展示,包括品种、年龄、健康状况、免疫情况、性格描述、照片等,让领养人在线上就能充分了解。
- 流程规范化:领养不是“看中就带走”,必须走严格的状态流转——提交申请、资质审核、线下见面、领养确认、后续回访。每一个环节都要有系统记录。
- 管理可视化:救助站管理员需要能够快速维护动物信息、审核申请、管理用户、发布公告,最好还能看到一些基础统计数据。
这套需求决定了系统不是一个简单的信息展示网站,而是一个带审批流的管理系统。搞清楚这一点,后面的表结构设计、接口设计、页面设计才能有的放矢。
1.2 功能模块划分与设计思路
基于上述需求,系统按用户角色划分为两端。这里我建议不要一开始就塞太多模块进去,毕设的功能在于“完整且自洽”,不在于多。
前台用户端(面向普通访客和注册用户):
- 宠物浏览与筛选:按种类(猫/狗)、性别、年龄段、健康状况等条件组合筛选
- 宠物详情页:完整的档案信息,包括多张图片、救助故事、健康记录
- 领养申请提交:用户填写领养申请表,包括居住情况、养宠经验、经济来源等
- 个人中心:我的申请记录、申请状态跟踪、个人资料修改、我的收藏
- 公告与留言:查看救助站公告,对宠物或救助站进行留言咨询
后台管理端(面向救助站管理员):
- 宠物档案管理:增删改查、上下架宠物、管理图片、更新领养状态
- 领养审核管理:查看申请详情,通过或拒绝申请,记录审核意见
- 用户管理:查看注册用户列表、冻结异常用户
- 公告管理:发布、编辑、删除公告
- 数据统计:宠物总数、待审核申请数、领养成功率等基础指标
这里有一个设计经验给各位参考:很多同学会把“评论”“点赞”“收藏”这类社交功能一股脑加进去,结果系统做得又大又难维护。实际上对于流浪动物领养这个场景,收藏和留言咨询是合理的,点赞和评论其实无关紧要。控制功能边界是设计中最重要的能力。
1.3 为什么选Springboot作为项目底座
热搜词里大量出现“Springboot自动装配原理”“Springboot项目结构”“Springboot默认使用cglib代理”这类关键词,说明Springboot在国内Java开发领域确实是事实标准。对于一个毕设项目管理类系统,选Springboot几乎是肉眼可见的最优解。
Springboot的自动装配机制是它和传统Spring项目最本质的差别。传统SSM项目需要写大量XML配置,定义Bean注入、数据源、事务管理器,哪怕一个最简单的项目也要折腾半天。Springboot通过@EnableAutoConfiguration加上spring.factories(新版本是AutoConfiguration.imports)里的自动配置类,按照条件注解@ConditionalOnClass、@ConditionalOnProperty等判断当前环境,自动帮你把DataSource、SqlSessionFactory、DispatcherServlet等组件装配好。
用好Springboot,不需要你手写繁琐配置,默认约定大于配置,单元测试也方便,打包成可执行Jar直接跑起来,部署也省心。这些对于毕设项目的开发效率和答辩时的演示稳定性都非常关键。
2. 技术选型与系统架构解析
2.1 后端技术栈:不只是Springboot,还有这些关键搭档
只用Springboot是玩不转一个完整项目的,它只是这个舞台上的主角。下面讲一讲和它搭配的常用技术组件,以及我为什么推荐这样组合。
持久层:MyBatis-Plus(极推荐)
毕设级管理系统大量操作是单表CRUD。如果纯手写MyBatis XML映射文件,每个实体都要配一套Insert/Update/Select/Delete,工作量相当可观,而且没有营养。MyBatis-Plus提供了BaseMapper接口,内置了insert、deleteById、selectPage等通用方法,单表CRUD几乎零SQL。它还提供分页插件
PaginationInnerInterceptor,一条配置搞定物理分页。数据库:MySQL 5.7 / 8.0
没有悬念的选择。MySQL的InnoDB引擎支持事务和外键,对于领养申请这种需要一致性的业务足够用。数据库设计时建议再加一个逻辑删除字段
deleted,避免物理删除导致的数据追溯困难。安全与登录:Spring Security + JWT,或者更简单的拦截器方案
这要看你自己的基础。如果时间充裕,建议学一下Spring Security + JWT,简历上好看;如果时间紧张,自定义一个HandlerInterceptor加Token拦截器也完全够用。我自己做过不少毕设项目,从实用角度讲,拦截器方案实现起来更直接,不容易在安全框架的配置细节里卡住。
文件存储:本地存储 + Nginx映射(最省事)
热搜词里出现了MinIO加到Springboot,这确实是现代项目的主流方案,但MinIO部署在答辩环境下有一定风险——你要额外启动一个中间件服务。稳妥的做法是先存本地磁盘,再用虚拟路径映射出去。如果你在毕设里想体现亮点,也可以把本地存储封装成一个FileStorageService接口,预留MinIO的Swagger配置和方法,展示一种可扩展的设计思想。这个我后续会在实现章节细讲。
2.2 前端选择:前后端分离还是服务端渲染
这是一个很多同学在做毕设选题时都会纠结的问题。我直接给结论:如果是从零开始做一个能跑通、效果好的毕设项目,优先选Vue + Element UI + Axios做前后端分离。
原因有三:一是前端展示效果更好,Element UI的表单、表格、对话框组件几乎为管理后台量身定制,对流浪动物领养系统这种大量表格加表单交互的场景非常适合;二是后端只需要写RESTful接口返回JSON,接口可以单独用Postman或Swagger测试,调试路径清晰;三是答辩时可以清晰地讲出前后端分离架构,这对评委来说是一个加分项,因为它是近几年工程实践的主流。
配合Vue生态,还需要考虑:
- Vue Router做前端路由,页面跳转和路由守卫(判断用户是否已登录)
- Pinia或Vuex做状态管理,存放登录用户信息
- Axios统一封装请求,拦截器里携带Token、统一处理错误码
如果觉得Vue学习成本高,退一步用Thymeleaf服务端渲染也能完成项目,但界面体验和开发效率都会明显打折。我见过太多学生最后卡在Vue打包、跨域代理这类问题上,这里先提个醒:后端项目要用CORS配置或Springboot的WebMvcConfigurer做好跨域处理。
2.3 数据库设计核心要点与状态流转逻辑
数据库设计是这个项目真正的“基本功考察点”。一个合理的表结构能撑起整个系统,设计不合理后期改起来极其痛苦。先看核心表的划分:
user:用户表,存用户名、密码(BCrypt加密存储)、角色(ROLE_ADMIN / ROLE_USER)、手机号、邮箱、头像、状态pet:宠物表,存宠物名称、种类(cat/dog)、性别、年龄、毛色、健康状态、疫苗情况、绝育情况、性格描述、救助故事、封面图、状态(待领养/审核中/已领养)pet_image:宠物图片表,与宠物一对多关联adoption_application:领养申请表,存领养人ID、宠物ID、居住情况、养宠经验、工作状况、申请状态(待审核/已通过/已拒绝/已完成)、审核意见notice:公告表,存标题、内容、发布时间message:留言表,存用户ID、宠物ID或公告ID、留言内容、回复内容
这里有一个极为关键的设计点:状态字段。很多新手喜欢用String类型直接存“待审核”、“已通过”这种中文值。这在显示上确实直接,但存在三个问题:数据冗余、检索效率低、容易脏数据(比如某一条记录写成了“已审合”)。更规范的做法是存整数枚举值,如0-待审核、1-已通过、2-已拒绝、3-已完成,再用getStatusDesc()方法或前端字典翻译来映射显示文本。
此外还要考虑领养状态的闭环。一只宠物从“待领养”变成“已领养”,不是一步完成的,中间要经过:提交申请(申请状态为待审核)→ 管理员审核通过 → 宠物状态改为已领养 → 线下交接后管理员可将申请状态改为已完成。如果没有这种流转约束,就可能出现宠物被多人重复申请后都显示成功的数据混乱。这里要在代码层面控制,同一个宠物只能有一个“已通过/已完成”的申请记录。
3. 核心功能模块的实操实现
3.1 项目初始化与核心配置
Springboot项目的搭建,建议直接使用Spring Initializr(https://start.spring.io)生成基础骨架。人工手动创建Maven工程再一个个加依赖,速度慢且容易版本冲突。Initializr上需要选的基础依赖如下(以Springboot 2.7.x为例,2024年之后的2.6、2.7版本相对稳定,避免直接上3.x导致javax到jakarta包名变动引发额外麻烦):
- Spring Web
- Spring Security(如果计划用)
- MySQL Driver
- MyBatis-Plus Framework(需要在Initializr中通过“Spring Boot 3.x兼容”提示或手动加入依赖坐标)
- Lombok
生成后用IDEA打开,项目结构可以按照常见的分层模式组织。这里提供一个适合毕设展示的标准目录结构:
src/main/java/com/example/adoption/ ├── common/ // 通用响应、异常处理、工具类 │ ├── Result.java │ ├── ResultCode.java │ ├── GlobalExceptionHandler.java │ └── JwtUtil.java ├── config/ // Springboot配置类 │ ├── WebMvcConfig.java │ ├── MybatisPlusConfig.java │ └── CorsConfig.java ├── controller/ // 控制器层,只做参数接收和响应封装 │ ├── PetController.java │ ├── AdoptionController.java │ └── UserController.java ├── service/ // 业务层,处理核心逻辑 │ ├── PetService.java │ ├── AdoptionService.java │ └── impl/ ├── mapper/ // MyBatis-Plus的Mapper接口 │ ├── PetMapper.java │ ├── AdoptionApplicationMapper.java │ └── UserMapper.java ├── entity/ // 数据库实体类 │ ├── Pet.java │ ├── User.java │ └── AdoptionApplication.java ├── dto/ // 请求体封装(前端传入) │ ├── PetQueryDTO.java │ └── AdoptionApplyDTO.java └── vo/ // 响应体封装(返回给前端) ├── PetDetailVO.java └── AdoptionRecordVO.java这个结构看起来多,但每个类的职责非常清晰:Controller只做转发,Service写业务逻辑,Mapper只管数据库操作。答辩的时候,评委问“你这个项目怎么分层的”,你能按照职责讲清楚,就是一个很加分的回答。
核心的application.yml配置如下:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/adoption?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0注意map-underscore-to-camel-case: true这个配置,数据库字段是create_time,实体字段是createTime,驼峰映射自动帮你完成转换,省掉大量@TableField注解。逻辑删除配置则是Springboot+MyBatis-Plus安全管理方面的重要一环,后面讲数据安全时会再展开。
3.2 宠物管理模块:实现CRUD与分页筛选
宠物管理是流浪动物领养系统的信息基础,所有领养流程都围绕它展开。这里先看基础CRUD,再看稍微有技术含量的分页与条件组合查询。
用MyBatis-Plus后,基础的增删改查确实简单。一个Mapper接口继承BaseMapper<Pet>后,就自动拥有了如下能力:
public interface PetMapper extends BaseMapper<Pet> { // selectPage、selectById、insert、updateById、deleteById 直接从BaseMapper继承 }Service层调用时:
// 新增宠物 Pet pet = new Pet(); pet.setName("小白"); pet.setSpecies("cat"); pet.setGender(1); pet.setStatus(0); petMapper.insert(pet); // 修改宠物信息 pet.setStatus(1); petMapper.updateById(pet); // 删除宠物(逻辑删除) petMapper.deleteById(petId);宠物列表的分页条件是毕设中的必考点。使用MyBatis-Plus的分页插件需要先配置一个MybatisPlusConfig类:
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }接下来实现组合查询,使用LambdaQueryWrapper构建动态查询条件,示例代码如下:
@Override public Page<Pet> getPetPage(PetQueryDTO query) { int pageNum = query.getPageNum() == null ? 1 : query.getPageNum(); int pageSize = query.getPageSize() == null ? 8 : query.getPageSize(); LambdaQueryWrapper<Pet> wrapper = new LambdaQueryWrapper<>(); // 如果前端传了species,就只查该种类 if (StringUtils.hasText(query.getSpecies())) { wrapper.eq(Pet::getSpecies, query.getSpecies()); } // 如果前端传了status,就按状态过滤 if (query.getStatus() != null) { wrapper.eq(Pet::getStatus, query.getStatus()); } // 关键字搜索:按名称模糊匹配 if (StringUtils.hasText(query.getKeyword())) { wrapper.like(Pet::getName, query.getKeyword()); } // 按创建时间倒序 wrapper.orderByDesc(Pet::getCreateTime); return petMapper.selectPage(new Page<>(pageNum, pageSize), wrapper); }这套写法的核心体会是:构建条件用Lambda方式可以在编译期就校验字段名,比传统QueryWrapper的字符串写法更安全,也更优雅。分页插件会自动生成LIMIT ? , ?,你不用手动拼接SQL,避免注入风险。
宠物图片上传我用的是“本地磁盘存储 + 虚拟路径映射”的方案。Springboot内置的MultipartFile配合Files.copy即可完成。具体实现如下:
public String uploadImage(MultipartFile file) { if (file.isEmpty()) { throw new RuntimeException("上传文件不能为空"); } String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); // 生成随机不重复文件名,防止覆盖 String fileName = UUID.randomUUID().toString().replace("-", "") + ext; String uploadDir = uploadProperties.getPath(); // 本地存储路径 File dir = new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } File dest = new File(uploadDir + File.separator + fileName); file.transferTo(dest); return "/upload/" + fileName; // 该路径通过WebMvcConfig做映射 }再配合一个配置类做静态资源映射:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler(uploadProperties.getPath()); } }为何这样设计?因为Springboot默认的静态资源目录是classpath:/static/,但用户上传的图片不应该打进jar包里。将图片存到外部独立目录再通过映射暴露给前端,既清晰又安全。我试过一个坑:很多同学把上传路径直接指向项目static目录,本地跑没问题,但打成jar包部署之后就找不到文件了。项目根目录和jar包内部是两个概念,这一点需要在答辩前提前验证。
3.3 领养申请模块:状态机与事务处理
领养是整个系统业务流程最复杂的地方。很多项目会栽在这里,根因是没有把状态机拆清楚。我建议直接画出状态流转图,然后对照状态图写代码。
先说申请这条链路的状态:
0待审核:用户提交申请1已通过:管理员审核通过,但宠物尚未完成交接2已拒绝:管理员审核拒绝,需要填写拒绝原因3已完成:线下交接完成,领养流程闭环
再说宠物状态与申请之间的联动:
- 宠物默认状态为
0(待领养) - 当有申请被审核通过后,宠物状态改为
1(已领养) - 此时其他用户的申请自动失效,需要在查询时过滤已领养的宠物
实现领养申请的两个关键动作:
第一个是提交申请。用户选择一只宠物,填写表单提交。后端要做的事是“校验再加记录”,校验项包括:当前登录人是否有权限、该宠物是否仍在待领养状态、用户是否已经对该宠物提交过申请(防止重复提交)。示例代码:
@Transactional public Long submitApplication(AdoptionApplyDTO dto, Long userId) { // 1. 查宠物是否可领养 Pet pet = petMapper.selectById(dto.getPetId()); if (pet == null || pet.getStatus() != 0) { throw new RuntimeException("该宠物已不在待领养状态,无法申请"); } // 2. 防止重复申请 LambdaQueryWrapper<AdoptionApplication> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(AdoptionApplication::getUserId, userId) .eq(AdoptionApplication::getPetId, dto.getPetId()) .ne(AdoptionApplication::getStatus, 2); // 已拒绝的可以重新申请 if (adoptionApplicationMapper.selectCount(queryWrapper) > 0) { throw new RuntimeException("您已申请过该宠物,请勿重复提交"); } // 3. 写入申请表 AdoptionApplication application = new AdoptionApplication(); application.setUserId(userId); application.setPetId(dto.getPetId()); application.setLiveSituation(dto.getLiveSituation()); application.setExperience(dto.getExperience()); application.setStatus(0); adoptionApplicationMapper.insert(application); return application.getId(); }这里用@Transactional是有讲究的。虽然这个场景看起来只是单表插入,但在完整实现中,提交申请时还可能要同步修改宠物的“申请次数”字段,或者写入一条操作日志。这些操作必须保证原子性——要么都成功,要么都失败。Springboot的声明式事务是一个高频考点,答辩时大概率被问到。
第二个是管理员审核。这个操作涉及的数据变更多,必须放在一个事务里:
@Transactional public void reviewApplication(Long applicationId, Integer reviewStatus, String reviewRemark) { AdmissionApplication application = applicationMapper.selectById(applicationId); if (application == null || application.getStatus() != 0) { throw new RuntimeException("申请状态异常,无法审核"); } // 1. 更新申请状态 application.setStatus(reviewStatus); application.setReviewRemark(reviewRemark); applicationMapper.updateById(application); // 2. 如果审核通过,更新宠物状态为已领养 if (reviewStatus == 1) { Pet pet = petMapper.selectById(application.getPetId()); pet.setStatus(1); // 已领养 petMapper.updateById(pet); // 3. 将同一宠物其他待审核申请置为已拒绝 LambdaQueryWrapper<AdoptionApplication> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(AdoptionApplication::getPetId, application.getPetId()) .eq(AdoptionApplication::getStatus, 0) .ne(AdoptionApplication::getId, applicationId); List<AdoptionApplication> otherApplications = applicationMapper.selectList(wrapper); for (AdoptionApplication other : otherApplications) { other.setStatus(2); other.setReviewRemark("该宠物已被他人领养,自动驳回"); applicationMapper.updateById(other); } } }这段代码就是整个系统里最具业务价值的地方。上面提到的三点变化:审核状态、宠物状态、其他申请自动驳回,必须全部成功或全部回滚。如果漏写@Transactional,在数据库异常时就会产生脏数据——比如申请通过了但宠物还是待领养状态,后续查看的人还能继续提交申请,整个业务闭环就断了。
3.4 认证权限:Spring Security还是自定义拦截器
登录认证我建议用JWT机制。JWT(JSON Web Token)的核心思路是后端在用户登录时签发一个加密Token返回给前端,前端之后每次请求都带上这个Token,后端通过解析Token来识别用户身份。
这里给出一个相对简单但完整的自定义方案:
@Component public class JwtUtil { private String secret = "your-secret-key"; private long expire = 604800; // 7天,单位秒 public String generateToken(Long userId, String role) { DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; return Jwts.builder() .setSubject(userId.toString()) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + expire * 1000)) .signWith(SignatureAlgorithm.HS256, secret.getBytes(StandardCharsets.UTF_8)) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret.getBytes(StandardCharsets.UTF_8)) .parseClaimsJws(token) .getBody(); } }再写一个JwtInterceptor拦截器,在preHandle阶段校验请求头里的Token,并将用户ID放入Request作用域,供后续Controller使用:
@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); } if (token == null || token.isEmpty()) { throw new BusinessException("未登录,请先登录"); } try { Claims claims = jwtUtil.parseToken(token); request.setAttribute("userId", Long.parseLong(claims.getSubject())); request.setAttribute("role", claims.get("role")); return true; } catch (Exception e) { throw new BusinessException("登录状态已过期,请重新登录"); } } }然后注册拦截器,并配置放行路径:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/register", "/api/pet/**", "/upload/**"); } }这种方案在答辩讲解时思路非常清晰:前端拿到Token → 每次请求放入Header → 后端拦截器验证Token → 识别用户身份。评委问细节也能答得出来。
4. 毕设全流程文档与答辩准备
4.1 开题报告与任务书怎么写才有效果
标题中带了“选题+开题+任务书+中期报告+程序设计+LW+答辩ppt全流程”,这部分很多同学低估了它的工作量。开题报告的核心作用不是“做样子”,而是帮你自己想清楚“做什么、怎么做、做出什么”。
开题报告一般包含这样几个部分:
- 选题背景与意义:从流浪动物数量增加、线下领养信息不对称的现状出发,分析“为什么需要这样一个系统”。写作时切忌空谈,最好带一些具体数据,比如“我国每年流浪动物数量达到数千万只级别,线下领养平台缺失”之类。
- 国内外研究现状:搜索几个现有的系统或平台(如各类宠物领养网站、公益组织的信息系统),分析它们的功能特点、存在不足,引出自建系统的价值点。
- 主要研究内容:对应系统功能模块来写,比如“基于Springboot的流浪动物信息发布与管理模块”“基于Java的领养申请与审核流程模块”等。
- 技术路线与可行性分析:把Springboot、MyBatis-Plus、Vue这套技术栈为什么可行讲清楚,说明你的开发环境、技术基础已经具备。
- 进度安排:按周/按月列出开发计划,从需求分析到测试答辩,留出足够的缓冲时间。
任务书则更注重“任务分解”。需要写明:项目要完成什么、每一阶段的目标、需要提交的成果(系统源码、数据库脚本、设计文档、论文)。一个容易踩的坑是任务书里写的目标和实际完成的内容对不上——比如任务书里写“实现基于百度地图的宠物救助站定位”,系统里根本做不出来,答辩时被评委翻出任务书对照就尴尬了。所以任务书要和最终实现完全对齐。
4.2 系统测试与论文写作的实操建议
系统开发完成后,系统测试部分往往是被忽视的重灾区。很多学生的论文里“测试”一章只有两张截图加一段描述,这其实很浪费。因为测试是论文里最好写、也最能体现工程能力的一部分。
建议至少设计以下几类测试用例:
- 功能测试:用户注册登录是否正常、宠物发布是否正常、各状态的申请流转是否符合预期
- 权限测试:未登录用户是否无法访问管理接口、普通用户是否能访问管理员接口
- 异常测试:重复提交申请是否有提示、上传非图片文件是否被拦截、登录过期后是否有提示
- 兼容性测试:前端页面在不同浏览器下的展示是否正常
测试用例表建议这样组织:
| 用例编号 | 测试功能 | 操作步骤 | 预期结果 | 实际结果 | 结论 |
|---|---|---|---|---|---|
| TC-001 | 用户注册 | 填写用户名、密码、邮箱,点击注册 | 注册成功并自动登录 | 注册成功并跳转首页 | 通过 |
| TC-002 | 重复申请拦截 | 已申请宠物A后再次申请 | 提示“已申请过” | 提示“已申请过” | 通过 |
| TC-003 | 管理员审核 | 通过/拒绝申请,观察宠物状态 | 通过后宠物变已领养,其他申请被拒 | 状态正确 | 通过 |
论文写作的核心结构保持经典的“绪论—相关技术—需求分析—系统设计—系统实现—系统测试—总结与展望”七章。写论文时有两个建议:
第一,系统设计的章节不要写重复的CRUD,重点写难点模块的设计过程,尤其是领养申请的状态流转、数据库表之间的关联关系、JWT认证流程,配合流程图和E-R图来展示,篇幅会自然充实,答辩时也有明确可讲的点。
第二,论文里不要出现“我”“我们”这类过于口语化的表述,统一用“本系统”作为主语,比如“本系统通过Springboot框架实现后端业务逻辑”,整体学术感会更明显。
4.3 答辩演示的高频问题与备战清单
答辩是整个毕设流程的压轴环节。我参与过不少答辩评审,评委的提问套路其实高度可预测,集中在这几类:
- “你的表为什么这样设计?这些字段的用途是什么?” 回答思路:讲清楚每张核心表的业务含义、外键关联、状态字段枚举含义。
- “你的系统安全性怎么考虑的?” 回答思路:用户密码BCrypt加密;JWT Token鉴权;拦截器权限校验;SQL使用预编译防注入。
- “如果并发1000个用户同时申请领养,你的系统会出什么问题?” 这题属于扩展题,回答方向是乐观锁/Redis分布式锁控制宠物状态更新,即使没实现也要把思路讲出来。
- “领养状态是怎么流转的?” 回答思路:直接画出状态机图,按申请提交-审核-完成的三段流程讲。
- “你的系统还有什么可改进的地方?” 准备两个合理的扩展方案,比如接入微信小程序端、基于内容推荐的相似宠物推荐、短信通知审核结果等。
演示环节有几个实战技巧,值得提前演练:
- 提前准备干净的演示账号和数据。把几只状态不同的宠物(待领养、已领养)提前造好,演示时按“浏览宠物-提交申请-管理员审核-状态变化”一路展示,节奏紧张感少很多。
- 提前测试图片加载速度,演示现场如果网速不行,图片加载卡顿会很影响观感。最好把所有图片资源提前传到项目内部或确保本地正常加载。
- 答辩PPT控制在10-12页,结构为:选题背景-需求分析-技术选型-系统设计-核心功能演示-测试结论-总结展望。页数不在多,关键是每张都要能讲满2分钟。
5. 实操中踩过的坑与最终体会
最后换个角度,说说我在实际做这类项目时踩过的坑,也是给大家的避坑清单。
第一个坑是时间分配严重失衡。有的同学前两周沉浸在环境搭建里,什么“版本不兼容”“依赖冲突”,一个连一个地浪费时间。我的建议是:如果你用Springboot 2.7.x搭配MySQL,用Initializr默认依赖,基本不会有大版本冲突;就算遇到,也不要在环境问题上死磕,换一个更稳妥的版本方案,时间成本要优先考虑。
第二个坑是前端联调时踩跨域坑。Vue开发环境默认跑在8081端口,后端是8080,Axios请求默认会被浏览器拦截。很多同学在这个问题上卡了一整天。解决办法是在后端加一个CorsConfig,或者在前端Vite/Webpack里配proxy代理。这里给一段后端跨域配置,亲测有效:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }第三个坑是图片上传后的回显路径问题。提交图片后数据库里存的是相对路径,前端页面展示时需要拼接一个基础访问路径。这里有两种做法:要么后端返回完整URL,要么后端把基础URL配进配置项。千万别直接存死为localhost:8080,后面前端部署到服务器时全部绝对地址失效,很容易翻车。
第四个坑是答辩前才发现数据被自己测试时搞乱了。建议在开发过程中定期做数据备份,保持一个“演示环境”和“开发环境”相互独立。演示环境的数据精心准备,多放一些照片可爱、档案信息完整的宠物档案,答辩演示效果能明显提升。
这个系统的后续扩展方向也很清晰。你可以把手机端做出来,使用Uniapp或微信小程序开发一套移动端页面;可以引入推荐算法,根据用户浏览和申请记录推荐相似宠物;可以接入消息通知服务,审核结果通过站内信或邮件的方式推送给用户。任何一个方向做出来,在简历上都是不错的项目亮点。
我个人的体会是,流浪动物领养系统这个题目的上限不低,关键看你是否真正把业务逻辑想透,把状态流转做扎实,把每一个模块讲明白。技术选型只是起点,真正的加分项,来自于你对这套业务系统完整而清晰的表达。