
做毕业设计的时候选题目我一直坚持一个原则别贪大但要完整。所谓完整就是前端、后端、数据库、部署一条链路全跑通。最后我选的是基于SpringBoot和Vue的宠物之家平台系统一个典型的前后端分离项目。SpringBoot负责把后端繁琐的配置收拢起来Vue负责把页面交互做得干净利落两个搭档做出来的东西演示起来有模有样答辩也不愁没东西讲。这篇就把整个实现过程从头到尾拆开讲一遍从技术选型、数据库设计再到接口实现、前端联调、打包部署该给的配置和代码都给你照着做基本能复现整个项目。1. 项目从0到1整体设计与技术选型1.1 为什么选SpringBoot和Vue这对组合宠物之家平台说白了就是一个围绕宠物生命周期的管理服务系统。核心场景包括宠物信息展示、领养申请、寄养服务预约、寻宠启示发布以及后台对这些内容的管理审核。这类系统有两个明显特征一是数据模型相对固定宠物、用户、申请、订单这些实体关系清晰二是页面交互有一定复杂度用户端要刷起来顺畅管理端要操作起来直接。这两个特征刚好落在SpringBoot和Vue最擅长的地方。先说SpringBoot。它解决了Java后端一个老大难问题——配置地狱。以前用SSH或者SSM光是配置Spring、Struts、MyBatis的XML文件就能写到手酸项目还没跑起来先被配置折腾一轮。SpringBoot通过自动装配机制把这些常规配置全收了你只要在pom里引starter依赖SpringBoot会根据classpath下的jar包自动配置好对应的组件。你可以把自动装配理解为你请了一位管家什么菜配什么锅他门儿清你只管把菜买回来。底层靠的是EnableAutoConfiguration注解和spring.factories里的配置类这个点面试也爱问后面细说。再说Vue。它是目前国内前后端分离开发中使用率最高的前端框架之一学习曲线平缓语法直观官方中文文档也齐全。做毕设项目Vue最舒服的一点是组件化开发宠物卡片、筛选栏、分页条这些高频UI元素封装一次到处复用代码量直线下降。而且Vue生态里有Element UI这样的现成组件库后台管理界面的表格、表单、弹窗拖过来就能用对不擅长CSS的同学非常友好。这两个组合到一起就形成了前后端分离的开发模式前端只负责渲染页面和发请求后端只提供JSON接口。好处是各管一摊出问题不用来回找原因而且部署的时候也更灵活前端打包成静态文件扔给nginx后端打包成jar独立运行。1.2 系统功能模块与业务流程宠物之家平台这个题目功能设计得既有发挥空间又能控制在一个毕设周期内完成。我的方案分用户端和管理端两块。用户端功能用户注册与登录手机号或邮箱注册登录后获取JWT令牌。宠物展示按分类狗、猫、兔子、鸟类等浏览待领养宠物支持关键词搜索和分页。领养申请选定宠物填写领养申请家庭情况、养宠经验、居住条件提交后等待审核。寄养预约选择寄养服务套餐提交宠物信息和寄养时间段生成服务订单。寻宠大厅发布走失宠物信息浏览其他人的寻宠启示提供联系线索。管理端功能仪表盘展示宠物总数、待审核申请数、新增用户数等统计信息。宠物管理新增、上下架宠物信息维护宠物图片、状态、疫苗记录。领养审核查看领养申请详情通过或驳回通过后自动更新宠物状态为已领养。服务订单管理确认、取消寄养订单跟进服务状态。公告管理发布平台公告用户在首页能看到的通知。业务流程上有几个关键节点要注意。领养申请这个流程是串起来的用户提交申请 - 管理员审核 - 审核通过 - 宠物状态改已领养 - 申请状态改已完成。这五个动作里任意一步出错数据就对不上。所以设计接口时这个流程必须在后端代码里做好状态流转的逻辑判断不能靠前端提示来约束。1.3 前后端分离架构下的目录规划项目结构我这里直接给出一个答辩时也很好展示的规划方式把所有代码放在一个总目录下分三块pet-home/ ├── backend/ # SpringBoot后端工程 ├── frontend/ # Vue前端工程 └── doc/ # 数据库脚本、接口文档、答辩PPT素材backend里按Maven标准结构分controller / service / mapper / entity / dto / config / common / utilcontroller只做参数接收和结果包装业务逻辑全在service层。frontend里按views / components / router / store / api / utils分配页面组件放views可复用组件放components所有接口请求统一放api模块管理。这样划分的好处是职责清晰团队协作时大家各改各的目录不会冲突毕业设计虽然通常一个人做但按照工程化标准组织代码量大了以后自己维护也省心。doc目录放数据库初始化SQL脚本和接口文档。接口文档我建议直接用Apifox或者Postman导出每个接口的参数说明、返回示例都留着答辩时演示接口调试比现场敲代码要稳得多。2. 后端核心实现从建表到接口落地2.1 数据库设计五张核心表数据库是整个系统的基础表结构设计得好不好直接决定后面写接口是顺手还是折腾。我先说设计原则每一张表都要有id主键、create_time和update_time两个时间字段这在后台管理项目里是基本操作排序、追踪问题都靠它们。宠物之家平台我设计了五张核心业务表外加用户和角色两张辅助表表名用途关键字段user用户表username、password、phone、avatar、statuspet宠物信息表name、category、age、gender、vaccine_status、description、status(0待领养/1已领养/2下架)adopt_application领养申请表user_id、pet_id、experience、address、status(0审核中/1通过/2驳回)service_order服务订单表user_id、pet_id、service_type、start_date、end_date、amount、statuslost_found寻宠信息表user_id、pet_name、lost_location、lost_time、contact、describenotice公告表title、content、statusrole角色表用户端/管理端角色前端渲染判断用设计时有两个细节值得注意。第一密码字段不要用明文存用BCrypt加密Spring Security自带这个工具类注册时encoder(password)登录时matches(rawPassword, encodedPassword)比对。第二所有状态字段都用tinyint数字表示0、1、2各自代表什么状态要用注释写清楚。前端根据数字去映射文字而不是直接把中文存进去这样后期改文案不用动数据库。外键关系上adopt_application和service_order都通过user_id和pet_id关联用户与宠物但在实际开发中我建议逻辑关联就好不建物理外键约束。原因很简单毕设阶段经常要手动改数据物理外键会限制你去删那些测试用的脏数据逻辑上通过业务代码保证一致性就够了。这也是现在企业里比较常见的做法。2.2 SpringBoot项目初始化与版本坑位提醒用IDEA创建SpringBoot项目很简单File - New - Project - Spring Initializr填好Group和Artifact选Java版本然后选依赖。我第一次建的时候顺手选了个最新版SpringBoot 3.2.0结果JDK17没装项目一启动就报错。后来老老实实换回SpringBoot 2.7.18配JDK8特别稳。这里要专门提醒一个版本坑SpringBoot 3.x和2.x在底层包名上有巨大差异3.x把javax命名空间换成了jakarta。如果你照着2.x时代的教程敲代码import javax.servlet.Filter、import javax.validation.Valid这些在3.x里全部编译不过必须改成jakarta.servlet.Filter、jakarta.validation.Valid。很多同学做毕设翻车就是翻在这个地方——按照网上的老教程写结果项目用的是新版报错报得莫名其妙。所以建议毕设直接用SpringBoot 2.7.x配JDK8教程多、资料全、不会卡壳。pom.xml里我引入的核心依赖就这几个dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.auth0/groupId artifactIdjava-jwt/artifactId version4.4.0/version /dependency我这里用了MyBatis-Plus做数据层框架省去写大量单表CRUD的XML。MyBatis-Plus的BaseMapper自带selectById、selectPage、insert这些方法85%的单表操作不用自己写SQL剩下的复杂查询再手写注解SQL或XML。application.yml的核心配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_home?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deletedMyBatis-Plus里面的分页插件配置要单独写一个配置类不然selectPage不生效。后面需要写一个MybatisPlusConfig类加上分页拦截器。关于SpringBoot自动装配原理这里稍微展开一下因为面试和答辩都容易问。SpringBoot启动类上的SpringBootApplication是一个组合注解核心是EnableAutoConfiguration。这个注解通过Import(AutoConfigurationImportSelector.class)导入自动配置类AutoConfigurationImportSelector会读取classpath下META-INF/spring.factories文件里所有EnableAutoConfiguration的配置项拿到一大堆候选配置类然后通过ConditionalOnClass、ConditionalOnMissingBean等条件注解按需加载。比如你引入了spring-boot-starter-webclasspath下有了Servlet类DispatcherServletAutoConfiguration就生效自动帮你配置好SpringMVC相关的Bean。正因为有了这套机制开发者才能做到引入依赖就能用不用写一行xml配置。2.3 用户认证JWT方案宠物之家平台需要区分管理员和普通用户就需要一个认证方案。我选用JWTJSON Web Token而不是传统的Session核心原因是前后端分离项目里Session天然不好用跨域环境下Cookie携带、保持会话状态都比较麻烦而JWT是一种无状态令牌服务端不保存会话前端每次请求在Header里带上token就行。JWT的结构是Header.Payload.Signature三段Payload里我存放userId和role签名用密钥保证token内容不被篡改。登录成功后后端生成token返回给前端前端存到localStorage每次请求在Axios拦截器里加上Authorization: Bearer {token}。生成和校验token的工具类我用的是java-jwtpublic class JwtUtil { private static final String SECRET pet-home-secret-key; private static final long EXPIRE_TIME 72 * 60 * 60 * 1000L; // 3天有效期 public static String createToken(Long userId, String role) { Date expireDate new Date(System.currentTimeMillis() EXPIRE_TIME); return JWT.create() .withClaim(userId, userId) .withClaim(role, role) .withExpiresAt(expireDate) .sign(Algorithm.HMAC256(SECRET)); } public static Long getUserId(String token) { DecodedJWT jwt JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token); return jwt.getClaim(userId).asLong(); } }校验token这步放在拦截器里统一处理。写一个JwtInterceptor实现HandlerInterceptor在preHandle里读取Header的token解析成功就放行解析失败返回401。然后注册到WebMvc配置里并设置放行路径——/api/user/login、/api/user/register、/api/pet/list这些公开接口不拦截管理员才能访问的接口用一个自定义注解RequireAdmin配合拦截器判断角色。这样权限控制的代码集中在一起而不是散落在每个Controller里。2.4 核心业务接口领养申请流程的实现领养申请是宠物之家平台里业务逻辑最完整的一个场景从申请提交到宠物状态更新是一条完整链路。我这里把核心代码流程拆解一下。先创建领养申请PostMapping(/adopt/apply) public Result apply(RequestBody ApplyDTO applyDTO, RequestHeader(token) String token) { Long userId JwtUtil.getUserId(token); Pet pet petService.getById(applyDTO.getPetId()); if (pet null || pet.getStatus() ! 0) { return Result.error(该宠物暂不可领养); } AdoptApplication app new AdoptApplication(); app.setUserId(userId); app.setPetId(applyDTO.getPetId()); app.setExperience(applyDTO.getExperience()); app.setAddress(applyDTO.getAddress()); app.setStatus(0); // 审核中 adoptService.save(app); return Result.success(申请提交成功); }这里有个容易忽略的点用户提交申请前必须校验宠物当前状态。如果宠物已经被其他人申请或者已经领养走了就不能再让人提交不然会出现一个宠物多条有效申请的脏数据。这也是为什么我把status字段设计为0、1、2三个值每提交一次申请就要读取一次宠物状态做校验。管理员审核领养申请时涉及两个表的更新必须加事务控制Transactional(rollbackFor Exception.class) public void approveApplication(Long appId) { AdoptApplication app adoptService.getById(appId); if (app.getStatus() ! 0) { throw new RuntimeException(该申请已处理); } // 1. 更新申请状态为通过 app.setStatus(1); adoptService.updateById(app); // 2. 更新宠物状态为已领养 Pet pet petService.getById(app.getPetId()); pet.setStatus(1); petService.updateById(pet); // 3. 将该宠物其他审核中的申请自动驳回 LambdaUpdateWrapperAdoptApplication wrapper new LambdaUpdateWrapper(); wrapper.eq(AdoptApplication::getPetId, pet.getId()) .eq(AdoptApplication::getStatus, 0) .ne(AdoptApplication::getId, appId) .set(AdoptApplication::getStatus, 2); adoptService.update(wrapper); }第三部很多人会漏掉。同一个宠物可能有多个用户提交了申请一旦管理员通过了其中一个其他申请理论上就都失效了。自动把它们全部驳回避免用户端看到奇怪的审核中状态。这就是典型的业务闭环思考。Transactional一定要加rollbackFor Exception.class不加的话只对RuntimeException生效某些检查异常发生时会静默提交数据就会不一致。这个细节如果有人问你可以直接展示出来比空口说我用了事务要有说服力得多。3. 前端开发Vue环境与页面实现3.1 Vue环境配置与项目初始化前端部分的第一步是装环境。Node.js是Vue的开发运行环境不多说直接去官网下载LTS版本安装一路下一步。装完打开命令行验证node -v npm -v国内网络环境下npm默认源下载依赖很慢先把镜像源换成淘宝的npm config set registry https://registry.npmmirror.com/然后创建Vue项目。我这里用的是Vue 3 Vite创建命令npm create vitelatest frontend -- --template vue cd frontend npm install为什么用Vite而不是Vue CLI核心感受是快冷启动和热更新的速度完全是两个体验Vite启动一个项目基本秒开Vue CLI那套Webpack配置启动要等十几秒。不过要注意Vite和Vue CLI在项目创建方式、配置文件上都有差异网上教程如果混着看容易懵选定一条路线走到底。装完基础依赖再装Vue Router和Element Plusnpm install vue-router4 element-plus element-plus/icons-vue axios piniaElement Plus是Element UI的Vue3版本表格、表单、分页组件都有现成样式后台页面基本都是围绕这几个组件搭出来的。Vue3项目里状态管理我选Pinia它是官方推荐的Vuex替代品语法更简洁去掉了mutations直接改state就行毕设用起来很顺手。Vuex和Pinia的区别面试也问得多Vuex比较啰嗦Pinia轻量且完美适配Vue3的组合式API。3.2 路由与状态管理页面跳转和数据共享宠物之家平台的前端路由我用vue-router做两级结构。一级路由是布局组件用户端Layout和管理端Layout二级路由才是具体页面。const routes [ { path: /admin, component: AdminLayout, children: [ { path: dashboard, component: Dashboard, meta: { role: admin } }, { path: pet-manage, component: PetManage, meta: { role: admin } }, { path: adoption-audit, component: AdoptionAudit, meta: { role: admin } } ] }, { path: /, component: UserLayout, children: [ { path: , component: Home, name: home }, { path: pet/:id, component: PetDetail, name: petDetail }, { path: adopt/apply/:petId, component: AdoptApply, name: adoptApply } ] } ]路由传参这里有两个方案容易搞混。一个是/pet/:id这种路径参数方式this.$route.params.id拿参数参数直接体现在URL路径里。另一个是/pet?id123这种query方式this.$route.query.id拿参数。路径参数适合详情页这类必须有这个id才能显示内容的场景query适合筛选条件这类可选的参数。我做宠物详情页用的是路径参数优势是刷新页面参数还在地址栏分享链接给朋友直接打开就是对应宠物页面。路由守卫是权限控制的前端实现。通过router.beforeEach每次页面跳转前判断localStorage里的token和角色信息如果要去后台管理页面但角色不是admin直接重定向到首页router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.role admin role ! admin) { next(/login) } else if (!token to.path ! /login) { next(/login) } else { next() } })Pinia这块我的用法是保存当前登录用户的信息一个很简单的storeimport { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: , userInfo: {} }), actions: { setToken(token) { this.token token }, setUserInfo(info) { this.userInfo info } } })一个坑要提醒Pinia的数据存在内存里刷新页面就没了。所以刷新后要重新拉取用户信息或者把关键信息放localStorage。如果你用Pinia管理登录状态记得在App.vue的onMounted里加一个从localStorage恢复token并请求用户信息的逻辑不然用户一刷新就变成未登录体验很糟糕。3.3 核心页面与组件设计宠物列表页是用户端的门面重点在筛选和展示。左侧按分类筛选猫、狗、小宠顶部关键词搜索右侧卡片网格展示宠物。每个宠物卡片我封装成了一个组件PetCard.vue接收一个pet对象展示宠物照片、名字、年龄、疫苗状态点击跳转详情页。template el-card classpet-card clickgotoDetail el-image :srcpet.avatar fitcover / div classpet-name{{ pet.name }}/div div classpet-meta el-tag sizesmall :typepet.vaccineStatus 1 ? success : info {{ pet.vaccineStatus 1 ? 已打疫苗 : 未疫苗 }} /el-tag /div /el-card /template script setup import { useRouter } from vue-router const props defineProps({ pet: Object }) const router useRouter() const gotoDetail () { router.push(/pet/${props.pet.id}) } /script宠物详情页展示完整的宠物信息和领养条件底部一个大大的申请领养按钮。这里要和后端配合如果宠物状态不是待领养按钮就要置灰并提示已被领养。用户点击申请后跳转到申请表单页表单里要填写养宠经验、家庭住址、居住环境。表单提交前做一层校验比如手机号11位、住址不能为空不合格直接拦截减少后端无效请求。后台管理端的布局我用Element Plus的Container布局左侧菜单栏、右侧内容区。菜单项就是仪表盘、宠物管理、领养审核、订单管理、公告管理这几项。表格组件统一用el-table分页用el-pagination表单弹窗用el-dialog。这套组合拳写后台特别快不用怎么操心样式。做一个表格列渲染状态值的小技巧后端返回的是数字状态码0、1、2前端展示中文不要直接{{ row.status }}写一个映射方法const statusMap { 0: 审核中, 1: 已通过, 2: 已驳回 } const formatStatus (val) statusMap[val] || 未知3.4 前后端联调Axios封装与跨域处理前端页面写好了要和后端交互就绕不开Axios。直接在每个页面里axios.get不是不行但一旦要统一加token、统一处理错误弹窗改起来就很痛苦。我的做法是封装一个request工具模块import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 15000 }) 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) { localStorage.removeItem(token) window.location.href /login } else { ElMessage.error(error.message) } return Promise.reject(error) } )然后所有接口请求都集中到一个api模块里比如api/pet.jsimport request from ../utils/request export const getPetList (params) request.get(/pet/list, { params }) export const getPetDetail (id) request.get(/pet/${id}) export const submitAdoptApply (data) request.post(/adopt/apply, data)这样改起来方便页面里调用也简洁。跨域问题在开发环境比较常见。前端跑在localhost:5173后端跑在localhost:8080浏览器会因为同源策略拦截请求。解决方案有两个。开发环境用Vite的代理配置在vite.config.js里加export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, // 不需要重写路径后端Controller统一带/api前缀 } } } })这样前端请求/api/pet/list开发服务器会代理转发到http://localhost:8080/api/pet/list浏览器感知不到跨域也就不会拦截了。生产环境则是nginx做反向代理同样思路后面部署章节讲。4. 打包部署与常见问题排查实录4.1 后端打包SpringBoot版本带来的坑后端开发完要打成可执行jar包跑起来。在IDEA的Maven面板里执行clean package或者命令行mvn clean package -DskipTests。打包过程中一个很经典的问题本地开发好好的打包出来的jar一运行就报错Failed to execute goal org.springframework.boot:spring-boot-maven-plugin。这个大概率是Maven版本和SpringBoot插件不兼容或者项目里重复引入了打包插件检查pom里只用了一个spring-boot-maven-plugin且Maven版本在3.6以上基本能解决。另一个SpringBoot版本坑我在初始化章节提过3.x和2.x的javax/jakarta命名空间差异。这里再展开说一个实际场景——如果项目用了SpringBoot 3.x而网上找的工具类代码里是import javax.servlet.*编译直接报程序包javax.servlet不存在。别慌这不是你代码写错了是版本演进带来的区别把javax全替换成jakarta就能过。SpringBoot 3.x还要求JDK17JDK8环境下根本启动不了。所以我反复强调做毕设除非你非常清楚这些差异否则就选SpringBoot 2.7.x配JDK8节省下来的时间拿去做功能不香吗。打出来的jar包用java -jar backend.jar启动默认占用8080端口。端口被占用也是个高频问题报错信息类似Port 8080 was already in use。排查命令Windows是netstat -ano | findstr 8080Linux是lsof -i:8080找到占用的进程号结束掉再启动就行。不想处理就直接改application.yml里server.port换个端口。4.2 Vue打包后布局异常排查前端开发模式下一切正常npm run build之后打开dist目录下的index.html要么白屏要么样式全乱掉是Vue项目打包最常遇到的问题。这里有几个排查方向按出现频率排序。第一资源路径问题。Vue项目默认配置打包后资源引用的是绝对路径/assets/xxx如果项目部署在服务器根目录没问题但如果部署在子路径下比如http://ip:8080/dist/所有资源都会404页面就是白屏。解决办法在vite.config.js里设置base: ./让打包后的资源引用相对路径这样放到任意目录都能打开。export default defineConfig({ base: ./, plugins: [vue()], // ...其他配置 })第二路由模式问题。Vue Router默认是hash模式URL长这样http://localhost:5173/#/pet/1这种模式打包部署后直接打开文件是没有问题的。但如果用了history模式URL就是http://localhost:5173/pet/1打包部署到服务器上刷新页面或直接访问子路径会出现404因为服务器上根本没有/pet/1这个物理路径需要nginx配置try_files兜底。毕设项目建议直接用默认hash模式省去服务器配置的麻烦。第三CSS样式错乱。这个问题常见于Element Plus按需引入配置不当。如果全局引入Element Plus那就是在main.js里app.use(ElementPlus)样式应该全的。如果你手写了按需引入的插件配置但没配好样式解析就会出现样式丢失、布局全乱的状况。排查办法很简单打开浏览器F12看Elements面板检查某个组件的class是否生效样式请求是否返回200。如果某个组件的样式没加载就去检查对应的插件配置。我在实际部署时就遇到过这个问题npm run build后首页能打开但Element Plus组件的样式完全没有整个页面就像回到上世纪。查了半天发现是Vite的插件配置里少了ElementPlusResolver的样式导入。后来干脆放弃按需引入改成main.js全量引入Element Plus打包体积大一点但稳定性好太多毕设这个体量根本不需要纠结那几百KB的体积差异。4.3 Nginx部署与远程访问配置毕设演示通常有两种方式本机演示和服务器部署。本机演示最省事前端npm run dev跑开发服务器后端IDEA里运行main方法数据库本地MySQL浏览器访问localhost:5173就能看到页面。但这种方式有一个风险演示现场万一网络波动、端口被占用场面就尴尬了。所以我更建议提前部署到一台服务器上用域名或IP访问演示的时候打开即用稳定性强得多。服务器部署的时间线大致是服务器安装JDK和MySQL - 导入数据库SQL - 上传后端jar并启动 - 上传前端dist静态文件 - 配置nginx。nginx配置要点就是静态文件服务 API反向代理。server { listen 80; server_name your-domain.com; # 前端静态文件 root /opt/pet-home/dist; index index.html; # 解决history模式刷新404的问题如果用了history模式 location / { try_files $uri $uri/ /index.html; } # 后端API反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里注意一个细节如果后端接口是/api/pet/list前端baseURL是/api那nginx的location/api/会和后端接口路径对接上。如果后端Controller里的路径本身没有/api前缀可以写proxy_pass http://127.0.0.1:8080/;加一个斜杠这样请求/api/pet/list会被转发到http://127.0.0.1:8080/pet/list把/api前缀去掉。这个机制不搞清楚前端打包后很可能出现接口请求404的问题。服务器上的MySQL一定要记得给root用户设置密码并且配置好远程访问的授权。用Navicat或命令行导入SQL脚本mysql -u root -p pet_home pet_home.sql4.4 常见问题速查表我把整个开发过程中碰到的典型问题整理成一张速查表供不同阶段排查参考问题现象可能原因解决方案SpringBoot启动报错ApplicationContext failed to load数据库连接失败、MySQL没启动检查mysql服务状态核对application.yml用户名密码、url地址Maven依赖下载慢或失败未配置国内镜像源在settings.xml里配置阿里云镜像前端npm install卡住npm源是国外默认源npm config set registry https://registry.npmmirror.com/接口报401未授权token过期或未携带检查localStorage是否有token拦截器是否正确设置Authorization头接口报500服务器内部错误后端业务异常或SQL错误看后端控制台日志排查SQL语句是否写错、字段名是否匹配前端页面能打开但数据不显示跨域或接口路径不对看F12 Network请求确认API返回状态和响应体打包后白屏base路径不对或路由模式配置问题vite.config.js设置base:./或改用hash模式宠物图片不显示图片路径是相对路径刷新后丢失图片走后端接口或存OSS前端不要用相对路径拼静态资源时间字段显示为UTC格式Jackson时区未配置application.yml设置spring.jackson.time-zoneAsia/Shanghai修改了数据但页面不刷新接口缓存或前端未重新请求强制刷新浏览器检查请求是否真的发出去了排查问题的思路比问题本身更值得总结。我的习惯是从前端控制台看Network面板确认请求是否发出、返回什么状态码然后看后端日志追踪具体异常堆栈最后定位到SQL或业务逻辑。这条链路走下来90%的问题都能解决。不要一上来就怀疑框架或者环境大概率是自己代码或者配置的问题。最后再分享几个做这个项目留下来的感受。第一个是数据库设计一定不要急花半天时间把表结构想清楚后面写接口能省好几天。第二是前后端联调的时候接口文档一定要写哪怕用Excel列一下接口路径、入参、出参都行不然联调阶段你对着代码猜字段名效率极低。第三是打包部署环节要提前测试不要把服务器部署留到答辩前一天线上环境的问题排查起来真的很头疼。这个项目做完最大的收获不只是用了SpringBoot和Vue做了一套系统而是完整跑通了一个Web项目从设计到上线的全流程这个经验比任何一个单独的技术点都值钱。