毕业设计做宠物领养系统,是近几年很常见的选题。原因不难理解:一方面领养代替购买确实是社会关注度高的方向,另一方面它天然包含用户注册、内容展示、后台管理、申请审核、状态流转这些功能点,正好能把SpringBoot和Vue的核心知识点都串起来。我手上这套SpringBoot+Vue宠物领养系统完整项目源码,配套了SQL脚本和接口文档,是某高校一位同学的毕业设计,整体完成度比一般课程作业要高不少,拿来作为Java Web方向的项目参考或者二次开发底座都挺合适。
这篇文章不打算只讲“怎么部署跑起来”,更想把整个项目从模块划分、数据库设计、前后端联调到部署上线整个链路拆开聊一聊。毕竟毕设答辩时候老师最常问的就是“为什么要这样设计”,如果只看代码不看思路,现场容易卡壳。
1. 项目整体架构与核心需求拆解
1.1 技术栈选型为什么是SpringBoot + Vue
宠物领养系统这类项目,业务逻辑不算特别复杂,但功能面比较宽:前台要展示宠物信息、用户注册登录、提交领养申请、收藏点赞;后台要管理宠物上架下架、审核领养申请、管理用户和公告。这种形态天然适合前后端分离,后端只管业务逻辑和接口,前端专注页面交互。
选SpringBoot的理由很实在:开箱即用,省掉大量Spring XML配置,内置Tomcat能直接打包运行,Spring Data JPA或者MyBatis-Plus操作数据库也很顺手。对毕设来说,SpringBoot是目前Java方向的主流选择,答辩时技术选型这块不会成为减分项。
前端选Vue同样是从实际出发:Vue 2或者Vue 3配合Element UI/Element Plus,几分钟就能搭出一套后台管理界面。组件化开发方式让宠物卡片列表、申请表单、审核弹窗这些重复性模块可以复用,开发效率比传统JSP高很多。再加上Axios做HTTP请求,Vue Router做页面路由,对于没有太多前端基础的同学来说,学习曲线也比React平缓。
1.2 六大核心功能模块梳理
整个系统如果按业务域拆,大概能分成六个模块:
- 用户端注册登录模块:包含用户注册、登录、个人信息维护。密码一般走BCrypt加密,不能明文入库。
- 宠物信息管理模块:前台展示待领养宠物列表,支持按品种、年龄、性别筛选;后台支持管理员新增、编辑、上下架宠物。重点功能是图片上传,这里涉及静态资源访问配置和文件存储路径处理。
- 领养申请模块:用户提交领养申请,填写个人情况、养宠经验等内容;管理员审核后状态变更。这个模块是整个系统业务价值最核心的部分,需要设计好状态机。
- 收藏与互动模块:用户对心仪的宠物点赞或收藏,方便后续查看。注意要给用户展示“我的收藏”列表。
- 公告与站内消息模块:管理员可发布公告,比如领养须知、活动通知;用户之间或者管理员与用户之间有站内消息往来。
- 后台管理模块:管理员操作后台,包括用户管理、宠物审核、领养审核、数据统计,一般还会加一个简单的看板展示宠物数量和领养成功率。
这套模块划分的逻辑是:用户端和后台管理端在物理上虽然是两个入口(可以做成两个前端项目,Vue通常分两个子工程或者用路由做区分),但共享同一套后端服务。这样设计的好处是后台的代码复用率很高,比如宠物上下架和审核可以调用同一套Service逻辑,只是接口权限不一样。
1.3 角色权限与数据隔离怎么设计
毕设项目通常不会引入特别复杂的安全性框架,但至少要有RBAC(基于角色的访问控制)的思想。这套系统里分了ROLE_USER和ROLE_ADMIN两种角色,通过对接口做拦截器配置,把管理员相关的接口全部保护起来。
数据隔离方面,普通用户只能操作自己的领养申请和收藏记录,前端做页面隐藏,后端做接口校验双保险。这些逻辑看似简单,但答辩考官很在意你是否有“敏感操作必须后端校验”的意识。很多同学只顾着前端隐藏按钮,忽略了后端接口谁都能调用,这是一个非常容易被问到的漏洞点。
2. 源码目录、数据库脚本与接口文档说明
2.1 前后端源码目录怎么看、怎么改
拿到源码压缩包后,别急着改代码,先花半小时把目录结构过一遍。后端Maven工程通常是标准结构:
pet-adoption-backend/ ├── src/main/java/com/example/adoption/ │ ├── controller/ // 接口层,接收HTTP请求 │ ├── service/ // 业务逻辑层 │ ├── mapper/ // MyBatis-Plus的Mapper层 │ ├── entity/ // 实体类 │ ├── config/ // WebMvc、拦截器、跨域配置 │ ├── common/ // 统一返回结果、异常处理 │ └── utils/ // JWT、文件上传等工具 ├── src/main/resources/ │ ├── mapper/ // XML文件 │ └── application.yml // 数据源、端口、文件上传路径配置 └── pom.xml前端如果用的是Vue CLI或者Vite,目录通常是这样:
pet-adoption-frontend/ ├── src/ │ ├── api/ // Axios接口请求封装 │ ├── assets/ // 静态资源 │ ├── components/ // 公共组件(宠物卡片、分页组件等) │ ├── router/ // 路由配置 │ ├── store/ // Vuex/Pinia状态管理 │ ├── views/ // 页面组件(首页、宠物详情、后台管理) │ ├── utils/ // request.js等工具 │ └── App.vue ├── vue.config.js // 开发代理配置 └── package.json建议重点关注application.yml里修改端口、数据库连接、文件上传路径、JWT密钥等几个参数。前端vue.config.js里的代理转发配置也很关键,开发环境跨域问题全靠它解决。
2.2 SQL脚本里都初始化了什么数据
SQL脚本是整包最有价值的部分之一,因为建表语句和初始化数据能直接反映作者对业务的思考。一般包含这几类内容:
第一是建库建表语句。核心表通常有:用户表user、宠物表pet、领养申请表adoption_apply、收藏表favorite、公告表notice、消息表message。字段命名风格要观察一下,比如是否统一用下划线命名,是否有created_at/updated_at这类通用字段。
第二是外键关系和索引。如果建表语句里没有任何外键,说明作者主要靠逻辑层关联。如果能合理添加普通索引(比如用户ID和宠物ID),说明考虑到了查询性能。
第三是初始化数据。管理员账号通常是admin/admin123这类固定值,还有若干宠物演示数据。这部分最适合拿来快速跑通项目验证功能。
导入SQL建议用Navicat或者DataGrip,先创建一个数据库实例,再执行脚本。如果遇到导入失败,大概率是字符集问题或者版本兼容问题,设置utf8mb4基本能解决。
2.3 接口文档的正确打开方式
接口文档一般有两种形式:集成Swagger/Knife4j的在线文档,以及Markdown格式的离线文档。这套项目配套的是Markdown接口文档,大概翻一遍能看出来作者把接口按模块分组:用户模块、宠物模块、领养模块、收藏模块、统计模块。
每个接口基本都包含请求方法、路径、请求参数、响应示例。建议重点关注领养模块的几个接口,因为领养审核的业务逻辑最复杂,涉及状态流转和多表更新。用Apifox或者Postman导入接口文档后,可以直接把后端跑起来逐个测试,比直接看代码理解得快。
3. 核心业务逻辑拆解与关键代码实现
3.1 领养申请状态机:从待审核到已完成
领养申请是整个系统业务价值最高的模块,设计得好不好一眼就能看出来。最简单的状态机至少包含以下状态:
待审核(PENDING):用户提交申请后的初始状态审核通过(APPROVED):管理员同意该用户的领养申请审核拒绝(REJECTED):管理员拒绝申请,需要填写拒绝理由已领养(ADOPTED):用户实际完成宠物领养已取消(CANCELED):用户主动取消申请
后端代码中通常会看到类似status字段存整型或者字符串,每次状态变更都伴随一次数据库update。这里有几个细节能体现代码水平:
第一,状态变更时必须校验前置状态。比如只有PENDING的申请才能被审核,已经审核通过的申请不允许再次审核,否则会出现逻辑冲突。很多同学忽略这个校验,导致同一份申请被审核两次。
第二,审核通过后需要联动更新宠物状态。宠物从“待领养”变成“已领养”,如果只是改了领养申请表的状态而忘记改宠物表,前台页面上这只宠物还会继续展示,这是典型的逻辑bug。这种联动要么用事务包裹,要么就放在同一个Service方法内。
第三,取消操作只能由用户本人发起,且只有PENDING状态可以取消。这个校验如果写在Service层而不是Controller层,数据安全性会更高。
3.2 JWT认证与登录态管理
毕设项目用Session+Cookie的也不少,但主流方案已经转向JWT。这套系统用的是JWT(JSON Web Token),登录成功后后端返回一个token,前端存在localStorage里,每次请求在Authorization请求头带上,后端拦截器解析校验。
核心实现通常是这样:
// 登录成功后生成Token String token = JWT.create() .withClaim("userId", user.getId()) .withClaim("role", user.getRole()) .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .sign(Algorithm.HMAC256(SECRET_KEY));拦截器里要做两件事:排除登录、注册等白名单接口,其余接口校验token是否有效。这里我建议加一层更细的权限控制,比如管理员接口用@RequireRole("ADMIN")这样的自定义注解配合拦截器再判断一次角色,代码可读性和安全性都更好。
实际踩过的坑是:token过期时间设置太短,用户填个申请表页面还没提交就被迫重新登录了,体验很差。建议不短于2小时,但也不要超过24小时。
3.3 宠物图片上传与访问路径处理
宠物图片上传是文件类操作的典型代表。前端用Element Plus的Upload组件,选中图片后通过Axios发送到后端接口,后端用MultipartFile接收,再存储到本地指定目录。
关键代码思路如下:
@PostMapping("/upload") public Result upload(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("上传文件不能为空"); } String originalFilename = file.getOriginalFilename(); String extName = originalFilename.substring(originalFilename.lastIndexOf(".")); String fileName = UUID.randomUUID().toString().replace("-", "") + extName; // 按日期建子目录,避免单目录文件过多 String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date()); String filePath = uploadDir + datePath + "/" + fileName; File dest = new File(filePath); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest); // 返回可访问的URL路径 String url = "/api/file/" + datePath + "/" + fileName; return Result.success(url); }有两个点很容易被忽略。一是上传目录如果不在项目根目录下,后端配置的静态资源映射路径要跟上;二是如果上线部署,文件保存路径不能写死,要通过配置项配置,否则部署环境不同就出错。常见做法是在application.yml里加一个upload.path配置项,代码中用@Value注入。
3.4 后台管理端的数据统计看板
数据统计看板是很多毕设的加分项。这套系统的后台首页展示宠物总数、领养成功数、待审核申请数、注册用户数,用Vue-ECharts画柱状图和饼图展示每月领养数量、宠物品种分布等。
后端统计接口通常用聚合查询实现,比如统计每月领养数量:
SELECT DATE_FORMAT(update_time, '%Y-%m') AS month, COUNT(*) AS count FROM adoption_apply WHERE status = 'ADOPTED' GROUP BY DATE_FORMAT(update_time, '%Y-%m') ORDER BY month;用MyBatis-Plus时可以通过QueryWrapper完成部分统计,也可以通过自定义SQL完成复杂统计。如果数据量不大,直接在内存里聚合也可以,但缺少数据库分组查询的亮点。答辩时如果能主动讲出来这类SQL的思路,会给人“确实是完整做过项目”的印象。
4. 本地部署与前后端联调全流程实操
4.1 环境准备:JDK、Maven、Node、数据库
先把环境准备好,版本对应关系很重要。JDK建议1.8版本(部分SpringBoot 3项目需要JDK17,看项目具体要求),Maven用3.6以上,Node看Vue项目具体版本要求(Vue CLI项目通常建议Node 14以上),MySQL建议5.7或8.0,且注意MySQL 8默认字符集是utf8mb4,部分SQL脚本如果不兼容建议提前调整。
安装顺序没有强约束,但建议MySQL最先装,因为后面导入SQL需要。装MySQL后建议把端口改成后端的连接配置,或者反过来改后端配置,总之两者保持一致。
4.2 导入SQL脚本并启动后端服务
第一步在Navicat里新建数据库,字符集选utf8mb4,排序规则选utf8mb4_general_ci,然后运行SQL文件。导入成功后检查一下表数量和数据行数,比如用户表是否初始化了管理员账号,宠物表是否有演示数据。
第二步打开后端工程,修改application.yml里的数据源配置:
server: port: 8088 spring: datasource: url: jdbc:mysql://localhost:3306/pet_adoption?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver如果后端用了MyBatis-Plus,通常不用写Mapper XML也能跑基础CRUD,但复杂查询需要确认XML路径配置是否匹配。
第三步直接在IDE里运行启动类。如果启动时报端口被占用,就换一个端口;如果报表不存在,说明数据库连错库或者SQL没导入成功。启动成功后控制台会打印Tomcat启动端口号,同时Spring Boot的启动横幅出现。
4.3 启动Vue前端并处理跨域问题
前端启动前要检查node_modules是否安装。如果压缩包里没带依赖包,打开终端执行npm install,这会花点时间。安装完成后再执行npm run serve,默认端口一般是8080或8081。
前后端分离项目必会遇到跨域问题。开发环境常见的解法是在vue.config.js里配置代理:
module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:8088', changeOrigin: true } } } };这段配置的含义是:前端页面请求/api/xx时,由开发服务器转发到后端的8088端口。这样一来浏览器里看到的请求是同源的,解决了跨域报错。
很多同学在这里卡住是因为后端WebMvcConfigurer里也加了allowedOrigins("*")这种跨域配置,结果和前端代理叠加导致问题。开发环境推荐只保留前端代理,生产环境则由Nginx做反代和统一入口。
4.4 本地联调时那些必踩的坑
第一坑是登录后请求接口报401。原因基本是token没取到,或者拦截器排除路径没写对。检查前端请求拦截器是否在Authorization头里带了token,检查后端拦截器允许的路径是否包含/api/auth/login和/api/auth/register。
第二坑是图片上传后页面打不开图片。原理是后端把文件保存到了一个目录,但没有把该目录映射成静态资源URL。需要在后端增加资源映射:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/api/file/**") .addResourceHandler("file:" + uploadDir); }第三坑是字段不一致导致前端显示空白。后端返回的字段名是userName,前端ESLint或者JavaScript里却用username,页面一直在报错却找不到原因。建议先打开浏览器控制台看接口实际返回的JSON字段名,再决定改前端还是改后端。
第四坑是npm install时报依赖版本冲突。Vue项目和Node版本匹配问题非常典型。一个快速检查方法是看package.json里Vue版本,Vue 2.6配Node 14比较稳,Vue 3配合Vite的项目通常要Node 16以上。
5. 常见问题排查与避坑技巧整理
5.1 启动类报错问题清单
我整理了本地部署时最高频的几类报错,按频率排序:
ClassNotFoundException: com.mysql.cj.jdbc.Driver,通常是mysql-connector依赖没导入或者版本太低。检查pom.xml里是否引入mysql-connector-java,并尽量用较新版本。
Whitelabel Error Page,说明后端接口本身报错了。此时打开后端控制台看具体异常堆栈,最常见的是空指针或者SQL语法错误。
java.sql.SQLSyntaxErrorException,大部分是SQL脚本执行不全导致缺表,或者数据库字段命名不统一。可以用navicat看表结构是否和实体类注解对应。
端口被占用,Windows下用netstat -ano | findstr 8088查进程,然后taskkill /PID xxx /F强杀。
5.2 页面加载很慢或数据不显示
这种问题排查思路是先定位是前端还是后端。按下F12打开Network面板,找到对应接口,如果状态码是200且返回了数据,说明问题在前端渲染;如果状态码500或者404,再对应处理后端。
404一般有两种情况:接口请求路径和后端Controller路径不一致;部署环境上下文路径不同导致路径前缀变化。建议全局搜一下后端@RequestMapping注解,比对前端api模块里写的路径。路径统一以/api开头能减少很多问题。
500通常查后端日志。最常见的几个原因是:查询的结果为null但代码里没判空;数据库字段名与实体类驼峰属性无法映射;日期格式转化失败。MyBatis-Plus可以在application.yml里开启下划线转驼峰,能少很多字段映射问题。
5.3 文件上传失败的排查
上传失败通常包括三种表现:上传接口报错、上传成功后图片不显示、上传成功但无法访问。
接口报错最常见的是上传目录不存在或者没有写权限。后端代码里mkdirs()是创建多级目录,如果没有这行代码就需要手动建目录。
图片不显示先看网络面板里图片请求的状态码。如果404,看addResourceHandler映射的路径是否和实际返回的URL匹配。这里有个很容易犯的错:返回给前端的URL如果是/api/file/2024/01/01/xxx.jpg,映射路径就得是/api/file/**,二者前缀必须一致。
5.4 数据库连接失败的特殊情况
MySQL 8和MySQL 5.7在驱动类名和URL参数上略有区别。MySQL 8的驱动类是com.mysql.cj.jdbc.Driver,URL需要加serverTimezone=Asia/Shanghai,否则会报时区错误。
如果连接失败报Access denied for user,先检查用户名密码对不对,再看看数据库是否只允许localhost访问。如果修改了MySQL的root密码,后端配置也要同步改。如果用了Docker启动MySQL,注意端口映射是否正确,容器内部3306端口要映射到宿主机对应端口。
5.5 毕设答辩中必问的几个问题怎么答
答辩时老师大概率会追问几个点,提前准备会非常加分。
第一个问题:“为什么用JWT不用Session?”回答可以这样说:Session需要后端维护状态,集群部署时还要解决Session共享问题,JWT本身携带用户信息、天然适合前后端分离项目;但同时要主动补充JWT的缺点,比如无法主动失效,续期需要额外机制,体现思考深度。
第二个问题:“领养状态字段是怎么设计的?”答出状态机设计思路,并说明状态流转校验存放在Service层,避免Controller直接改状态值,就能体现代码分层意识。
第三个问题:“上传的图片会存在哪里?项目部署后怎么办?”先说本地目录的静态映射方案,再补充说明生产环境更优做法是上传到OSS等对象存储服务,降低本地存储维护成本。哪怕实际没做OSS,有这种意识就不会被问倒。
第四个问题:“数据库表之间怎么关联的?”可以把用户表、宠物表、领养申请表的关系讲清楚,顺带说一下哪个表是中间表,在思考写SQL查询时的思路,让老师相信你确实是一行行代码写出来的。
6. 做毕业设计项目时额外想叮嘱的几件事
6.1 源码拿到手后先跑通再改,别一上来就动代码
很多同学拿到一个项目源码,迫不及待想改成自己想要的样式,结果还没跑通就改出一堆bug,最后都不知道是原项目的问题还是自己改的问题。正确顺序是:先按照文档原样部署一遍,跑通全部功能,再做增量改动。
增量改动的原则是一次只改一个点。比如想把宠物卡片改成左右布局,先改前端页面,不动后端接口;确认效果后再调整字段展示。如果一次改太多,出现问题时定位困难,尤其前端组件化项目依赖关系复杂,一个小改动可能会影响多个页面。
6.2 源码是“参考”不是“交差”
这个项目源码再完整,也只是一个处境参考。毕业设计的核心考察点是“你是否理解自己做的系统”,所以必须花时间去读完核心代码,尤其是Controller、Service层和SQL脚本,理解每部分为什么这样写。答辩时老师最反感的就是连自己项目的登录流程都讲不清的同学。
建议拿到源码后做三件事:第一,画一张登录注册模块的时序图;第二,整理一份所有接口的清单,标注哪个模块用哪些接口;第三,把数据库表的字段逐个过一遍,能说出每个字段含义以及是否冗余。做完这三步,基本就具备答辩游刃有余的基础了。
6.3 扩展功能可以从哪里入手
如果时间充裕,想在原项目基础上加亮点,可以从这几个方向选一个:引入Redis缓存宠物列表,降低数据库压力;增加管理员操作日志记录,用AOP实现日志切面;引入WebSocket实现用户和客服实时聊天;给宠物列表增加搜索排序功能,比如按最近发布排序或按品种筛选。
其中性价比最高的是Redis缓存,因为技术点新、代码改动量不大、答辩时又能讲出一套完整的缓存更新策略。实现思路不复杂:宠物列表接口先查Redis,命中就直接返回;没有命中就查数据库,再写回Redis并设置过期时间。宠物上下架操作时删除对应缓存,保证数据一致性。
6.4 关于打包部署上线的一点经验
如果想把系统真正跑起来展示给更多人看,本地开发环境通过代理访问后端只能在开发阶段用,正式部署还要解决打包问题。
后端打包用Maven打包成Jar,命令是mvn clean package -DskipTests。打包完成后用java -jar启动。前端打包用npm run build,生成dist目录,里面是纯静态文件。
前后端分离项目最常见部署方式是:Nginx托管前端静态文件,反向代理/api请求到后端端口。这样你的系统只有一个入口地址,前后端完全透明。Nginx配置核心部分如下:
server { listen 80; server_name yourdomain.com; location / { root /var/www/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8088; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }有一点非常容易被忽略:打包后前端如果用history模式路由,刷新页面会出现404,所以try_files $uri $uri/ /index.html一定要写上。这一步如果不知道,部署后检查没几分钟就能查出来。
最后分享几点这个项目给我的体会
带过不少同学用类似项目做毕设,我发现真正能把整个系统讲清楚的人,往往不是代码写得最多的,而是数据库字段动过脑筋的人。比如宠物表里有没有加健康状态字段、有没有疫苗记录字段、领养申请里有没有家庭住址字段,这些细节最能体现“站在使用者的角度思考系统”。
我自己的调试习惯是,每改动一个接口,都应该用Postman重新测一遍响应数据,确认没有破坏原有功能。做前后端分离项目,后端接口返回什么结构,前端页面就能长成什么样子,接口设计稳定了,前端开发才会顺利。如果前期接口参数改来改去,前端返工就是家常便饭。
最后想说的是,宠物领养系统本身并不复杂,它真正的难度在于把用户流程捋清、把状态流转做严谨、把异常情况处理到位。源码能帮你减少重复劳动,但系统里那些细节的经验,还是得靠自己一行一行写出来、一次一次调试出来。这个项目做完,SpringBoot和Vue这两条线基本就能串起来了,以后再做别的管理类系统,思路就是完全相通的。