每年到毕业季,总有人在群里问:“Java方向有什么既好实现、又能拿得出手的毕设题目?”我的回答一直很固定——基于SpringBoot + Vue的宠物领养救助管理系统。这个题目好就好在:技术栈主流、业务场景清晰、功能边界好控制,而且源码、数据库、文档都能自己完整落地。不管是导师追问还是答辩演示,每一个环节你都有东西可讲。
这篇文章不聊虚的,就以这个“源码+数据库+文档”三件套齐全的毕设项目为主线,把系统怎么拆、数据库怎么设计、后端怎么搭、前端怎么写、部署怎么避坑,全部给你捋一遍。适合正在选毕设题目的在校生,也适合想快速入门全栈开发的初学者。全文基于我实际开发同类项目时的经验整理,该给代码的地方给代码,该提醒的坑一个不落。
1. 项目整体设计与技术选型
1.1 为什么是SpringBoot + Vue这个组合
先说结论:这个组合是目前Java后端毕设里最稳的方案,没有之一。
后端用SpringBoot,核心优势是“约定优于配置”,你不需要像SSM时代那样写一堆XML配置文件。一个spring-boot-starter-web依赖加进来,内嵌Tomcat直接跑,开发调试的体验比传统Servlet项目好太多。而且SpringBoot的生态极其成熟,整合MyBatis、MySQL、Redis、JWT、MinIO这些常用组件都有一句话配置的解决方案,对时间紧张的毕业生特别友好。
前端用Vue,是因为它渐进式的设计非常适合一个人开发毕设。Vue的双向数据绑定能让你少写大量DOM操作代码,配合Element Plus这类组件库,后台管理页面的表格、表单、弹窗基本是拼积木式开发。更重要的是Vue的社区资料极多,遇到任何报错,一搜基本都有答案。对于不熟悉前端的同学来说,这个学习曲线是最缓的。
前后端分离是当今企业级项目的主流架构。前端负责页面渲染和用户交互,后端只暴露JSON接口,两者通过HTTP通信。这种模式的好处是:后端不用关心页面长什么样,前端不用关系SQL怎么写,各干各的,联调时用接口文档对齐就行。在毕设答辩时,你也可以很自然地讲出“前后端分离架构”这个概念,这是一个标准的加分说法。
1.2 项目核心模块与业务流程梳理
宠物领养救助管理系统,顾名思义,业务围绕两条主线展开:宠物领养和流浪救助。
领养主线走的是“动物救助站”模式:管理员或者志愿者把救助回来的宠物登记到系统,包括宠物种类、年龄、健康状况、救助地点、照片等;普通用户(领养人)浏览宠物列表,查看详情后提交领养申请;管理员审核领养申请,审核通过后在线下完成交接,系统里更新宠物状态为“已领养”。
救助主线走的是“求助工单”模式:用户看到流浪动物后,可以在系统提交救助请求,填写位置、动物状态、紧急程度等信息,附上现场照片;管理员或志愿者在后台看到工单,进行派单处理、跟进和结果反馈,最终关闭工单。
两条主线形成闭环之后,系统还需要支撑这些功能的基础能力:用户注册登录与角色权限、宠物档案增删改查、图片上传存储、公告资讯发布、数据统计看板。这些模块全部做成前后端配合的完整功能,就是一套可以直接演示、可以直接写进论文的毕设系统。
1.3 技术栈扩充与版本选型建议
基础三件套是SpringBoot + Vue + MySQL,但如果你想在众多毕设里显得不那么平庸,建议在下面几个点上做“小升级”:
- ORM框架:强烈建议用MyBatis-Plus而不是纯MyBatis。它自带通用Mapper、分页插件、条件构造器,增删改查的代码量能省一半,对写论文时的“系统实现”章节更是省事。
- 鉴权方案:用JWT + Spring拦截器实现,简单轻量,比引入Spring Security的一堆过滤器配置更适合毕设场景。
- 文件存储:本地存储够用,但如果你想加亮点,用MinIO做对象存储,把图片上传变成一个独立的文件服务。热搜里也常看到“minio加入到springboot”,说明这个点是很多人在摸索的。
- Excel导出:用EasyExcel把宠物列表或领养记录导出成Excel交给管理员,是一个性价比极高的加分功能,实现也就几十行代码。
版本选型上,SpringBoot建议用2.7.x或3.0.x之间犹豫的话,我建议老老实实用2.7.x。原因很现实:SpringBoot 3要求JDK 17或更高,而且包的命名空间从javax.*变成了jakarta.*,很多老教程的代码直接复制会报错。毕业设计追求的是稳妥落地,不是追新。Vue方面,如果你对前端基础一般,选Vue 2 + Element UI和Vue 3 + Element Plus都行,但如果现在从头学,直接上Vue 3,官网文档清晰、生态也已经成熟了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计与核心表结构
2.1 核心数据表与关联关系
数据库设计是毕设论文里最容易被评阅老师翻看的部分,也是你答辩时展示“系统设计能力”的关键章节。这套系统的数据库表,我建议分成三组:
用户与权限组:sys_user(用户表,含角色字段)、sys_role、sys_menu(菜单权限表),后两张表如果只是为了毕业设计,可以简化合并,甚至只需在用户表里用一个role字段区分普通用户和管理员即可。如果时间充裕,做三张表的RBAC模型,答辩时会更有说服力。
业务核心组:pet(宠物信息表)、adoption_apply(领养申请单)、rescue_request(救助工单)、pet_category(宠物分类)、adoption_contract(领养协议或回访记录)。
辅助支撑组:notice(公告表)、file_record(文件记录表,用于统一管理上传的图片文件)、operation_log(操作日志表,加分项)。
表与表之间的关联很清晰:pet属于一个pet_category,被多个adoption_apply申请;adoption_apply由sys_user发起,针对某一只pet;rescue_request由sys_user发起,由管理员处理。这些关系在ER图里画出来,论文的“数据库设计”章节就丰满了一半。
2.2 字段设计与关键约束
以pet表为例,核心字段设计如下:
sql复制CREATE TABLE `pet` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(50) DEFAULT NULL COMMENT '宠物昵称',
`category_id` bigint(20) DEFAULT NULL COMMENT '分类ID,关联pet_category',
`gender` tinyint(1) DEFAULT NULL COMMENT '性别:0未知 1公 2母',
`age` varchar(20) DEFAULT NULL COMMENT '年龄描述,如2个月/3岁',
`health_status` varchar(200) DEFAULT NULL COMMENT '健康状况描述',
`vaccinated` tinyint(1) DEFAULT NULL COMMENT '是否已免疫:0否 1是',
`sterilized` tinyint(1) DEFAULT NULL COMMENT '是否已绝育:0否 1是',
`status` tinyint(1) DEFAULT '0' COMMENT '状态:0待审核 1可领养 2审核中 3已领养 4已下架',
`cover_image` varchar(255) DEFAULT NULL COMMENT '封面图URL',
`detail_images` text COMMENT '详情图,逗号分隔多个URL',
`rescue_address` varchar(255) DEFAULT NULL COMMENT '救助地点',
`rescue_date` date DEFAULT NULL COMMENT '救助日期',
`description` text COMMENT '宠物描述',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_status` (`status`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宠物信息表';
这里面的关键设计有几处,答辩时值得展开讲讲:
- 状态字段用
tinyint加注释,而不是直接用字符串,好处是存储空间小、查询效率高,配合Java枚举类,在代码里可以做状态机流转校验。 - 封面图和详情图分开字段存储,封面图用于列表页缩略图展示,详情图用于Detail页的图片轮播,很贴合真实业务场景。
- 索引策略:
status和category_id都是查询频率很高的字段,加上普通索引就够了,没必要搞联合索引,因为查询条件组合并不固定。 - 所有时间字段用
datetime,配合MyBatis-Plus的自动填充功能,让create_time和update_time在插入和更新时自动落值,省去每一条SQL手写时间。
2.3 初始化数据的准备
一套漂亮的初始数据,对毕设演示的观感提升是巨大的。你不可能在答辩现场临时去注册用户、去上传宠物图片,所以一定要在数据库脚本里预置好数据。
你需要准备至少:一个管理员账号、一个普通用户账号(密码加密后存储);8到12只不同状态、不同种类的宠物数据,配上清晰的图片URL;3到5条救助工单数据,包括不同紧急程度和处理状态;几条公告和新闻内容。
图片URL建议直接把网络图床的稳定外链写进SQL脚本,或者启动系统后通过后台上传一次,再把URL固化到初始化脚本里。这里有一个坑要提醒:不要用localhost作为图片URL,演示时如果换了电脑或者被别人访问,图片会全部裂掉。要么用相对路径配合MinIO或本地存储的映射访问,要么用公网可访问的图片地址。
3. SpringBoot后端核心实现
3.1 项目结构分层与统一返回格式
后端项目的包结构,我建议按照“controller-service-mapper-entity-common”五层来组织:
code复制com.example.petadoption
├── controller # 接口层,只做参数接收和结果返回
├── service # 业务逻辑层,接口+实现类
├── mapper # MyBatis-Plus的Mapper接口
├── entity # 数据库实体类
├── dto # 前端请求/响应参数封装
├── common # 统一返回结果、异常处理、常量、工具类
└── config # 配置类(跨域、拦截器、MinIO、MyBatis-Plus)
分层的好处是职责单一:Controller只处理HTTP请求,不写业务代码;Service层做具体业务校验和逻辑编排;Mapper层只负责数据库交互。论文写“系统设计”章节时,这个结构可以直接复用,每一层写一段职责说明,图都不用重画。
统一返回格式是前后端分离项目的标配,定义一个R对象:
java复制@Data
public class R<T> {
private Integer code;
private String message;
private T data;
public static <T> R<T> success(T data) {
R<T> r = new R<>();
r.setCode(200);
r.setMessage("操作成功");
r.setData(data);
return r;
}
public static <T> R<T> error(String message) {
R<T> r = new R<>();
r.setCode(500);
r.setMessage(message);
return r;
}
}
所有接口的返回值都包一层R,前端Axios拦截器里统一判断code,为200取data,否则统一弹出错误提示。这样写的好处是:异常处理逻辑在前端只写一次,后端切面里出现未捕获异常时也能返回一个格式统一的错误响应。
3.2 登录鉴权与角色权限设计
毕设系统的鉴权不需要做得太复杂,但一定要有,因为“系统安全性”是评阅老师喜欢问的点。我的方案是 JWT + 拦截器 + 注解。
用户登录成功后,后端用jjwt库生成一个带用户ID和角色的Token,返回给前端。前端把Token存到localStorage,每次请求在Axios请求拦截器中加上Authorization: Bearer <token>头。后端写一个拦截器统一校验Token,通过后把用户信息放到ThreadLocal或请求上下文里,供Service层使用。
java复制@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行登录接口和预检请求
if (request.getMethod().equals(HttpMethod.OPTIONS.toString())) {
return true;
}
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new BusinessException(401, "未登录或Token已过期");
}
String token = authHeader.substring(7);
Long userId = jwtUtil.parseToken(token);
if (userId == null) {
throw new BusinessException(401, "登录状态无效,请重新登录");
}
// 将用户ID放入上下文,供业务层取用
UserContext.set(userId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
UserContext.clear();
}
}
角色权限方面,在Service层写一个简单的校验方法,比如只有管理员能调用/admin/**下的接口。用拦截器根据URL前缀做一级拦截,再在管理员相关Service方法内部用@RequireRole("ADMIN")这种自定义注解做二级校验。层层校验的好处是,即便前端漏掉了某个按钮的隐藏逻辑,接口层面也兜住了权限边界。
这里必须提醒一个坑:拦截器放行白名单一定要包含登录接口和Swagger文档路径,否则系统一启动,前端调登录接口直接报“未登录”。各URL的分类逻辑放在Config里做,不要写在拦截器类里写死,否则后期加接口很容易漏放。
3.3 领养申请流程的状态机设计
领养申请是整个系统里最复杂的业务逻辑,因为它的状态不是一张表里改个字段那么简单,它需要流转。我先定义一张申请表的核心状态:
- 0 待审核:用户提交申请,等待管理员处理
- 1 已通过:管理员审核通过,待用户确认线下领养
- 2 已拒绝:申请被驳回,用户可以看到驳回原因
- 3 已完成:宠物已被领养人实际接走
- 4 已取消:用户或管理员主动取消申请
在代码中,我用一个枚举类来管理状态,每个状态定义允许跳转到哪些下一个状态:
java复制public enum ApplyStatusEnum {
PENDING(0, "待审核"),
APPROVED(1, "已通过"),
REJECTED(2, "已拒绝"),
COMPLETED(3, "已完成"),
CANCELLED(4, "已取消");
private final Integer code;
private final String desc;
// 允许的状态流转:PENDING -> APPROVED/REJECTED/CANCELLED; APPROVED -> COMPLETED/CANCELLED
}
Service里更新状态前先校验当前状态是否允许目标状态跳转,否则抛出业务异常。比如用户提交领养申请时,后端要同时校验三件事:宠物状态必须是可领养、当前用户对该宠物没有未完成的申请、申请表单必填字段是否齐全。
关键在于,当某条申请被审核通过时,宠物状态同步变为已领养,并且其他所有待审核状态的申请自动变为已拒绝,理由写“该宠物已被其他用户领养”。这个联动逻辑是业务里的经典场景,写进论文是很好的业务亮点,答辩时也能讲得有深度。
对于救助工单,也可以套用同样的模式:待派单、处理中、已完成、已关闭。核心原则是一致的——用状态机替代散落的if-else,让状态流转有据可查、有规可依。
3.4 宠物图片上传:将MinIO接入SpringBoot
图片上传是宠物系统的刚需。本地存储虽然简单,但项目部署路径一变、或者上传的文件多了之后,管理会很乱。这里推荐用MinIO做对象存储,它就是一个开源的S3兼容服务,本地起一个服务端,就相当于自己搭了一个迷你“OSS”。
后端集成MinIO很直接:
xml复制<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version>
</dependency>
java复制@Service
public class FileService {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.access-key}")
private String accessKey;
@Value("${minio.secret-key}")
private String secretKey;
@Value("${minio.bucket}")
private String bucket;
public String upload(MultipartFile file) throws Exception {
MinioClient client = MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
// 判断桶是否存在,不存在则创建
boolean exists = client.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
if (!exists) {
client.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
}
String objectName = UUID.randomUUID().toString().replace("-", "")
+ "." + FilenameUtils.getExtension(file.getOriginalFilename());
client.putObject(PutObjectArgs.builder()
.bucket(bucket)
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
return endpoint + "/" + bucket + "/" + objectName;
}
}
这里有几个必须注意的细节:
- 文件名用UUID重命名,不要用用户上传的原始文件名,原因一是避免中文乱码,二是防止重名覆盖,三是防止文件名包含特殊字符引发安全问题。
- 内容类型要透传,也就是
file.getContentType(),否则浏览器访问图片时会强制下载而不是直接展示。 - 桶策略要设置为公开读,这样返回的URL直接能访问,否则前端拿到图片URL但是无法加载,调试半天都不知道问题在哪。
- 如果不想装MinIO,退而求其次用本地磁盘存储,记得配置一个静态资源映射路径,把
/upload/**映射到磁盘目录,这也能达到类似效果。
4. Vue前端核心实现
4.1 Vue环境配置与项目初始化
前端这一侧,第一步是装环境。你需要安装Node.js(建议LTS版本,比如18.x或20.x)、npm或yarn包管理器。检查装没装好就用两个命令:
bash复制node -v
npm -v
然后通过Vue官方脚手架创建项目。如果你用的是Vue 3,推荐用create-vue这个更轻量的方式,也可以走传统的vue create:
bash复制npm create vue@latest pet-adoption-web
创建过程中会问你需不需要TypeScript、Router、Pinia等,毕设项目建议选JavaScript而不是TypeScript,能少碰很多类型报错的麻烦。路由器选上,Pinia状态管理选上(比Vuex简单),其他都可以不选。
项目建好之后,安装核心依赖:
bash复制npm install axios element-plus
Element Plus是Vue 3配套的组件库,表格、表单、消息提示、分页全都有现成组件。这一步做完,你就有了一套可跑的前端骨架。接下来最重要的两件事:封装Axios请求工具、配置路由守卫。
4.2 Axios封装、路由守卫与动态路由
Axios封装的核心目标是:让每个接口调用处只关注业务数据,不重复处理Token、错误提示、加载状态。下面是一个很实用的封装思路:
javascript复制import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from '@/router'
const request = axios.create({
baseURL: '/api', // 配合后端网关地址或代理
timeout: 15000
})
// 请求拦截器:带上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 => {
const res = response.data
if (res.code === 200) {
return res.data
}
if (res.code === 401) {
localStorage.removeItem('token')
router.push('/login')
ElMessage.error('登录状态已过期,请重新登录')
return Promise.reject(new Error(res.message))
}
ElMessage.error(res.message || '请求失败')
return Promise.reject(new Error(res.message))
},
error => {
ElMessage.error(error.message || '网络异常')
return Promise.reject(error)
}
)
export default request
路由守卫用于控制页面访问权限。没有登录的用户只能访问首页和登录页,普通用户可以访问领养申请相关的页面,管理员可以访问后台管理子路由:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
const role = localStorage.getItem('role')
if (to.meta.requiresAuth && !token) {
next('/login')
} else if (to.path.startsWith('/admin') && role !== 'ADMIN') {
next('/403')
} else {
next()
}
})
很多同学做动态路由是希望不同角色登录后看到不同的菜单。这个在毕设里可以用一种更省事的方式实现:菜单数据存在前端,根据角色字段用v-if控制显隐。只有管理端页面数量特别多、权限层级复杂时,才有必要用后端返回动态路由表。你可以在答辩时提到“如果要扩展更多角色,可以把菜单配置挪到数据库,用动态路由实现”,展示你对这个方案的理解就够了。
4.3 核心页面拆解与实现思路
前端页面不用追求数量多,但要保证质量。我建议核心页面围绕以下六个来写:
- 首页:导航栏 + 宠物卡片列表 + 公告栏 + 页脚。宠物卡片用Element Plus的
Card组件,配合el-carousel轮播展示救助故事,视觉效果好。这里能放一张可爱的猫狗图片,演示时会让氛围轻松很多。 - 宠物详情页:宠物照片轮播、基本信息标签、领养条件说明、“申请领养”按钮。详情页的数据是上一步从后端通过
/pet/{id}接口获取的。申请领养按钮点击后弹出表单,表单里有“住宅类型”“家庭成员”“养宠经验”等字段,模拟真实领养审核场景。 - 领养申请页(个人中心):展示我的申请列表,每条申请显示对应的宠物缩略图、申请状态、处理时间。已经通过审核的申请显示“联系管理员交接”的提示信息。
- 后台管理-宠物管理页:宠物列表表格 + 搜索框(按名称、分类、状态筛选)+ 新增/编辑弹窗 + 图片上传。表格用
el-table,分页用el-pagination,图片上传用el-upload配合后端文件接口。 - 后台管理-申请审核页:所有待审核的领养申请列表,点开详情能看到申请人的联系方式、申请理由。审核操作有两个按钮:通过、拒绝(填理由)。审核通过时要弹窗提示“该操作将同步更新宠物状态为已领养”。
- 后台管理-救助工单页:救助工单的地图分布预览(可选)、表格列表、派单处理、进度回写。
这些页面之间通过Vue Router连接,每个页面都对应后端的1到3个接口,形成了完整的前后端交互链路。写代码的时候建议先写后端接口、用Swagger或Postman调通,再写前端页面去对接,这种“后端先行”的顺序比前后端同时乱写效率高得多。
4.4 Vue打包部署与SpringBoot整合
现在很多毕设要求把前端打包后和后端一起发布,最容易踩坑的就是这一步。前端打完包之后,默认生成一个dist目录,里面是index.html和一堆静态资源。把它整合进SpringBoot,主要有两种做法:
推荐做法:用Nginx做前后端分离部署
前端dist目录扔到Nginx的html目录,后端SpringBoot跑在8080端口,Nginx把所有/api开头的请求反向代理到后端地址。这样前端和后端是完全独立的,后面你改前端代码只要重新打包替换dist,不用重启Java服务,联调效率最高。
nginx复制server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://127.0.0.1:8080/api;
}
}
更省事的做法:把dist文件复制进SpringBoot的static目录
SpringBoot默认把classpath:/static/下的文件当作静态资源处理,所以直接把dist里的内容复制到src/main/resources/static/下,然后启动后端,访问http://localhost:8080就能看到前端页面。但这里有一个大坑:Vue默认使用history模式的路由,打包后跳转子页面刷新会出现404,因为你访问/pet/123时后端没有对应的Controller处理。
解决办法有两个:一是后端写一个ViewController把非/api和静态资源的路径都转发到index.html;二是Vue路由改用hash模式,URL会变成/#/pet/123这种带#的形式,刷新不会请求后端路径。毕设演示为了省心,可以先用hash模式;想展示专业性,就配合后端转发处理history模式。这两种方法我都试过,对毕设而言hash模式最省事,history模式更显深度,看你答辩时想讲哪个。
5. 典型问题排查与避坑实录
5.1 数据库连接与连接池问题
数据库方面的报错,90%集中在几个问题上,我把常见情况和解决方案整理成了下表:
| 报错/现象 | 根本原因 | 解决办法 |
|---|---|---|
Access denied for user 'root'@'localhost' |
数据库密码不对或用户权限不足 | 核对application.yml中的用户名密码,注意MySQL 8默认认证插件是caching_sha2_password,如果连接驱动版本低会报错,升级mysql-connector-java到8.0.x |
Public Key Retrieval is not allowed |
MySQL 8使用caching_sha2_password认证时驱动默认禁用了公钥获取 |
在JDBC URL加参数allowPublicKeyRetrieval=true&useSSL=false |
连接一段时间后报Connection is not available |
连接池耗尽或连接被数据库服务端断开 | 给数据源配置hikari.maximum-pool-size和connection-timeout,使用SpringBoot默认的HikariCP即可 |
| 插入中文数据乱码 | 数据库、表、连接字符集不一致 | 建表指定utf8mb4,连接URL加characterEncoding=utf8 |
关于连接池,这里多说一句:SpringBoot 2.x默认内置HikariCP,它是目前性能最好的Java连接池,你不需要额外引入Druid。除非你想在论文里写“使用Druid监控SQL性能”,否则HikariCP足够。如果你用Druid,记得排除掉SpringBoot自带的HikariCP依赖,否则两个连接池配置会发生冲突,日志里会出现一堆奇怪的报错。
5.2 跨域与前后端联调问题
前后端分离项目,跨域问题是新手最常卡住的地方。现象就是:前端页面能打开,但所有请求都不通,浏览器F12控制台报CORS policy错误。
解决跨域最简单的方式是在后端加一个全局配置类:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
这段配置放行所有来源的跨域请求。但要提醒一个细节:当你加了JWT拦截器后,OPTIONS预检请求必须在拦截器里放行,否则浏览器发出的预检请求会被拦截器挡在门外,跨域问题依然存在。我之前在JwtInterceptor.preHandle里专门加了if (request.getMethod().equals(HttpMethod.OPTIONS.toString())) return true;,就是为了规避这个问题。
如果你用了我上面提到的方式,让后端通过Nginx代理/api路径,那么前后端实际上是同源的,根本不存在跨域问题。这两种方式选一个就好:要么后端全局配置CORS,要么用Nginx/代理转发,二选一,不要两个同时搞,反而可能引起冲突。
5.3 Maven构建、版本兼容与打包问题
后端打包最常见的几个报错,都是环境级别的,这里集中说一下:
- JDK版本与SpringBoot版本不匹配:SpringBoot 2.x需要JDK 8或11,SpringBoot 3.x需要JDK 17。如果你电脑装的是JDK 17,idea里又新建了一个2.7.x的项目,编译会报
java: error: invalid source release: 17。解决方法是到项目结构里把Project SDK和Modules的Language level调成一致。 - Maven依赖下载缓慢或失败:国内网络环境下,建议在
~/.m2/settings.xml里配置阿里云镜像。
xml复制<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
- 打包后找不到主类:SpringBoot项目打包用的是
spring-boot-maven-plugin,如果你在pom里额外配置了maven-jar-plugin或者没有正确地指定主类,打出来的jar包运行会报no main manifest attribute。检查pom里是否有:
xml复制<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
- 接口返回的数据前端undefined:这个问题看似前端,根源多半是后端实体类字段命名不规范。比如数据库字段
health_status,Java实体类如果写成healthstatus或者没开MyBatis-Plus的驼峰映射,JSON返回的字段就和前端对不上。建议所有实体类字段严格遵循驼峰命名,并在application.yml里开启注解式驼峰转换,或者统一用DTO做字段映射。
5.4 常见问题速查与避坑清单
最后整理一份毕设全流程避坑清单,每一行都是从真实开发中沉淀出来的经验:
| 阶段 | 高频问题 | 避坑建议 |
|---|---|---|
| 环境准备 | Node和npm版本过新或过旧导致依赖安装失败 | 使用Node 18/20 LTS,遇到ERR_PNPM这类报错优先换npm或yarn重试 |
| 数据库初始化 | 导入SQL时报语法错误 | 用Navicat或命令行的source方式导入,注意SQL文件编码为UTF-8 |
| 后端联调 | Swagger文档能打开但接口报404 | 检查Controller的@RequestMapping路径是否和前端调用路径一致,核对/api前缀 |
| 文件上传 | 上传成功但图片打不开 | 检查MinIO桶权限、返回URL是否包含endpoint,先用浏览器直接访问URL测试 |
| 前端路由 | 刷新页面404 | 部署到Nginx时配置try_files;打包进SpringBoot时用hash模式或后端转发 |
| 答辩演示 | 电脑没网,前端CDN资源加载失败 | 所有前端依赖用npm安装并打包,不要用线上CDN引用;MinIO本地服务提前启动好 |
| 论文查重 | “系统实现”章节描述和网上雷同 | 用自己的代码结构重新组织表述,多写配置参数和具体实现细节,少复制概念性段落 |
| 时间管理 | 功能越做越拖,没时间写论文 | 先做核心领养闭环,再做救助工单,公告和统计放最后,文档边做边写 |
一个实际演示中的小技巧
最后,分享一个我在预答辩和正式演示时总结出来的实操技巧:提前准备两组演示数据。一组是“完美幸福结局”的数据——宠物全部被领养、工单全部已处理;另一组是“待办警示”的数据——有3只待审核的宠物、2条待处理的救助工单。正式演示时用第二组,这样你在点击“审核通过”“处理工单”时,页面有状态变化,表格分页有跳转,数据库字段在变,评委能看到实时的交互效果,远比打开一个全静态的系统页面更有说服力。
还有一个很多人忽视的细节:演示前把MySQL服务、MinIO服务(如果用了)和SpringBoot后端按顺序启动好,浏览器里提前登录好账号。别等到上了讲台才插U盘、装环境,这些准备工作在答辩当天比任何代码优化都重要。根据我的经验,大部分答辩翻车都不是因为项目不行,而是环境没起好、演示数据没准备好。这个系统的每个功能模块都可以继续往深做,比如对接微信小程序做移动端领养申请、引入ElasticSearch做宠物搜索、用WebSocket做领养进度实时通知,这些扩展方向你在论文的“总结与展望”章节点两句,评委对系统的完整度印象会提升一个档次。核心还是在基础功能上打磨到位,先把SpringBoot后端和Vue前端的每一条链路走通,再考虑锦上添花的事。
