ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue3前后端分离动物领养平台项目实战解析

SpringBoot+Vue3前后端分离动物领养平台项目实战解析 最近整理完一套动物领养平台的完整工程技术栈是 Java SpringBoot Vue3 MyBatis MySQL前后端分离代码里包含了用户端和管理端两套界面。这个项目从数据库设计到接口开发再到前端页面和联调部署基本把业务系统开发的完整链路走了一遍很适合正在学 SpringBoot 和 Vue3 的同学拿来当练手项目也适合毕设或者个人作品集参考。我不会只给你贴一堆源码截图而是把整个项目的核心设计思路、表结构怎么拆、接口怎么定、前端怎么配合、部署踩过哪些坑全部讲清楚。你把思路理顺了源码拿到手之后改造成自己的项目会比单纯抄代码快得多。1. 项目整体设计与技术选型思路1.1 为什么是 SpringBoot Vue3 MyBatis 这一套动物领养平台本质上是典型的 CRUD 状态流转业务系统核心场景是用户浏览宠物、提交领养申请、管理员审核处理。这类系统选技术栈第一考虑的不是“哪个框架新”而是“哪个组合能最快把业务落地、后期好维护”。SpringBoot 在这个场景里的优势非常明显Starter 机制把常用依赖全部封装好无需手动管理复杂配置内置 Tomcat 让后端可以直接java -jar跑起来自动装配配合统一的配置文件省掉了一大堆 XML 配置。对比传统 SSM 项目SpringBoot 把开发效率提高了不止一个档次尤其是新项目开荒阶段十分钟就能拉起一个能跑的服务。Vue3 这边组合式 API 让代码组织和逻辑复用比 Vue2 时代舒服太多。宠物卡片、筛选栏、领养表单这些组件各自的状态和交互逻辑可以集中写在setup里不像 Options API 那样 data、methods、computed 分散多处。配合 Vite 的开发热更新速度前后端联调时体验相当顺畅。MyBatis 放在这个项目里也有讲究。半自动 ORM 意味着 SQL 完全由你掌控宠物列表要按品种、年龄、状态做组合筛选这种多条件动态查询用 MyBatis 的where和if标签写起来特别顺手。如果你用 JPA复杂查询反而要花更多精力去拼 Specification不如直接写 SQL 来得直观。MySQL 就不用多说了中小型业务系统的标配存储。领养平台的数据量级远没到需要分库分表的程度MySQL 的事务能力和稳定性完全够用而且部署运维成本低本地开发调试也方便。1.2 业务模块规划领养流程是怎么拆的动手写代码之前先要明确这个平台到底要管哪些事。我做的这套设计里整个系统按角色拆成用户端和管理端两条线用户端注册登录、浏览宠物列表、按分类筛选、查看宠物详情、提交领养申请、收藏宠物、留言咨询、查看申请进度。管理端管理员登录、宠物信息录入与上下架、宠物分类管理、领养申请审核、用户账号管理、公告发布。整个业务的核心是领养流程的状态流转。一张领养申请单从用户提交开始经历待审核、已通过、已拒绝、已完成这几个状态。管理端审核通过后宠物状态同步改为“已领养”避免其他用户重复申请。这个状态机设计是整套系统的灵魂也是面试时值得拿出来讲的业务亮点。公告模块也别忽略。平台需要发领养须知、活动通知这个模块虽然简单但能补齐管理端首页的信息发布能力让系统显得完整。1.3 前后端分离的架构取舍前后端分离在这个项目里带来两个最直接的收益一是前后端开发互不阻塞后端把接口定义好之后前端可以并行开发页面联调阶段再对接二是部署灵活前端构建后的静态文件放在 Nginx后端独立部署任何一端出问题不会导致整个服务不可用。对应的工程结构是这样拆的animal-adoption ├── backend # SpringBoot 后端工程 │ ├── controller # 接口层 │ ├── service # 业务逻辑层 │ ├── mapper # MyBatis 数据访问层 │ ├── entity # 实体类 │ ├── common # 统一返回、异常处理、工具类 │ └── config # 跨域、拦截器配置 └── frontend # Vue3 前端工程 ├── src │ ├── api # 接口请求封装 │ ├── views # 页面组件 │ ├── components # 公共组件 │ ├── router # 路由配置 │ ├── store # Pinia 状态管理 │ └── utils # 请求拦截、工具函数 └── vite.config.js这套结构是业务系统开发里被验证过无数次的经典分层。后端 Controller 只做参数接收和结果返回业务逻辑全部收拢在 Service 层Mapper 层专注 SQL前端页面组件只负责视图渲染请求统一走 api 模块状态交给 Pinia。各层职责清晰项目源码即使交给另一个不熟悉的人接手也能很快定位到该改哪里。2. 数据库设计与后端核心实现2.1 表结构设计思路数据库设计是这类业务系统最见功底的部分。表结构如果设计得不好后面写 SQL 的时候会处处别扭。我最初设计时的核心表包括user用户表字段包括用户名、密码BCrypt 加密存储、昵称、手机号、角色普通用户/管理员、头像、创建时间。角色字段用tinyint存0 表示普通用户1 表示管理员简单直接没必要拆表。pet宠物表核心字段有宠物名称、分类 ID、年龄、性别、品种、健康状态、描述、图片 URL、状态待领养/已领养/已下架、发布人 ID、发布时间。category宠物分类表比如猫、狗、兔子这些字段就 id、名称、排序号。adoption_application领养申请表字段包括用户 ID、宠物 ID、申请说明、联系方式、状态待审核/已通过/已拒绝、审核人 ID、审核时间、备注。favorite收藏表用户 ID 和宠物 ID 联合唯一索引防止重复收藏。notice公告表标题、内容、发布时间。以宠物表为例建表 SQL 大致是这样CREATE TABLE pet ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, name varchar(50) NOT NULL COMMENT 宠物名称, category_id bigint NOT NULL COMMENT 分类ID, age int DEFAULT NULL COMMENT 年龄(月), gender tinyint DEFAULT NULL COMMENT 性别 0-公 1-母, breed varchar(50) DEFAULT NULL COMMENT 品种, health_status varchar(100) DEFAULT NULL COMMENT 健康状况描述, description text COMMENT 详细描述, image_url varchar(255) DEFAULT NULL COMMENT 图片地址, status tinyint NOT NULL DEFAULT 0 COMMENT 状态 0-待领养 1-已领养 2-已下架, publisher_id bigint NOT NULL COMMENT 发布人ID, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_category_id (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物信息表;这里有几个设计细节值得说。第一update_time用ON UPDATE CURRENT_TIMESTAMP数据更新时自动维护时间程序里不用再手动 set。第二status字段用数字枚举而不是字符串查询走索引更高效语义通过常量类注释说明。第三我只在分类表等明确需要级联约束的地方设置外键业务表如adoption_application里的用户 ID、宠物 ID 只保留逻辑关联不建物理外键。因为后来踩过外键的坑批量更新数据时外键约束会让操作束手束脚实际开发中逻辑关联完全够用。2.2 后端分层与 MyBatis 应用细节后端采用经典的 Controller-Service-Mapper 三层架构。我始终认为分层不是为了显得“正规”而是为了出了问题能找到人。Controller 层只做参数校验和结果封装Service 层处理业务规则Mapper 层只管 SQL各司其职。MyBatis 在这个项目里的用法有两次关键决策。第一Mapper 接口和 XML 映射文件的关系。我选择在 XML 里写比较复杂的 SQL比如宠物列表的多条件筛选select idselectPetList resultTypecom.example.entity.Pet select * from pet where if testcategoryId ! null and category_id #{categoryId} /if if teststatus ! null and status #{status} /if if testkeyword ! null and keyword ! and (name like concat(%, #{keyword}, %) or breed like concat(%, #{keyword}, %)) /if /where order by create_time desc /selectwhere标签会自动处理多余的 and这个特性在条件组合查询场景下尤其好用。第二分页用 PageHelper 插件一行代码PageHelper.startPage(pageNum, pageSize)就能自动生成 count 查询和 limit 语句避免了手写分页 SQL 的重复劳动。还有一个容易被坑的配置是驼峰映射。数据库字段create_time要能自动映射到实体类属性createTime必须在application.yml里开启mybatis: configuration: map-underscore-to-camel-case: true如果不加这个配置你会发现查出来的数据永远少几个字段而且不报错排查起来非常费劲。2.3 登录鉴权与核心接口设计用户和管理员的登录鉴权我用了 JWT。对比 Session 方案JWT 天然适合前后端分离的场景后端不需要维护会话状态Token 里直接携带用户 ID 和角色信息前端请求时在请求头带上Authorization后端拦截器校验即可。登录接口的流程是用户提交用户名密码Service 层用BCryptPasswordEncoder校验密码正确性通过后生成 JWT 返回给前端。后续请求统一经过拦截器拦截器从 Token 解析出用户信息放到ThreadLocal里业务代码可以直接获取当前登录用户 ID避免每个接口反复传用户参数。这套设计下核心接口大概是这样一个结构模块接口路径方法说明认证/api/auth/loginPOST登录返回 Token认证/api/auth/registerPOST注册普通用户宠物/api/pet/listGET宠物分页列表宠物/api/pet/detail/{id}GET宠物详情宠物/api/pet/addPOST管理端添加宠物宠物/api/pet/update/{id}PUT管理端更新宠物领养/api/adoption/applyPOST提交领养申请领养/api/adoption/listGET领养申请列表领养/api/adoption/auditPUT管理端审核申请收藏/api/favorite/togglePOST收藏/取消收藏统一返回结构也花了一点心思。所有接口返回{ code, message, data }格式code 为 200 表示成功非 200 对应各种业务错误码。前端 Axios 拦截器拿到非 200 统一弹错误提示业务代码只需要关心 data 就行。这套约定让前后端沟通成本低了很多后来加新接口时完全不用重复协商返回格式。提交领养申请这里有个事务细节必须注意。插入领养申请表之后还要同步把宠物状态改为“已被申请”这两个操作必须放在同一个事务里。我在 Service 方法上加了Transactional任何一步失败就整体回滚避免出现“用户申请成功但宠物状态没变”的数据不一致。3. 前端 Vue3 实现与联调细节3.1 Vue3 项目组织与组合式 API 实践前端项目通过 Vite 创建使用vue3 vue-router pinia element-plus这套组合。为什么不用 Vue CLIVite 基于原生 ESM 的开发服务器冷启动只需几百毫秒改动后页面秒级热更新开发体验比 Webpack 时代的等待感好太多。页面结构划分为用户端和管理端两套布局。用户端是横幅 宠物卡片流 详情抽屉的 C 端风格管理端采用左侧菜单 右侧内容区的后台布局。两套布局通过路由的meta.role字段区分访问权限普通用户访问管理端路由会被路由守卫拦截。组合式 API 在实际使用中最爽的场景是宠物筛选卡片。筛选栏的状态分类、状态、关键词、筛选结果列表、加载状态、分页信息这些相关的状态和逻辑在 Vue2 里会被拆散在 data、methods、watch 三处读代码时要来回跳。Vue3 里我用script setup把它们收拢在一起const filterParams reactive({ categoryId: null, status: 0, keyword: }) const petList ref([]) const loading ref(false) const total ref(0) const currentPage ref(1) async function loadPetList() { loading.value true const res await getPetList({ ...filterParams, pageNum: currentPage.value, pageSize: 10 }) petList.value res.data.records total.value res.data.total loading.value false }ref和reactive的使用原则我的实测经验是单个基础类型值用ref对象或数组这类引用类型用reactive。但如果你在代码里发现要对对象整体重新赋值比如petList.value res.data.records那还是用ref包一层方便因为reactive对象整体替换会丢失响应式。3.2 组件设计与状态管理前端公共组件抽了三个宠物卡片组件PetCard、图片上传组件ImageUpload、状态标签组件StatusTag。以PetCard为例它接收一个 pet 对象作为 props展示宠物图片、名称、品种、状态标签点击后触发view事件跳转详情。组件只负责展示和事件通知不直接写业务逻辑这样页面里复用起来不需要关心细节。Pinia 主要管理两件事用户登录信息和 Token以及全局的布局状态。登录成功后用户信息写入 Pinia store配合localStorage持久化刷新页面后通过store里的初始化方法恢复登录态。路由守卫检查如果目标路由需要登录又没有 Token跳转到登录页并带上 redirect 参数登录完成后跳回原页面。Axios 的封装是前端联调体验的关键。我在utils/request.js里统一创建 Axios 实例设置baseURL为/api请求拦截器从 Pinia 取 Token 放进请求头响应拦截器统一处理 code 非 200 的错误提示和 401 跳转登录。这样视图组件里完全不需要关心 Token 怎么加、错误怎么提示只需拿到 resolve 的 data 就能渲染。3.3 前后端联调的注意事项联调阶段最容易出问题的是跨域配置。开发环境下前端跑在 5173 端口后端跑在 8080 端口浏览器会拦截跨域请求。我的做法是在 Vite 配置文件里加代理而不是在后端开启全面 CORSserver: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端代码里请求写的还是/api/pet/list开发环境由 Vite 代理转发到后端生产环境由 Nginx 做同样的路径代理。后端不需要额外处理跨域过滤器避免被扫出安全漏洞。第二个常见坑是时间格式。后端返回的 LocalDateTime 默认是 ISO 格式字符串前端拿到后直接用会显示成2025-06-01T12:30:45这种带 T 的样式。我在后端统一配置了 Jackson 的日期格式化spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8第三个值得提醒的是图片上传。宠物需要上传照片后端接口接收 MultipartFile保存到本地磁盘的uploads目录返回可访问的 URL。前端上传组件用的 Element Plus 的 Upload 组件配上action指向后端接口headers带上 Tokenon-success回调里把返回的图片 URL 写入表单数据。要注意后端的静态资源映射配置否则上传成功后图片 URL 访问不到spring: web: resources: static-locations: classpath:/static/,file:${upload.dir}4. 环境搭建、部署上线与问题排查4.1 本地环境准备与参数配置先强调版本匹配问题这是我见过新手踩坑最多的地方。JDK 版本用 17 或 21SpringBoot 用 3.xMyBatis 用mybatis-spring-boot-starter的 3.0 版本如果你用的是 SpringBoot 2.7对应 JDK 8 或 11 就行两边版本的 JDK 不能混用。Vue3 项目要求 Node.js 16 以上推荐 18 或 20 LTS 版本npm 也同步升级到最新。MySQL 建议用 8.x。安装完成后有两点必须确认一是字符集要设置为utf8mb4否则存储中文可能出乱码二是 MySQL 8 默认的加密插件是caching_sha2_password如果你的 JDBC 驱动版本太低会连不上。我用的是 8.0.33 版本连接串和驱动都对齐就没遇到这个问题。后端application.yml里的关键配置spring: datasource: url: jdbc:mysql://localhost:3306/animal_adoption?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true jwt: secret: your-secret-key expire-hours: 72serverTimezoneAsia/Shanghai这个参数一定要显式配置。MySQL 8 不配时区的话后端连接时会报 CST 时区错误或者返回的时间差 8 小时属于必踩的坑。4.2 打包部署实操本地开发跑通之后部署上线其实就三件事前端构建、后端打包、配置 Nginx。前端在工程目录执行npm run buildVite 会把产物生成到dist目录。这个目录里的 index.html、js、css 就是最终的静态文件。我习惯把管理端和用户端分开放如果你只有一个前端工程直接整个dist丢给 Nginx 就行。后端打包执行mvn clean package -DskipTests生成的可执行 jar 包直接用java -jar启动。为了日志可控启动命令我一般写成nohup java -jar animal-backend.jar --spring.profiles.activeprod app.log 21 Nginx 配置是前后端分离部署的核心。静态文件由 Nginx 直接返回所有/api请求反向代理到后端端口server { listen 80; server_name your-domain.com; root /var/www/animal-frontend/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /uploads/ { proxy_pass http://127.0.0.1:8080; } }这里有个特殊点。开发时前端路由用的 history 模式刷新页面时 Nginx 会去磁盘找路径对应的文件结果找不到返回 404。必须加一个 location 配置把未命中文件的路由都重写到 index.htmllocation / { try_files $uri $uri/ /index.html; }这个配置不加上线后用户刷新详情页或管理端页面就会看到白屏 404是前后端分离部署最高频的问题。4.3 高频问题排查实录把我在这个项目里实际遇到的问题整理成了一张排查表按频率排序现象可能原因解决办法启动报错Invalid bound statementMapper XML 没被扫描到确认 mapper-locations 路径正确XML namespace 与接口全限定名一致登录后列表接口 401Token 过期或拦截器没放行登录接口排查拦截器 excludePathPatterns 是否包含 /auth/login、/auth/registerVue 页面数据不更新reactive 对象整体被替换赋值改用 ref 管理列表或使用 Object.assign 合并中文乱码数据库字符集不是 utf8mb4建库时显式指定 character set utf8mb4连接串加 characterEncodingutf8时区差 8 小时连接串缺 serverTimezone 或项目容器时区不对连接串配置 Asia/ShanghaiLinux 设置timedatectl set-timezone Asia/Shanghai跨域请求被拦截Vite 代理未生效或后端 CORS 冲突优先用 Vite proxy后端不要同时开全局 CORS文件上传失败磁盘目录不存在或权限不足确保 upload.dir 目录已创建Nginx worker 用户有写权限刷新页面 404前端 history 路由未配置 try_files按上文添加 try_files 配置还有一个 MyBatis 调试技巧。开发阶段如果你想知道某条 SQL 到底执行了什么、参数传了什么在application.yml里加一段配置日志里就会打印完整的 SQL 语句和参数值logging: level: com.example.mapper: debug注意这里的com.example.mapper要改成你实际的 Mapper 包路径。这个配置比看异常栈快多了复杂的动态 SQL 排查参数问题时几乎必用。最后提一下首次启动的数据初始化。我会准备一个schema.sql和data.sql把建表语句和默认管理员账号用户名 admin密码 BCrypt 加密后的值一起放进 SQL 文件。新环境部署时先执行 SQL 脚本再启动后端省去手动敲初始化命令的麻烦。这套项目做完我个人最大的体会是一个业务系统从零到上线真正的成本不在写了多少行代码而在数据结构怎么设计、接口约定怎么定、环境和部署的坑怎么避。你把这个动物领养平台的源码吃透把上面这些设计权衡和排查经验用自己的话复述出来遇到同类业务系统无论是毕设答辩还是面试聊项目都能非常扎实地讲清楚每一层在干什么、为什么这么干。
返回列表