拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SpringBoot2+MyBatis-Plus+Vue3流浪动物救助平台实战:从数据库设计到前后端部署

1. 项目整体设计与技术选型拆解1.1 这个系统到底解决了什么问题流浪动物救助平台不是一个复杂的电商系统也不涉及高并发和高可用它的核心价值在于把“发现流浪动物—救助登记—信息发布—领养申请—审核管理”这条业务链路数字化。传统场景里救助站或爱心人士发现流浪动物后只能靠朋友圈转发、线下登记信息分散且无法追溯想要领养的人也很难找到可信赖的渠道。这套 Java Web 项目要做的就是把零散信息收敛到一个 Web 平台上让动物信息、救助记录、领养申请、用户管理都在一套系统里跑起来。对做毕设或者练手的开发者来说它还有一个额外价值覆盖的技术面足够完整。后端有 SpringBoot2、MyBatis-Plus、MySQL8.0前端有 Vue3项目里还带了设计文档意味着你可以直接从数据库设计、接口设计、页面实现、部署运维这条完整流程走一遍学到的不仅是写几个 CRUD而是“一个真实业务系统是怎么从零搭起来的”。1.2 为什么选这套技术栈而不是其他组合很多人拿到这类项目后第一反应是“老掉牙了SpringBoot3 都出来了为什么还用 SpringBoot2”。我的看法是SpringBoot2 在 2023 年之前大量毕业设计和企业老项目里依然是绝对主力网上资料、面试题、踩坑贴都非常密集。SpringBoot2.7.x 和 SpringBoot3.x 最大的差别在于底层是 Java 8 还是 Java 17而很多学校的教学和生产环境仍然停留在 JDK8选 SpringBoot2 能保证你在本地随便找一台机器就能跑起来不用为了 JDK17 的模块化去折腾额外配置。前端用 Vue3 而不是 Vue2理由更直接。Vue3 的组合式 APIComposition API在逻辑复用上比 Vue2 的 Options API 舒服太多特别是管理后台这种表单密集、状态分散的场景。组合式 API 允许你按业务逻辑组织代码而不是按 data、methods、computed 这种固定分类去切分——实际写下来你会发现维护一个动物申请状态组件时相关的响应式数据和处理函数能放在一起改起来不用到处翻。MyBatis-Plus 就不用多说了它本质上是在 MyBatis 之上做了一层增强把单表 CRUD、分页、逻辑删除、自动填充这些高频操作封装成现成能力。你在项目里写的绝大多数 SQL 都是单表查询根本不需要自己写 XML mapper继承 BaseMapper 之后增删改查方法直接就有了。配合它的分页插件列表接口加两行配置就能用。这类系统不是互联网高并发场景MyBatis-Plus 的灵活性完全够用而且代码量能压缩一半以上。MySQL8.0 则是目前社区推荐的主流版本。相比 5.78.0 默认字符集是 utf8mb4对中文和 emoji 支持更友好还支持窗口函数、CTE公用表表达式这些现代 SQL 特性。配合 MySQL Workbench 或 Navicat 做可视化操作建表和调数据都很直观。这里提醒一句8.0 和 5.7 的 JDBC 驱动类名不一样8.0 要用 com.mysql.cj.jdbc.DriverURL 里还要带上 serverTimezone 参数否则本地跑起来大概率报时区错误后面我会详细说。2. 后端核心数据库设计与 SpringBoot2 MyBatis-Plus 实战2.1 表结构怎么设计才贴合业务流浪动物救助平台最核心的表是动物信息表、用户表、领养申请表围绕这三张表再扩展出动物图片、救助记录、公告等附属表。设计数据库时不能一上来就写 SQL先想清楚实体关系和状态流转。动物信息表建议包含这些核心字段动物编号、名称、种类猫/狗/其他、性别、年龄、毛色、健康状况描述、绝育状态、疫苗状态、所在救助站或位置、图片 URL、当前状态待领养/已预约/已领养/暂养中、创建时间、更新时间。这里的状态字段是整个业务的分水岭前后端交互、列表筛选、申请审核全部依赖它。用户表除了常规的用户名、密码、手机号建议再加上角色字段区分普通用户和管理员。注意密码不要明文存储至少用 BCrypt 加密Spring Security 或简单的加密工具类都能实现这类项目虽然不需要完整的权限框架但密码加盐是底线。领养申请表是最容易设计得过度的表。很多人一开始想记录“申请表单里所有字段”比如职业、住房情况、养宠经验结果越加越复杂。合理的做法是申请表只保留关键关联字段申请用户 ID、动物 ID、申请说明、联系信息、状态待审核/通过/拒绝/已完成领养、审核备注、申请时间、处理时间。扩展的问卷字段单独存成一个 JSON 或者干脆放到申请说明里这样表结构扁平查询也简单。CREATE TABLE animal ( id bigint PRIMARY KEY AUTO_INCREMENT, animal_no varchar(32) UNIQUE COMMENT 动物编号, name varchar(64) COMMENT 动物名称, category varchar(16) COMMENT 猫/狗/其他, gender tinyint COMMENT 0未知 1公 2母, age varchar(32) COMMENT 年龄描述, health_status varchar(255) COMMENT 健康状况, vaccine_status tinyint DEFAULT 0 COMMENT 是否已打疫苗, spay_status tinyint DEFAULT 0 COMMENT 是否已绝育, location varchar(255) COMMENT 所在位置/救助站, cover_image varchar(255) COMMENT 封面图片URL, status tinyint DEFAULT 0 COMMENT 0待领养 1已预约 2已领养 3暂养中, description text COMMENT 详细介绍, create_time datetime, update_time datetime ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT流浪动物信息表;如果你拿到的是带文档的完整源码数据库脚本一般会放在项目根目录的 sql 文件夹或 docs 目录下直接用 Navicat 或命令行执行即可。2.2 SpringBoot 项目脚手架与核心依赖创建一个 SpringBoot2 项目不需要从 start.spring.io 开始纠结直接在 IDEA 里用 Spring Initializr 生成选 Java 8、Spring Boot 2.7.x勾选 Spring Web、MySQL Driver、Lombok 就够了。注意不要勾 MyBatis因为我们用的是 MyBatis-Plus它有自己的 starter。pom.xml 里最关键的几个依赖如下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 groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency版本号一定要对得上尤其注意 MyBatis-Plus 的版本和 SpringBoot2 的兼容性。3.5.3 以上对 SpringBoot2 支持都很好但如果你用了 SpringBoot3就得换成 mybatis-plus-spring-boot3-starter这是很多新人第一次踩的坑。Maven 依赖下载慢的问题也提前说一句在 IDEA 设置里给 Maven 配置阿里云镜像把 settings.xml 里的远程仓库换成 aliyun 镜像否则下载依赖可能卡半天实测下来能提速十倍以上。2.3 MyBatis-Plus少写一半代码的套路使用 MyBatis-Plus 之后最直接的收益是实体类和 Mapper 层变得非常薄。实体类用注解映射表名和主键开启驼峰命名映射后数据库下划线字段和 Java 驼峰字段自动对应不需要手动写 mapping。Data TableName(animal) public class Animal { TableId(type IdType.AUTO) private Long id; private String animalNo; private String name; private String category; private Integer gender; private String age; private String healthStatus; private Integer vaccineStatus; private Integer spayStatus; private String location; private String coverImage; private Integer status; private String description; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }Mapper 接口直接继承 BaseMapperMapper public interface AnimalMapper extends BaseMapperAnimal { }这样 AnimalMapper 自动具备 selectById、selectList、insert、updateById、deleteById 这些方法。列表页常见的分页查询只需要在配置类里注册分页插件。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后 Service 层调用 Page 对象public IPageAnimal pageAnimals(int pageNum, int pageSize, String category, Integer status) { LambdaQueryWrapperAnimal wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(category), Animal::getCategory, category) .eq(status ! null, Animal::getStatus, status) .orderByDesc(Animal::getCreateTime); return animalMapper.selectPage(new Page(pageNum, pageSize), wrapper); }这段代码里的 LambdaQueryWrapper 是 MyBatis-Plus 的灵魂之一。它把 SQL 条件变成链式调用条件字段如果不满足前面那个布尔表达式就自动忽略这个筛选条件。写列表接口时再也不用手拼 SQL 判断 where 后面跟的是 and 还是 or直接往里怼条件就行。2.4 领养申请审核业务状态流转的实现这系统里最有业务味的逻辑是领养申请的状态流转。我建议在后端用一个 Service 专门管这件事避免在 Controller 里堆业务逻辑。核心设计是这样用户提交申请后状态是“待审核”管理员在后台看到申请列表点“通过”后这个动物要变成“已预约”防止别人重复申请等到实际完成领养管理员再确认一次动物状态更新为“已领养”申请状态更新为“已完成领养”。如果中途审核拒绝或者用户撤回申请状态变成“已拒绝/已取消”动物重新回到“待领养”。Transactional(rollbackFor Exception.class) public boolean approveAdoption(Long applicationId, Long adminId) { AdoptionApplication application applicationMapper.selectById(applicationId); if (application null || !待审核.equals(application.getStatus())) { throw new BusinessException(申请状态异常无法审核); } Animal animal animalMapper.selectById(application.getAnimalId()); if (animal null || animal.getStatus() ! 0) { throw new BusinessException(该动物当前不可领养); } // 申请人状态流转 application.setStatus(通过); application.setAuditTime(LocalDateTime.now()); applicationMapper.updateById(application); // 动物状态流转待领养 - 已预约 animal.setStatus(1); animal.setUpdatedTime(LocalDateTime.now()); animalMapper.updateById(animal); return true; }这里必须加 Transactional因为同时更新了申请和动物两张表中间任何一步失败都要回滚。很多新人忽略这个注解本地测试数据量小看不出问题一旦有并发提交就会出现“申请通过但动物状态没变”的脏数据。状态判断也很有讲究如果动物已经被人预约了后提交的申请就应该直接被拦下这既保护了动物侧的信息一致性也守着领养申请的公平性逻辑。3. 前端工程Vue3 Vite 从初始化到页面落地3.1 环境准备和工程创建Vue3 推荐用 Vite 构建工具而不是 Vue CLI理由是启动速度完全不同。Vite 基于原生 ES Module开发环境冷启动和热更新都很快改一个组件基本秒级刷新在调试管理后台这种状态多、改得频繁的页面时体验差距非常明显。创建项目就一行命令npm create vitelatest adoption-platform -- --template vue然后进入目录安装依赖和常用库。路由用 Vue Router 4状态管理用 PiniaHTTP 请求用 axiosUI 库用 Element Plus。这几个库里要特别说下 Pinia它相比 Vuex 4 在 TypeScript 类型推导和模块结构上更简洁Vue3 项目新写代码基本没人用 Vuex 了热词里你搜 Vue3 面试题大概率也会问到 Composition API 和 Pinia 的关系。安装npm install vue-router4 pinia axios element-plus element-plus/icons-vueElement Plus 的引入方式建议按需引入也就是配合 unplugin-auto-import 和 unplugin-vue-components 插件否则全量引入会让打包后的 JS 体积变很大。如果你只是练手不考虑体积全局引入也能跑。这里有个常见的坑按需引入后Element Plus 的 message、messagebox 这类函数式组件不会自动引入样式需要单独引入 element-plus/dist/index.css 或者用 ElMessage 的样式导入网上很多人报“vue3引入UI框架不生效”就是这个问题。3.2 页面架构与路由设计前端页面按角色可以拆成两块面向普通用户的 C 端页面包括首页、动物列表、动物详情、领养申请、个人中心面向管理员的后台页面包括仪表盘、动物管理、申请审核、用户管理、公告管理。路由设计上我建议把后台和 C 端分开用嵌套路由组织。const routes [ { path: /, component: HomeLayout, children: [ { path: , component: Index }, { path: animals, component: AnimalList }, { path: animals/:id, component: AnimalDetail }, { path: apply/:id, component: ApplyAdoption }, ]}, { path: /admin, component: AdminLayout, children: [ { path: , redirect: /admin/dashboard }, { path: dashboard, component: Dashboard }, { path: animals, component: AdminAnimalList }, { path: applications, component: AdminApplications }, ]}, ]后台管理界面常见的布局就是左侧菜单加右侧内容区Element Plus 的 el-container 组件可以直接搭出来。注意一点后台路由最好加一个全局前置守卫判断本地有没有管理员 token没有就跳转登录页不然管理接口暴露出去就是个安全隐患。3.3 组合式 API 写列表页一个能直接抄的示例拿动物列表页举例。用 Composition API 组织逻辑核心就是把“筛选条件分页数据加载函数”放在同一个 setup 作用域里逻辑紧凑、一目了然。script setup import { ref, reactive, onMounted } from vue import { ElMessage } from element-plus import { getAnimalPage } from /api/animal const loading ref(false) const pageData reactive({ current: 1, size: 10, total: 0 }) const filter reactive({ category: , status: null }) const animalList ref([]) async function fetchList() { loading.value true try { const res await getAnimalPage({ pageNum: pageData.current, pageSize: pageData.size, ...filter }) animalList.value res.records pageData.total res.total } finally { loading.value false } } function handleSearch() { pageData.current 1 fetchList() } function handleSizeChange(size) { pageData.size size fetchList() } onMounted(fetchList) /script这段代码你注意几个细节。ref 定义简单类型和数组reactive 定义需要深层响应式且结构固定的对象比如分页信息和筛选条件。调用后端接口时axios 拦截器会统一处理 token 和错误提示页面里只用关注数据回填和 loading 状态。列表请求回来后直接赋值给 ref 变量模板里使用的时候会自动解包。很多人在 Vue3 里不习惯的是ref 变量在 script 里访问必须用 .value在模板里则不需要。这个规则记住就不会出错——模板编译时 Vue 会自动帮你去掉 .value 这一层。如果你写的是 reactive 对象script 和模板里都不需要 .value但配合 ES6 解构会丢失响应式这是热词里“Vue3 ref万能对象”“组合API和Options API区别”背后的实战考点。3.4 表单校验和动态增减行后台管理常见需求后台新增动物信息的时候表单里会有种类、疫苗情况、绝育情况、图片上传这些字段。Element Plus 的表单校验规则用起来非常顺重点注意日期和自定义规则。比如要求领养人年龄必须大于 18 岁自定义 validator 写在 rules 里const rules { age: [ { required: true, message: 请输入年龄, trigger: blur }, { validator: (rule, value, callback) { if (value 18) { callback(new Error(领养人必须年满18周岁)) } else { callback() } }, trigger: blur } ] }关于动态增减表单项常见应用场景是管理员录入动物时补充多张图片、或者救助站记录多次救助行为。这时候用 v-for 循环渲染表单行每一行绑定一个独立对象再配合 el-button 的添加/删除事件操作数组。这里唯一的坑是 v-for 的 key 不能使用数组下标否则增删行会导致状态错乱最好用一个自增 id 或者直接使用行的唯一字段作为 key。图片上传在 Vue3 里还有一个容易出问题的点组件库的 on-success 回调有时候监听不到。这通常是因为上传组件把 action 指向了后端接口但后端响应结构不是组件默认期望的格式比如字段名不是 url或者接口返回了非 2xx 状态被拦截器拦截了。解决的通用思路是上传接口不返回完整文件 URL而是返回一个文件 id组件拿到 id 后再根据业务拼接出访问地址或者在后端响应里调整字段名让前端 upload 组件的自定义解析逻辑能读到文件名和地址。4. 让他跑起来MySQL8.0 部署、前后端联调与文档使用4.1 MySQL 8.0 的安装与初始化如果你本地是 Windows最省事的方式是下载 MySQL8.0 的 ZIP 包解压后手动初始化。步骤很简单解压到目录后在根目录建一个 my.ini 配置文件设置端口 3306、basedir 和 datadir然后以管理员身份打开 CMD执行 mysqld --initialize-insecure这会生成一个 data 目录且 root 用户初始密码为空再执行 mysqld --install 安装服务最后 net start mysql 启动服务用 mysql -uroot 连接后执行 ALTER USER rootlocalhost IDENTIFIED BY 你的密码;。如果你是 macOS 或者熟悉 Docker直接用 Docker 跑更干净docker run --name mysql8 \ -e MYSQL_ROOT_PASSWORDroot \ -p 3306:3306 \ -v /your/path/data:/var/lib/mysql \ -d mysql:8.0注意 MySQL8.0 默认的认证插件是 caching_sha2_password老版本的连接工具和早期 JDBC 驱动可能会连不上。如果遇到“Public Key Retrieval is not allowed”这个错误在 JDBC URL 里加上 allowPublicKeyRetrievaltrueuseSSLfalse 就能解决。数据库创建和执行脚本时统一用 utf8mb4 字符集排序规则选 utf8mb4_general_ci这样才能保证中文和特殊符号都正常存进去。4.2 后端启动连库、导脚本、跑起来拿到源码后第一步不是直接点运行而是先看项目结构和文档。带文档的项目一般在 README 或 docs 目录里写明JDK 版本、MySQL 版本、初始化脚本位置、前端构建命令、默认账号密码。先把这些信息读一遍比盲目启动省时间得多。application.yml 是后端连接数据库的窗口server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/adoption_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: root注意 URL 里的 serverTimezoneAsia/Shanghai 不能少否则 MySQL8.0 默认时区是 UTC你插入的 create_time 会比北京时间慢 8 小时排查起来非常头疼。用 IDEA 打开后端工程后先执行 Maven 的 clean install确认依赖都下载完整。然后把 sql 目录下的 init.sql 导入 MySQL再找到主启动类运行。日志出现 Tomcat started on port 8080 意味着后端起来了。可以先用浏览器访问一下 /api/animals 或者后端自带的 Swagger 文档接口确认数据能正常读到。4.3 前端启动路由、代理、调试前端工程打开后先看 vite.config.ts里面应该配了开发代理。开发模式下前端的 5173 端口和后端的 8080 端口属于跨域请求必须通过 Vite 的 proxy 转发而不是在前端代码里直接把请求地址写死后端 IP。export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })启动命令很简单npm install npm run dev然后浏览器访问 http://localhost:5173前端发出的 /api/xxx 请求会被 Vite 代理转发到后端 8080浏览器端看不到跨域问题。注意 npm install 的时候如果网络不好可以临时改成淘宝镜像 npm config set registry https://registry.npmmirror.com下载速度会快很多。如果你是第一次接触他人项目前端最容易出现的问题是“我明明改了接口地址为什么还是请求到原来的服务器”。原因是浏览器缓存或者代理配置没热更新修改 vite.config.ts 后必须重启 dev server只保存不重启是不生效的。Node 版本也建议保持在 16 以上Vite 4 和 Vue Router 4 对高版本 Node 兼容性更好。4.4 项目文档该怎么用标题里写着“含文档”拿到手之后文档不能只当摆设。一套合格的毕设项目文档一般包括需求规格说明书含功能模块图和用例说明、数据库设计文档含 ER 图和数据字典、系统部署说明含环境要求和启动步骤、测试报告。这些文档最大的作用不是提交给老师看而是让你能逆向理解开发者的设计思路。我会建议你照着这样的顺序使用文档先看需求文档了解系统有哪些角色和功能再看数据库设计文档把表结构和表关系画一遍接着看部署说明把项目跑起来最后对照代码看接口设计理解某个功能在前后端是怎么配合的。这四步走完这套源码你才算真正吃透了。面试或答辩的时候被问“你是怎么设计领养审核流程的”你可以说清楚状态流转和表设计比单纯说“我用了 SpringBoot 和 Vue”要强得多。5. 实战踩坑记录环境、框架与联调问题排查5.1 MySQL 8.0 的经典三连坑第一个坑是连接失败报 SSL 或公钥检索错误。MySQL8.0 默认启用 SSL 并采用 caching_sha2_password 认证JDBC 连接时经常出现 Public Key Retrieval is not allowed。解决方式就是前面提过的在 JDBC URL 后拼接 useSSLfalseallowPublicKeyRetrievaltrueOneNote 别小看这两个参数无数人本地连不上数据库都是这个原因。第二个坑是时区偏差。数据库存进去的时间比实际少了 8 小时或者查询报 The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。这是 MySQL 连接时区配置缺失导致的在 URL 中明确指定 serverTimezoneAsia/Shanghai 即可解决。如果你用 Navicat 或 Workbench 连接正常只有 JDBC 连接报错优先检查 URL。第三个坑是密码认证插件问题。有些老项目用的 mysql-connector-java 5.x 驱动连接 MySQL8.0 时会报 Unable to load authentication plugin caching_sha2_password。方案有两个升级驱动到 8.x 版本或者执行 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; 把认证方式改回旧式。实际项目里建议直接升级驱动改动面最小且更安全。5.2 MyBatis-Plus 使用中的几个“为什么不生效”分页不起作用是最容易碰到的。现象是代码里写了 Page 参数也调用了 selectPage但 SQL 里就是没有 LIMIT所有记录全查出来了。原因是 MyBatis-Plus 3.5.x 的分页插件必须显式注册不注册拦截器就不会拦截自动拼分页 SQL。如果你在项目里发现分页失效先检查是否配置了 MybatisPlusInterceptor 并且放入了 PaginationInnerInterceptor。第二个是逻辑删除配置。如果你在实体类的删除字段上加了 TableLogic那所有查询默认会带上 deleted0 条件。但如果你使用了自定义 SQL比如自己写了一段script里的查询 SQL逻辑删除条件是不会自动拼接的。这个细节在动物表“已删除记录还要保留在库里但列表不显示”的场景里很关键建议优先用继承 BaseMapper 的方法自定义 SQL 里手动加条件。第三个是批量插入。MyBatis-Plus 的 saveBatch 是基于批量 SQL 执行器实现的它并不是一条 SQL 插入所有数据而是按批次拆成多条 SQL默认每批 1000 条。如果你在初始化动物数据时发现 saveBatch 很慢可以调整 batchSize 参数但不宜设置过大否则单条 SQL 的字符串拼接会占用大量内存。5.3 Vue3 组合式 API 容易被绕晕的几个点先说 ref 和 reactive 的选择。我见过很多新手喜欢什么数据都用 reactive然后在方法里直接解构出变量赋值给一个常量结果视图不更新。记住一个原则所有基本类型、数组、以及会被整体替换的数据用 ref结构固定、属性较多且经常单独修改属性的对象用 reactive。列表数据几乎永远用 ref因为每次请求回来是整体替换你只需要 data.value res 就搞定。第二个是 v-model 在自定义组件上的改动。Vue2 里自定义组件用 model 选项配置 prop 和 event默认是 value 加 input。Vue3 改成默认 prop 是 modelValue事件是 update:modelValue写法是AnimalStatusSelect v-modelanimal.status /子组件内部defineProps([modelValue]) const emit defineEmits([update:modelValue])如果你是从零开始写组件这个知识点基本必考考试和面试都喜欢围绕它出题。第三是 UI 组件库样式不生效。Element Plus 按需引入后如果发现按钮、表格、弹窗样式缺失十有八九是样式文件没有被自动导入。你可以在 main.js 里全局引入 element-plus/dist/index.css 应急然后再排查自动导入插件的配置。特别是网上很多人说“vue3引入所有UI框架都不生效”大概率就是这个原因。第四是动态表单的响应式。之前提过 v-for 循环表单项注意在增删行时数组元素必须是响应式对象而不是普通字面量。你直接 push 一个普通对象后续修改该对象的字段视图可能不会更新。正确做法是每次新增行时创建一个 reactive 或 ref 包裹的对象再 push 进去保证嵌套属性的响应式链路是通的。5.4 前后端联调阶段的定位思路跨域问题在前端配置代理后基本能解决但有时代理配置没问题后端还是收到空请求或者 403。这时候先看后端控制台有没有跨域相关的日志再看前端的 request 拦截器是不是拦掉了带 token 的请求。系统开发中常见的拦截器逻辑是请求带上 Authorization 头后端校验 token如果 token 过期或者缺失直接返回 401。前端拿到 401 后跳转到登录页这是正常流程。如果没跳转大概率是 axios 响应拦截器里对 401 的处理写错了导致页面在错误状态里卡住。图片上传跨域是另一个高频点。后端上传接口接受了文件并返回了 URL但前端请求这个 URL 时发现图片不显示。如果图片是存在本地服务器上的后端的静态资源映射路径没配好就会出现这个问题。SpringBoot 里加一个配置Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadPath); }前端展示图片时直接拼接成 /upload/xxx.jpg 即可。如果图片存的是七牛云或阿里云 OSS那就不涉及这个配置而是要注意 URL 的 Bucket 权限是否允许公网读取。写在最后一套源码跑通之后的下一步我个人实际跑完一套这类项目后的体会是最大的收获不是“我启动了一个 SpringBoot 项目”而是终于把数据库设计、后端接口、前端页面这三层东西串在了同一条业务线上。领养申请从表设计一路走到前端表单提交中间所有的字段命名、接口约定、状态流转都环环相扣任何一环对不上功能就起不来。如果你拿到的源码是带文档的最后再分享一个小技巧不要把文档里的数据库设计直接当圣旨。对照着真实的业务页面想想这个字段前端到底用在哪、那个状态是谁改的发现问题就自己调整表结构或补字段。这个过程才是这套项目对你最大的帮助——源码是别人的业务思路消化完之后就是你自己的。后续有条件的话可以尝试把项目拆成前后端分离部署前端用 Nginx 托管后端打成 jar 包运行再把 MySQL 数据目录迁移一下。这套跑顺了你对 Java Web 项目从开发到部署的理解就完整了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门