
每年到毕业季SpringBoot Vue就是Java方向学生的顶流技术组合这不奇怪。一个是后端生态里用起来最顺手的框架一个是前端工程化最稳定的框架前后端分离又是现在企业开发的主流形态。而“宠物服务系统”这个名字一听就是典型的全栈业务系统宠物信息管理、领养申请、护理预约、回访记录这些功能足够撑起一份像模像样的毕业设计又不会超出学生能驾驭的复杂度。这篇内容我按实操角度来写把从需求分析到前后端联调再到部署的关键点一次讲透准备做这类项目的朋友可以直接拿来当参考。1. 项目背景与整体设计思路1.1 这套系统到底要解决什么问题我见过不少同学毕设选题是“XX管理系统”但做了三个月还在堆CRUD。宠物服务系统比普通管理系统的优势在于它有清晰的业务闭环不是一个单纯增删改查的列表页面而是有一个完整的服务流程在里面。线下宠物店的日常业务很零散客户想看有哪些宠物可以领养需要一家店一家店跑想预约洗澡、打疫苗只能打电话或者到店登记领养之后宠物状态如何、多久回访一次全靠工作人员拿本子记。这套系统要解决的问题就是把“宠物信息展示—领养申请—护理预约—服务记录—回访跟进”这些环节全部线上化让用户、护理人员、管理员在同一个平台上各司其职。所以它天然适合做毕业设计因为它既有明确的角色划分又有完整的业务状态流转。评阅老师看重的不是页面漂不漂亮而是系统能不能把某个实际场景讲圆、实现完整。1.2 为什么选SpringBootVue这对组合技术选型这件事很多人会纠结但在我看来毕业设计不需要追新要的是稳妥。SpringBoot的自动配置机制极大降低了SSM时代繁琐的XML配置成本内嵌Tomcat也让部署变得简单写完一个java -jar就打进服务器这对学生来说非常友好。Vue.js则是目前国内招聘市场上应用最广的前端框架之一组件化开发思路清晰配合Element UI这类组件库能在极少代码量下做出像模像样的后台管理界面。选择这对CP还有一个现实原因资源多。遇到底层问题搜索引擎一搜就能找到解决方案社区成熟度对毕设来说太重要了。这套组合在企业级业务系统里也是主流方案面试时聊起来也拿得出手。1.3 核心角色与主业务流程梳理系统按角色来划分通常分三类普通用户访客/注册用户、护理人员服务提供方、管理员。角色不同看到的功能菜单也不同。普通用户进入系统后可以浏览宠物信息列表、查看宠物详情、提交领养申请、预约护理服务、查看自己的申请进度和服务记录。护理人员可以查看分配给自己的护理预约单、完成服务后填写服务记录、更新宠物健康状态。管理员则负责宠物信息的上下架、用户管理、领养申请审核、护理预约调度、回访记录管理以及全局数据统计。核心业务流转是这类系统的灵魂。以领养为例用户提交领养申请后宠物状态仍是“待领养”申请状态为“待审核”管理员审核通过后宠物状态变为“已领养”同时生成一条待回访记录回访完成后领养流程才算真正走完。这个状态流转是画流程图时最重要的素材也是答辩时的亮点同学们一定要在设计文档里写清楚。1.4 数据库设计的关键决策数据库设计决定了系统能走多远。宠物服务系统的核心表至少包括用户表、宠物信息表、领养申请表、护理服务类型表、护理预约表、服务记录表、回访记录表。业务表之间用外键逻辑关联比如领养申请表关联用户ID和宠物ID护理预约表关联用户ID、宠物ID和服务类型ID。在设计时有一个容易犯的错就是喜欢用各种冗余字段存业务状态。我在做这类系统时习惯用一个status字段管理状态流转配合update_time做时间戳记录。以宠物信息表为例CREATE TABLE pet_info ( id BIGINT AUTO_INCREMENT PRIMARY KEY, pet_name VARCHAR(50) NOT NULL COMMENT 宠物名称, pet_type TINYINT DEFAULT 0 COMMENT 0-猫 1-狗 2-其他, breed VARCHAR(50) COMMENT 品种, age INT COMMENT 年龄月份, gender TINYINT DEFAULT 0 COMMENT 0-公 1-母, status TINYINT DEFAULT 0 COMMENT 0-待领养 1-已被领养 2-护理中 3-已下架, cover_image VARCHAR(255) COMMENT 封面图URL, description TEXT COMMENT 详细描述, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );不要一开始就设计太复杂的表结构先把业务主链路跑通再逐步加字段这是我反复强调的一点毕业设计最怕在前期过度设计导致后面改不动。比如回访记录可以后面再加领养申请审核字段也可以后面迭代先保证核心链路完整。2. 后端核心模块设计与实现2.1 工程搭建与目录规划后端工程我建议直接使用Spring Initializr初始化这比手动搭建要快得多。需要注意的是SpringBoot版本选择很多同学直接选最新的SpringBoot 3.x觉得新版本就是好。但实际踩过坑的人才知道SpringBoot 3.x基于JDK 17很多老教程里的配置方式会产生兼容性问题。毕设项目建议稳妥为主我用的是SpringBoot 2.7.x搭配JDK 1.8这套组合被全网无数教程验证过出问题也好排查。工程目录按职责分层避免把所有类都堆在一个包里。我习惯这样规划com.example.pet ├── config // 配置类CORS、拦截器、静态资源映射 ├── controller // 接口层 ├── service // 业务逻辑层 ├── mapper // MyBatis Plus数据访问层 ├── entity // 实体类 ├── dto // 请求/响应对象 ├── vo // 视图对象 ├── common // 通用工具类、统一返回结果、异常处理 └── PetApplication.java很多同学会忽略common包里的统一返回结果和全局异常处理只把Controller里直接返回Map或者返回实体。这在系统小的时候看不出问题一旦前端联调就会很痛苦。我建议从第一天起就定义统一返回结构比如{ code, message, data }这样的格式前端axios统一判断code是否为200这是非常成熟的做法。2.2 基于JWT的认证与权限控制宠物服务系统里有三种角色接口不能裸奔。最简单的方案是用JWT配合Spring拦截器不引入Spring Security那套复杂的东西因为你只需要判断登录状态和角色权限两层。用户登录成功后后端生成一个JWT字符串返回给前端前端存在localStorage里每次请求在请求头带上Authorization: Bearer token。后端写一个拦截器拦截所有需要对登录态的接口路径校验token是否有效再把用户ID和角色信息写入请求上下文。这里的核心代码如下public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行OPTIONS预检请求 if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.isBlank(token) || !token.startsWith(Bearer )) { throw new BusinessException(401, 未登录或登录已过期); } Claims claims JwtUtil.parseToken(token.substring(7)); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } }角色权限校验可以通过自定义注解配合拦截器实现比如RequireRole(ADMIN)也可以在服务层手动判断。毕设项目不用做太复杂手动判断也完全够。但有一点必须重视拦截器只拦截后端接口不拦截静态资源否则前后端分离打包后会出现页面资源无法加载的问题这个我后面在部署部分会细说。2.3 宠物领养与护理预约接口实现领养和护理预约是整个系统最核心的业务接口。先说领养申请接口的设计。用户提交领养申请时提交petId和申请备注后端要做三步校验宠物是否存在且状态为待领养、用户是否已经对该宠物提交过申请防止重复申请、申请备注是否包含有效联系方式。校验通过后生成领养申请记录同时将宠物状态改为“申请中”防止其他人同时申请同一只宠物造成冲突。这个过程中有一个细节容易被忽视就是事务的使用。领养申请涉及同时插入申请记录和更新宠物状态两步操作如果第二步失败第一步的数据就会成为脏数据。解决方式很简单在Service层方法上加Transactional注解。同样的逻辑也适用于护理预约接口预约时要校验预约时间是否与已有预约冲突这一步需要查询预约表。这里用一条简单的SQL就能实现查询同一宠物、同一服务项目、时间范围重叠且状态不是“已取消”的记录是否存在。护理预约的接口设计我建议把时间设计成时间段比如上午、下午、晚上三个时段而不是精确到几点几分。原因很简单用户选择具体时间点时冲突校验复杂度会成倍增加而且在真实业务场景里宠物护理本来就是按半天来排的。2.4 MyBatis Plus如何减少重复工作如果用手写MyBatis XML的方式这个系统的CRUD代码量会非常大而且这些代码没有任何技术含量纯粹是重复劳动。MyBatis Plus的价值就在于把单表CRUD全部封装好了Mapper接口只需要继承BaseMapperTpublic interface PetInfoMapper extends BaseMapperPetInfo { }这就是全部代码复杂的SQL依然可以在XML里手写或者用注解方式实现。MyBatis Plus还内置了分页插件配置好后只需要调用Page对象就能实现分页查询不用再手写LIMIT语句和总条数统计这对毕业生来说是最省心的地方。在写复杂查询时比如宠物列表要关联显示最近一条领养申请状态你还是需要用自定义SQL或Query Wrapper来组装条件所以不要觉得MyBatis Plus能解决一切。MyBatis Plus还有一个实用功能是逻辑删除。比如用户领养申请被管理员删除后物理删除会让历史数据无法追溯这对答辩展示很不利。改用TableLogic逻辑删除后删除操作只是在记录上打个标记查询时会自动过滤。多花一分钟配置后面少很多麻烦。3. Vue.js前端实现与联调细节3.1 前端工程与路由规划前端用Vue CLI创建项目最省心。进入项目目录后执行vue create pet-frontend选择Vue Router和Vuex其他默认即可。如果你是用Vue 3组件用script setup语法会让代码更加简洁。路由规划直接映射系统功能模块我的习惯是每个一级菜单对应一个父路由子页面用嵌套路由实现前端路由规划 ├── / // 首页宠物展示列表 ├── /pet/:id // 宠物详情页 ├── /adopt // 领养申请相关 │ ├── /adopt/list // 我的领养申请 │ └── /adopt/apply // 提交领养申请 ├── /care // 护理服务相关 │ ├── /care/list // 服务项目列表 │ └── /care/order // 护理预约 ├── /user // 个人中心 └── /admin // 后台管理管理员角色可见 ├── /admin/pet // 宠物管理 ├── /admin/adopt // 领养审核 ├── /admin/care // 预约调度 └── /admin/review // 回访管理路由守卫是必做的我记得有个朋友在答辩演示时退出登录后直接输入后台地址居然还能进入后台管理页面当场被问得很难堪。解决这个问题很简单在beforeEach路由守卫中检查用户token和角色信息没有登录就跳转到登录页登录了但访问了没有权限的路由就跳转到403页面。这一个细节就能让整个系统的用户体验和完成度提升一个档次。3.2 axios封装与请求拦截前端直接在每个组件里写this.$http.get(...)会让代码完全失控。正确的做法是统一封装axios实例把baseURL、超时时间、请求头、错误处理全部集中管理import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动附加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) { if (res.code 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(new Error(res.message)) } return res.data }, error { return Promise.reject(error) } ) export default request这样一个API文件里每个方法只需要一行代码export const getPetList (params) request.get(/pet/list, { params }) export const applyAdopt (data) request.post(/adopt/apply, data)重点说一下baseURL。开发环境可以用/api配合Vue CLI的代理配置把请求转发到后端8080端口。生产环境打包时如果前端代码被放在SpringBoot的static目录里/api这个路径就会直接打到同一台服务上的接口接口路径这样就完美绕开了跨域问题。这条经验是我自己试出来的很多同学卡在跨域上几天搞不定用这个方案直接从源头避开。3.3 核心页面与组件拆解页面设计上不建议去写一堆花里胡哨的自定义样式用Element UI的组件库几乎可以覆盖所有需求。宠物列表页是一张卡片式展示墙每张卡片显示宠物图片、名字、品种、年龄和领养状态底部放“查看详情”按钮。这个页面用el-card加el-image就能搭出来。宠物详情页重点展示宠物描述、健康信息和领养按钮。领养申请表单需要校验用户输入的手机号、居住信息、养宠物经验。这里建议用el-form的rules做前端校验同时后端接口再验一次两层防护。护理预约页面要展示服务项目列表用户选择了服务类型后再选择时段再提交预约。后台管理页面则是典型的表格加表单弹窗模式宠物管理用el-table展示新增编辑用el-dialog弹窗审核操作通过表格中的按钮触发。组件拆分的思路是复用部分尽量提取成单独组件。比如宠物卡片组件在首页和具体分类列表页都会用到已经领养状态、护理中状态都要有不同标识把这段逻辑抽成PetCard.vue组件传一个pet对象进去就可以避免了多处维护同一套DOM结构。3.4 前后端联调与打包部署联调阶段最常见的问题就是跨域。开发环境有几种解法我推荐在Vue项目的vue.config.js里配置devServer代理module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }这种做法的好处是前端代码中请求的是相对路径浏览器认为是同源请求根本不会触发CORS拦截。在process.env.NODE_ENV development时期生效生产部署时只要后端把前端构建产物放在静态资源目录依然走这个逻辑。打包阶段最值得注意的问题是Vue Router的history模式会导致页面刷新时404。因为history模式依赖服务端配置重定向而SpringBoot默认不会把未知路径转发到index.html。两种解法方案一把Vue Router改成hash模式URL中会带一个#号刷新没问题且配置零成本方案二后端加一个转发规则把非接口路径转发到index.html。我建议毕设用方案一简单直接不折腾。打包后把dist目录里的文件复制到SpringBoot项目的src/main/resources/static目录下然后重新打包运行访问http://localhost:8080就能看到整个系统了。这一套操作看起来简单但一定要自己亲手跑一遍很多同学到这一步才发现路径引用的坑比如图片路径、静态资源路径配错导致加载失败。4. 关键技术细节与性能优化4.1 宠物图片上传与静态资源映射宠物系统最核心的展示内容就是图片所以图片上传和访问是必做模块。前端用el-upload组件选择文件通过axios以multipart/form-data格式发送到后端。后端接收逻辑不复杂但有一个必须注意的坑文件不能直接进数据库而是保存到服务器磁盘路径数据库只存储文件的访问URL。PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(请选择文件); } String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString().replace(-, ) suffix; String filePath uploadDir fileName; file.transferTo(new File(filePath)); return Result.success(/images/ fileName); }这里的文件名一定要生成新的唯一名称千万不能用原始文件名。如果用户上传了两张同名的a.jpg第二张就会覆盖第一张。同时要做好类型校验只允许jpg、png、gif等图片格式否则用户传个exe上来不只是体验问题还是安全漏洞。图片访问需要配置静态资源映射让URL路径映射到磁盘目录。在SpringBoot里只需要在配置类中重写addResourceHandlersOverride public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceHandler(file: uploadDir); }Windows下的路径和Linux下的路径不同如果你部署到服务器上这个路径一定要写成绝对路径并且确认目录存在且用户拥有可写权限。这个问题的坑我踩过Linux下启动正常但一上传图片就报FileNotFoundException。4.2 定时任务实现疫苗与回访提醒宠物领养后的回访提醒、疫苗接种到期提醒这类功能最适合用SpringBoot的定时任务实现。在启动类加EnableScheduling然后新建一个定时任务类Component public class RemindTask { Scheduled(cron 0 0 9 * * ?) public void remindReview() { // 查询所有待回访的领养记录回访时间距今超过7天 ListAdoptRecord list adoptRecordMapper.selectList( new QueryWrapperAdoptRecord() .eq(status, 2) .lt(last_review_time, DateUtil.offsetDay(new Date(), -7)) ); for (AdoptRecord record : list) { // 发送提醒短信或站内信 } } }定时任务的cron表达式很多人记不住我提供一个简单的速记从左到右是秒、分、时、日、月、周0 0 9 * * ?就是每天上午9点执行一次。调试定时任务时不要干等直接在测试类里调用一次任务方法确认逻辑没问题再挂到生产环境。4.3 接口安全与签名校验后端接口如果裸奔任何人都可以直接调用接口修改数据这在毕设答辩时一旦被问到“安全性如何保证”就会很尴尬。除了JWT权限控制外还可以加一个简单的操作日志切面记录每个用户的增删改操作这在论文中可以写成“系统操作日志模块”。接口签名校验也是一个可以亮出来的加分点。简单做法是在前端调用写操作接口时把token 时间戳 请求体做一次MD5生成签名放在请求头后端校验签名是否匹配同时校验时间戳是否在有效期内。这样即使有人拿到了请求数据包也无法篡改内容。这个方案不用引入复杂的加密SDK纯代码即可实现在答辩时讲清楚思路就已经能体现出设计深度了。4.4 分页查询与索引优化宠物列表接口的数据量一开始可能不大但作为毕设需要体现对性能的思考。用MyBatis Plus分页插件是标准做法。分页参数建议前端传page和size后端返回total和records。这里有一个容易踩的坑MyBatis Plus的分页插件需要显式配置PaginationInnerInterceptor不配置的话分页查询会在内存里执行数据量大时直接内存溢出。我曾经见过一个同学的项目就是漏了这段配置列表页一查几万条数据就卡死。配置方法如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }索引方面宠物姓名模糊查询和状态筛选是高频查询条件应该给pet_name和status字段建立索引。但模糊查询的关键词如果写在中间LIKE %xx%MySQL的索引会失效这是数据库原理中讲烂了的内容在论文的数据库设计章节写上一句“针对查询条件建立非聚簇索引避免使用前置通配符模糊查询”是有分量的专业表述。5. 常见问题与排查实录5.1 跨域、端口与路径三大高频问题跨域问题在开发阶段几乎人人都会遇到。表现是浏览器控制台报错CORS policy或者Access-Control-Allow-Origin。我在实践中发现最稳妥的解决方案不是在后端配置CrossOrigin而是前端用代理转发让浏览器认为是同源请求。如果非要在后端配置CORS要注意的是前端请求如果带了自定义请求头后端必须通过allowedHeaders(*)允许全部请求头否则请求会一直挂在OPTIONS预检阶段。端口被占用的问题几乎每天都在发生。SpringBoot默认8080端口被其他程序占用启动直接报Port 8080 was already in use。解决方案有两种一种是在application.yml里改端口另一种是直接杀掉占用进程。Windows下可以执行netstat -ano | findstr 8080找到进程PID然后用taskkill /F /PID 进程号杀掉它。这个问题虽然简单但在答辩演示现场如果发生非常影响心情。路径问题则体现在前端请求的URL写错了层级。SpringBoot的Controller映射路径如果定义为/api/pet/list而mapper里用的是/pet/list后端返回404。排查404要看两点一是Controller的RequestMapping前缀是否正确二是是否有拦截器把接口路径拦掉了。我在拦截器配置里专门放过/api/auth/login等不需要登录的接口这一行要写清楚。5.2 数据库与框架版本踩坑合集MySQL 8和MySQL 5.7在连接驱动上有区别MySQL 8的驱动类是com.mysql.cj.jdbc.Driver不是老版的com.mysql.jdbc.Driver。同时连接的URL上必须要加时区参数否则会报Server returns invalid timezone错误。推荐配置是jdbc:mysql://localhost:3306/pet_db?characterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse。时区参数这块是个高频面试点答辩时提一下“通过指定时区解决服务器与数据库时间不一致问题”比单纯说配置连接字符串要专业得多。SpringBoot版本选择我前面已经强调过SpringBoot 3.x JDK 17对于刚上手的人确实有门槛。MyBatis Plus也需要跟SpringBoot版本匹配我用的是MyBatis Plus 3.5.x适配SpringBoot 2.x完全没问题。如果你选了SpringBoot 3要注意MyBatis Plus也需要升级到适配新版本的3.5.3以上。5.3 前端打包部署常见疑难前端打包后发现页面空白这是我在实际运行中遇到最多的问题。分两种情况一种是把Vue Router配置成了history模式刷新非首页时报404另一种是打包后引用的资源路径是绝对路径/js/xxx.js而部署环境是子目录导致资源加载失败。解决办法Vue Router用hash模式vue.config.js中publicPath设置为./这样打包出来的HTML引用资源时会自动使用相对路径。还有一个比较隐蔽的问题是打包后接口请求404原因是把前端dist目录放到static下时后端接口路径要跟前端baseURL匹配。如果前端请求的是/api/pet/list而后端Controller只有/pet/list那么打包部署后基本都会404。解决办法是后端接口统一增加/api前缀或者Controller的RequestMapping(/api)前缀路径上加进去。我在前端联调时用代理把/api转发掉了但部署到SpringBoot后没有代理了这个/api前缀就变成了真实存在的路径所以前后台接口定义要统一考虑。6. 毕设推进节奏与文档沉淀6.1 时间线与里程碑拆解很多同学毕设拖到最后一个月才开始动手这个节奏必然踩坑。我建议的排期是8周前2周做需求分析和数据库设计这里的关键里程碑是画出一张完整的ER图和生产一份数据库建表SQL表结构一旦改了后面的代码全要跟着改所以这个阶段宁可慢一点。第3到4周完成后端主体功能按照用户模块、宠物模块、领养模块、护理模块的顺序逐个写没做完一个模块就立即用Postman测一遍不要攒到最后一口气测。第5到6周做Vue前端页面和组件从登录注册和首页开始搭先跑通路由骨架再填充业务页面避免憋一整天代码后还不知道效果。第7周集中联调处理跨域、字段不一致、状态不同步等问题这个阶段是真正痛苦的阶段。最后1到2周留出缓冲写论文做PPT。6.2 论文与演示准备要点论文不用等代码写完再动笔可以从设计阶段就开始写需求分析章节。论文中需要包含系统架构图、功能结构图、流程图、ER图、数据库表结构说明、核心代码片段和截图。架构图可以用ProcessOn画流程图和ER图也可以用绘图工具画关键是图要清晰不要用代码里注释截图冒充。核心代码片段要选有代表性的比如JWT拦截器、领养状态流转逻辑注明“该模块实现了XXX机制”这种写法比贴一大段CRUD代码有内容得多。答辩演示时准备两个场景一是普通用户的完整使用链路从注册登录到浏览宠物再到提交领养申请二是管理员的审核链路从登录后台到审核用户申请再到录入回访记录。全程演示5分钟左右足够每步操作要流畅。建议提前准备好测试账号和数据避免现场现注册、现录入如果演示的Wi-Fi掉线本地环境就没有这个风险。文档这块还有一个容易被忽视的细节数据库表结构设计说明书。把每张表的字段名、类型、注释、约束列出来这是一张表格就能承载的内容却能让评阅老师一眼看出系统的完整度。我在做这类项目时还会额外写一份接口文档用Swagger或者直接在控制器上写注释都行毕竟“前后端分离的项目接口文档本身就是交付物的一部分”这个理念在答辩里会加很多印象分。最后再分享一个小技巧在做这类毕业设计时不要等到全部功能做完才开始写代码注释和文档。每完成一个模块就立即把接口文档、表结构说明、实现思路记录下来这样最后写论文时几乎就是整理笔记而不是重新回忆三个月的开发过程。我在做第一个项目时就是开发完才开始写文档结果连自己设计的表字段都要反查代码非常痛苦。项目做完文档也跟着完成答辩前一周会过得非常轻松。