简介:这是一套面向高校计算机相关专业学生的Java毕业设计完整项目包,以SpringBoot后端搭配Vue前端实现宠物领养业务系统,适合正在准备毕业设计、课程设计或期末大作业的同学直接参考与二次开发。资源共包含749个文件,涵盖124个Java源码、42个Vue组件、154个JavaScript脚本以及47个CSS样式文件,另有svg图标、gif与mp4演示素材、sql建库脚本、yml配置和bat启动脚本等,压缩包约62.68MB,前后端与数据库结构完整。项目已通过导师指导与答辩评审,得分97分,并在Windows 10/11环境下严格调试,下载后按说明即可运行。随包附赠PPT、开题报告、使用文档与演示视频,便于快速理解系统模块划分、接口设计与页面交互逻辑,也能为论文撰写和答辩展示提供现成素材。目前已有402人学习关注,适合希望节省搭建时间、聚焦业务实现与文档整理的同学使用。
1. 宠物领养系统毕设:从选题到答辩,一套能跑通的高分方案长什么样
每年毕业季,计算机专业的学生都会面临同一个问题:选什么题目既有技术含量,又能顺利做完,还能在答辩时让老师眼前一亮。宠物领养系统这个方向近几年热度一直不低,原因很直接——业务场景清晰、功能模块好拆分、技术栈主流,而且社会价值天然成立。但真正动手做的时候,很多人会卡在几个关键节点上:SpringBoot 和 Vue 怎么配合、数据库表怎么设计才合理、前后端联调时跨域和登录态怎么处理、答辩 PPT 和开题报告怎么写才能体现工作量。
这套基于 SpringBoot + Vue 的宠物领养系统,核心解决的就是「让毕设从能跑到能拿高分」这件事。它适合正在准备毕业设计、需要一套完整可复现方案的学生,也适合想用这个项目练手 SpringBoot 全栈开发的新手。接下来我会按实际开发顺序,把技术选型、环境搭建、核心功能实现、踩坑排查和答辩加分技巧逐层拆开讲,每一步都落到可执行的命令和代码上。
2. 技术选型与环境搭建:为什么是 SpringBoot + Vue 而不是别的组合
2.1 后端选 SpringBoot 的三个现实理由
做毕设选后端框架,核心考量不是「哪个最先进」,而是「哪个能让我在有限时间内把功能做完整,同时答辩时能讲清楚技术点」。SpringBoot 在这个场景下有天然优势。
第一,自动配置机制省掉了大量 XML 配置。传统 SSM 项目光是把 SpringMVC、MyBatis、数据源整合起来就要写一堆配置文件,版本不匹配还容易翻车。SpringBoot 的 starter 依赖把常用组件的默认配置都封装好了,引入spring-boot-starter-web和spring-boot-starter-data-jpa或mybatis-spring-boot-starter就能直接开发业务代码。
第二,内嵌 Tomcat 让部署变得简单。毕设项目通常不需要单独装 Tomcat,打成 jar 包直接java -jar就能跑,演示的时候少一个环境依赖就少一个出问题的环节。
第三,生态成熟,遇到问题容易搜到答案。这一点对毕设来说很关键——你不需要成为 Spring 专家,但你需要能在遇到报错时快速找到解决方案。
提示:SpringBoot 版本不要盲目追新。2.7.x 系列和 3.x 系列在 JDK 要求上有明显差异,3.x 需要 JDK 17 起步。如果学校机房环境不确定,选 2.7.x 更稳妥。
2.2 前端选 Vue 的实际考量
Vue 在毕设项目里的优势主要体现在两个方面:上手曲线平缓和组件化开发效率高。
Vue 的模板语法接近原生 HTML,指令系统(v-if、v-for、v-model)直观易懂,即使之前没接触过前端框架,花两三天也能写出可用的页面。组件化开发让页面复用变得简单——宠物列表卡片、领养申请表单、用户信息展示这些模块都可以封装成独立组件,在不同页面间复用。
前后端分离的架构下,Vue 通过 axios 调用后端 RESTful 接口,数据交互清晰。答辩时老师问「前后端怎么通信的」,你可以直接说「前端通过 axios 发 HTTP 请求,后端用 @RestController 返回 JSON 数据,跨域通过 CORS 配置解决」,逻辑链条完整。
2.3 环境搭建的具体步骤
后端环境搭建从创建项目开始。用 Spring Initializr 或者直接在 IDE 里创建 SpringBoot 项目,勾选以下依赖:
<!-- pom.xml 核心依赖 --> <dependencies> <!-- Web 开发:提供 RESTful 接口能力 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 持久层:操作数据库 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok:减少 getter/setter 样板代码 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>这段依赖配置里,spring-boot-starter-web提供了内嵌 Tomcat 和 SpringMVC 能力,spring-boot-starter-data-jpa负责数据库操作,Lombok 用来简化实体类代码。版本号由 SpringBoot 父 POM 统一管理,不需要手动指定。
前端环境搭建用 Vue CLI 或 Vite 创建项目:
# 使用 Vite 创建 Vue 3 项目(推荐,构建速度快) npm create vite@latest pet-adoption-frontend -- --template vue # 进入项目目录 cd pet-adoption-frontend # 安装依赖 npm install # 安装 axios 和 vue-router npm install axios vue-router@4 # 启动开发服务器 npm run devVite 相比 Vue CLI 的优势在于冷启动速度快,开发时热更新几乎无感知。npm run dev启动后默认在 5173 端口,后端 SpringBoot 默认在 8080 端口,前后端端口不同就需要处理跨域。
2.4 数据库设计的关键表结构
宠物领养系统的核心表不多,但设计时要把关系理清楚。主要包含用户表、宠物表、领养申请表、公告表这几张。
-- 用户表:存储注册用户和管理员 CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', `password` VARCHAR(100) NOT NULL COMMENT '加密后的密码', `role` VARCHAR(20) DEFAULT 'user' COMMENT '角色:user/admin', `phone` VARCHAR(20) COMMENT '联系方式', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 宠物表:核心业务表 CREATE TABLE `pet` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL COMMENT '宠物名字', `type` VARCHAR(20) COMMENT '类型:猫/狗/其他', `breed` VARCHAR(50) COMMENT '品种', `age` INT COMMENT '年龄(月)', `gender` VARCHAR(10) COMMENT '性别', `health_status` VARCHAR(50) COMMENT '健康状况', `description` TEXT COMMENT '详细描述', `image_url` VARCHAR(255) COMMENT '图片地址', `status` VARCHAR(20) DEFAULT 'available' COMMENT '状态:available/adopted/pending', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 领养申请表:关联用户和宠物 CREATE TABLE `adoption_application` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `pet_id` INT NOT NULL, `reason` TEXT COMMENT '领养理由', `status` VARCHAR(20) DEFAULT 'pending' COMMENT '审核状态', `apply_time` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`), FOREIGN KEY (`pet_id`) REFERENCES `pet`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;表设计里几个关键点:用户密码必须加密存储(用 BCrypt),宠物状态字段用枚举值控制流转,领养申请表通过外键关联用户和宠物形成多对多关系的中间表。字符集统一用 utf8mb4,避免中文乱码。
3. 核心功能实现:从宠物列表到领养审核的完整链路
3.1 后端 RESTful 接口的分层写法
后端代码按 Controller → Service → Repository 三层组织,这是 SpringBoot 项目最常见的结构。以宠物管理为例,先定义实体类:
// Pet.java 实体类 @Entity @Table(name = "pet") @Data @NoArgsConstructor @AllArgsConstructor public class Pet { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private String type; private String breed; private Integer age; private String gender; @Column(name = "health_status") private String healthStatus; private String description; @Column(name = "image_url") private String imageUrl; private String status; @Column(name = "create_time") private LocalDateTime createTime; }@Entity和@Table注解把类映射到数据库表,@Id和@GeneratedValue指定主键自增策略,@Column处理字段名和列名不一致的情况。Lombok 的@Data自动生成 getter、setter、toString 等方法。
Repository 层继承 JpaRepository 就获得了基本的增删改查能力:
// PetRepository.java public interface PetRepository extends JpaRepository<Pet, Integer> { // 按状态查询宠物列表 List<Pet> findByStatus(String status); // 按类型和状态组合查询 List<Pet> findByTypeAndStatus(String type, String status); // 按名字模糊搜索 List<Pet> findByNameContaining(String keyword); }Spring Data JPA 的方法名解析机制很实用——findByStatus会自动生成WHERE status = ?的查询,findByNameContaining生成WHERE name LIKE %?%。不需要手写 SQL 就能完成大部分查询需求。
Controller 层负责接收请求和返回响应:
// PetController.java @RestController @RequestMapping("/api/pets") @CrossOrigin(origins = "http://localhost:5173") // 允许前端跨域访问 public class PetController { @Autowired private PetService petService; // 获取可领养的宠物列表 @GetMapping public ResponseEntity<List<Pet>> getAvailablePets( @RequestParam(required = false) String type) { List<Pet> pets; if (type != null && !type.isEmpty()) { pets = petService.findByTypeAndStatus(type, "available"); } else { pets = petService.findByStatus("available"); } return ResponseEntity.ok(pets); } // 管理员添加宠物 @PostMapping public ResponseEntity<Pet> addPet(@RequestBody Pet pet) { pet.setStatus("available"); pet.setCreateTime(LocalDateTime.now()); return ResponseEntity.ok(petService.save(pet)); } // 更新宠物信息 @PutMapping("/{id}") public ResponseEntity<Pet> updatePet(@PathVariable Integer id, @RequestBody Pet pet) { pet.setId(id); return ResponseEntity.ok(petService.save(pet)); } }@RestController等价于@Controller+@ResponseBody,所有方法返回 JSON。@CrossOrigin解决前端 5173 端口访问后端 8080 端口的跨域问题。@RequestParam接收 URL 查询参数,@PathVariable接收路径参数,@RequestBody接收 JSON 请求体。
3.2 前端页面与接口对接
前端用 axios 封装统一的请求工具,避免在每个组件里重复写 baseURL 和拦截器:
// src/utils/request.js import axios from 'axios' const request = axios.create({ baseURL: 'http://localhost:8080/api', // 后端接口地址 timeout: 5000 // 请求超时时间 }) // 请求拦截器:自动携带 token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:统一处理错误 request.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { // 未登录,跳转到登录页 window.location.href = '/login' } return Promise.reject(error) } ) export default request拦截器的作用是统一处理请求头注入和错误响应。登录后把 token 存到 localStorage,后续每个请求自动带上,后端验证 token 有效性。401 状态码统一跳转登录页,避免在每个页面单独判断。
宠物列表页面组件:
<!-- src/views/PetList.vue --> <template> <div class="pet-list"> <div class="filter-bar"> <select v-model="selectedType" @change="fetchPets"> <option value="">全部类型</option> <option value="猫">猫</option> <option value="狗">狗</option> <option value="其他">其他</option> </select> </div> <div class="cards"> <div v-for="pet in pets" :key="pet.id" class="pet-card"> <img :src="pet.imageUrl" :alt="pet.name" /> <h3>{{ pet.name }}</h3> <p>{{ pet.type }} · {{ pet.breed }} · {{ pet.age }}个月</p> <p class="status">{{ pet.status === 'available' ? '待领养' : '已领养' }}</p> <button @click="applyAdoption(pet.id)" :disabled="pet.status !== 'available'"> 申请领养 </button> </div> </div> </div> </template> <script setup> import { ref, onMounted } from 'vue' import request from '@/utils/request' const pets = ref([]) const selectedType = ref('') const fetchPets = async () => { const params = selectedType.value ? { type: selectedType.value } : {} pets.value = await request.get('/pets', { params }) } const applyAdoption = async (petId) => { try { await request.post('/adoptions', { petId }) alert('申请已提交,请等待审核') } catch (e) { alert('申请失败:' + (e.response?.data?.message || '请先登录')) } } onMounted(fetchPets) </script><script setup>是 Vue 3 的组合式 API 写法,ref定义响应式数据,onMounted在组件挂载后调用接口获取数据。v-model双向绑定筛选条件,v-for渲染宠物卡片列表。申请领养时如果未登录,后端返回 401,响应拦截器会自动跳转登录页。
3.3 领养审核状态流转的实现
领养申请提交后,管理员需要在后台审核。状态流转逻辑是:用户提交 → pending → 管理员通过 → adopted(宠物状态同步更新)或 rejected。
// AdoptionService.java 核心审核逻辑 @Service @Transactional // 保证宠物状态和申请状态同时更新 public class AdoptionService { @Autowired private AdoptionApplicationRepository applicationRepo; @Autowired private PetRepository petRepo; public AdoptionApplication reviewApplication(Integer applicationId, String result) { AdoptionApplication app = applicationRepo.findById(applicationId) .orElseThrow(() -> new RuntimeException("申请不存在")); if (!"pending".equals(app.getStatus())) { throw new RuntimeException("该申请已处理,不能重复审核"); } app.setStatus(result); // "approved" 或 "rejected" applicationRepo.save(app); // 审核通过时同步更新宠物状态 if ("approved".equals(result)) { Pet pet = petRepo.findById(app.getPetId()) .orElseThrow(() -> new RuntimeException("宠物不存在")); pet.setStatus("adopted"); petRepo.save(pet); } return app; } }@Transactional注解保证两个更新操作在同一个事务里,要么都成功要么都回滚。审核前先检查当前状态是否为 pending,防止重复审核导致数据不一致。这个逻辑在答辩时是一个可以展开讲的点——数据一致性怎么保证的。
4. 避坑与排查:毕设开发中最容易翻车的五个地方
4.1 跨域问题反复出现
现象:前端调用后端接口时浏览器控制台报Access-Control-Allow-Origin错误,请求被拦截。
原因:前后端端口不同(5173 和 8080),浏览器同源策略阻止跨域请求。只在 Controller 上加@CrossOrigin有时不够,因为预检请求(OPTIONS)可能没被正确处理。
解决:在 SpringBoot 里配置全局 CORS,比在每个 Controller 上加注解更可靠:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:5173") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }allowedMethods必须包含 OPTIONS,否则预检请求会失败。allowCredentials(true)允许携带 Cookie,如果前端用 token 放在 header 里则不是必须的。
4.2 数据库中文乱码
现象:宠物描述、用户昵称等中文字段在页面上显示为问号或乱码。
原因:数据库字符集不是 utf8mb4,或者 JDBC 连接 URL 没指定编码。
解决:建表时指定DEFAULT CHARSET=utf8mb4,application.yml 里配置连接参数:
spring: datasource: url: jdbc:mysql://localhost:3306/pet_adoption?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai username: root password: your_passwordcharacterEncoding=utf8mb4是关键,serverTimezone不设置的话 MySQL 8.x 会报时区错误。
4.3 前端打包后刷新页面 404
现象:npm run build打包后部署到 SpringBoot 的 static 目录,访问首页正常,但刷新子路由页面报 404。
原因:Vue Router 默认用 history 模式,URL 看起来像/pets/1,但服务器上并没有这个物理路径。刷新时请求发到后端,后端找不到对应接口就返回 404。
解决:两种方案。一是 Vue Router 改用 hash 模式(URL 带 #),简单但不够美观。二是在后端加一个 fallback 配置,把所有非 API 请求转发到 index.html:
@Controller public class SpaController { @RequestMapping(value = "/{path:[^\\.]*}") public String forward() { return "forward:/index.html"; } }这个配置把所有不含点号的路径请求转发到 index.html,由前端路由接管。注意不要拦截/api/**路径。
4.4 密码明文存储被答辩老师追问
现象:答辩时老师查看数据库,发现用户密码是明文,质疑安全性。
原因:开发时图省事直接存了明文,或者用了 MD5 但没加盐。
解决:用 BCrypt 加密,Spring Security 提供了现成的工具类:
// 注册时加密 String encodedPassword = new BCryptPasswordEncoder().encode(rawPassword); user.setPassword(encodedPassword); // 登录时校验 boolean matches = new BCryptPasswordEncoder() .matches(rawPassword, user.getPassword());BCrypt 每次加密结果不同(自动加盐),但校验时能正确匹配。答辩时这是一个加分项——能说清楚为什么不用 MD5(彩虹表攻击)和 BCrypt 的优势(自适应计算强度)。
4.5 图片上传路径在打包后失效
现象:开发时图片上传到本地某个目录能正常显示,打成 jar 包部署后图片全部 404。
原因:开发时用的绝对路径(比如D:/upload/),打包后运行环境变了,路径不存在。或者图片存到了 jar 包内部,外部无法访问。
解决:把上传目录配置成相对于 jar 包运行目录的路径,并通过静态资源映射暴露出去:
# application.yml file: upload-dir: ./uploads/ # 相对于 jar 运行目录@Configuration public class WebConfig implements WebMvcConfigurer { @Value("${file.upload-dir}") private String uploadDir; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 把 /uploads/** 映射到本地文件目录 registry.addResourceHandler("/uploads/**") .addResourceLocations("file:" + uploadDir); } }这样上传的图片存在 jar 包外部的 uploads 目录,通过/uploads/文件名就能访问,重启或重新部署都不会丢。
5. 答辩加分项:开题报告、PPT 和演示视频的配合打法
5.1 开题报告里技术路线怎么写才不像模板
开题报告最容易被诟病的就是「技术路线」部分写成了一堆技术名词的堆砌。老师想看的是你为什么要选这些技术,以及它们怎么配合完成你的系统。
写法上建议按「问题 → 方案 → 理由」的结构展开。比如不要只写「后端采用 SpringBoot 框架」,而是写「系统需要提供 RESTful 接口供前端调用,同时要处理数据库事务和权限校验。SpringBoot 的自动配置和 starter 依赖能减少配置工作量,其事务管理注解可以保证领养审核时宠物状态和申请状态的一致性,因此选它作为后端框架」。
数据库设计部分要放 ER 图,说明表之间的关系。功能模块图不要画得太细,按用户角色(普通用户、管理员)分两块就够了。创新点不用硬凑,宠物领养系统本身业务逻辑清晰就是优点,你可以把「领养审核的状态流转」和「前后端分离架构」作为技术亮点来讲。
5.2 PPT 的页面分配和讲解节奏
答辩 PPT 控制在 12 到 15 页比较合适,时间通常 8 到 10 分钟。页面分配建议:
| 页码 | 内容 | 讲解时间 |
|---|---|---|
| 1 | 封面(题目、姓名、导师) | 10秒 |
| 2 | 选题背景与意义 | 40秒 |
| 3 | 技术选型与架构图 | 1分钟 |
| 4-5 | 数据库设计(ER图+核心表) | 1分钟 |
| 6-9 | 功能演示(截图+说明) | 3分钟 |
| 10 | 核心代码片段(选1-2处) | 1分钟 |
| 11 | 难点与解决方案 | 1分钟 |
| 12 | 总结与展望 | 30秒 |
功能演示部分放截图,每张图配一句话说明这个页面解决了什么问题。核心代码不要贴大段,选领养审核的事务处理或者跨域配置这种能体现思考的片段。
5.3 演示视频的录制要点
演示视频一般要求 3 到 5 分钟,录屏时注意几点:提前把测试数据准备好,不要现场录入;按用户操作流程走一遍完整链路(注册 → 浏览宠物 → 提交申请 → 管理员审核 → 查看结果);讲解语速适中,不要照着代码念;视频分辨率 1080p 足够,文件大小控制在 100MB 以内方便提交。
录制前把浏览器书签栏隐藏、无关标签页关掉,显得干净。如果答辩要求现场演示,提前在答辩用的电脑上把环境跑通,数据库连好,避免现场装环境翻车。
5.4 使用文档和源码整理的习惯
最后说一个我自己的习惯:从开发第一天就维护一个 README 文件,记录环境要求、启动步骤、数据库初始化脚本的位置。不要等到最后再来补文档,那时候很多细节已经忘了。源码提交前把application.yml里的数据库密码改成占位符,加一个application-example.yml作为模板,这样别人拿到你的代码知道怎么配置。
答辩前一周把整个项目从零部署一遍——换一台电脑,按 README 的步骤走,看能不能跑起来。这个过程能发现 90% 的环境问题。希望帮到你。
本文还有配套的精品资源,点击获取