☰
SpringBoot2+Vue3动物领养平台全栈实战:从数据库到部署
2026/10/13 23:50:09 网站建设 项目流程

说实话,做这类“动物领养平台”的Java Web项目,市面上源码不少,但大多数要么堆了一堆炫技框架,要么前后端耦合得没法看。我自己因为帮本地的动物救助站处理过领养信息登记,花了挺长时间从需求梳理到最后跑通,最后落地的技术栈正是 SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0。这套组合最大的好处就是:SpringBoot2保证了后端开发效率,Vue3 + Vite让前端开发体验顺畅,MyBatis-Plus把繁琐的CRUD写操作压缩到了极致,MySQL8.0有JSON、窗口函数这些现代特性兜底,整个项目从空目录到全功能上线,完全可以控制在可控范围内。

这篇文章我会把整套系统的技术选型、数据库建模、核心功能闭环、源码搭建步骤,以及我在实际运行中踩到的坑完整写出来。适合正在做毕设、公司内训项目,或者单纯想学习前后端分离架构的开发者参考。尤其是“含文档”这份东西,很多人忽略了它的价值——后面我会专门讲怎么看文档才能少走弯路。

1. 为什么选择SpringBoot2 + Vue3这套技术栈

1.1 前后端分离到底给这个项目带来了什么

动物领养平台天然存在两个差异极大的使用者:普通用户和救助站管理员。普通用户要的是快速浏览宠物、搜索品种、提交申请;管理员要看的是宠物管理、申请审核、用户管理。如果混在一个单体模板里,两边的交互逻辑会互相干扰,改一边容易坏另一边。

我按前后端分离来做,前端拆成用户端和管理端两个Vue3应用,后端只提供RESTful接口。Vue3的Composition API对业务逻辑的复用非常友好,比如宠物筛选条件、分页加载这类的公共逻辑,直接抽成hook函数,用户端和管理端同时调用,不用复制粘贴。加上Vite做开发服务器,热更新速度比原来用Webpack的时候快了一个量级,调整样式、改接口联调基本是秒级反馈。

从部署角度看,分离架构也更灵活。前端打包成静态文件放到Nginx里,后端就是一个Spring Boot的jar包,数据库单独一台实例。哪一层出问题就只处理哪一层,不容易出现整个服务全挂的情况。

1.2 MyBatis-Plus如何减少重复劳动

如果完全用MyBatis写XML,这个项目的开发量会大不少:因为宠物、用户、申请、收藏、评论这些实体的基础增删改查实在太类似了,每张表都要写一遍Mapper和XML,既浪费时间又容易在字段映射上犯错。

MyBatis-Plus把单表CRUD直接内置了,自带分页插件,代码里只需要这样:

// 服务层继承IService后,分页查询几乎就是一行 Page<Pet> page = petService.page( new Page<>(current, size), new LambdaQueryWrapper<Pet>() .eq(Pet::getStatus, 0) .like(StringUtils.hasText(keyword), Pet::getName, keyword) .orderByDesc(Pet::getCreateTime) );

LambdaQueryWrapper用方法引用代替字符串字段名,编译期就能发现字段拼写错误,比老写法安全得多。对于多表关联,我还是会手写XML或者用注解SQL,但绝大多数单表操作确实不用碰SQL了。这种取舍我认为很实用:框架帮你解决80%的重复劳动,剩下20%你牢牢掌握在自己手里。

1.3 MySQL8.0用上了哪些新特性

该项目使用了MySQL8.0,而不是还在用5.7,原因有三个:

  • 默认字符集是utf8mb4,emoji、生僻字都能正常存储,宠物名、用户留言里有emoji的情况还不少。
  • 支持窗口函数,比如统计每周新增宠物数量这类需求,SQL写起来清晰很多。
  • 公用表表达式(CTE)可以让递归查询分类层级时不用再写复杂的自连接。

另外8.0对JSON的支持更成熟。宠物表里有些字段是动态的,比如“疫苗记录”“健康状况备注”,不太适合固定列存储,我直接用JSON类型保存,需要查询时再配合JSON_EXTRACT处理,灵活性和查询性能兼顾。

2. 数据库建模:核心是“宠物-用户-申请”三张基础表

2.1 用户表和宠物表怎么设计

用户表是平台的基础,我关心的字段主要有这些:

字段名类型说明
idbigint用户ID,雪花算法生成
usernamevarchar(50)登录名
passwordvarchar(100)BCrypt加密后的密码
nicknamevarchar(50)昵称
phonevarchar(20)联系方式
avatarvarchar(255)头像路径
roletinyint0普通用户,1管理员
create_timedatetime注册时间

密码为什么用BCrypt而不用MD5?因为MD5撞库实在太容易了,可能几十秒就能跑完一个常用密码字典。BCrypt本身内置了随机盐,相同密码加密出的结果完全不同,能有效抵抗彩虹表攻击。

宠物表则是整个平台的内容核心:

字段名类型说明
idbigint宠物ID
namevarchar(50)宠物昵称
speciesvarchar(20)猫/狗/其他
breedvarchar(50)品种
age_monthint年龄(按月算)
gendertinyint性别
statustinyint0待领养,1审核中,2已领养,3已下架
cover_imagevarchar(255)封面图
descriptiontext宠物经历与性格
health_infojson疫苗、绝育、驱虫等健康情况
create_timedatetime发布时间

年龄我用“月”而不是“岁”,是因为幼龄宠物在领养场景里很常见,几个月大的小猫小狗写成0岁会显得很奇怪,按月存储统计筛选也精确。

2.2 领养申请表与状态机的设计

领养申请是整个业务里最容易出现并发问题的地方。我单独建了一张adoption_apply表:

  • id
  • pet_id
  • user_id
  • applicant_name
  • applicant_phone
  • address
  • reason
  • status(0待审核,1通过,2拒绝,3已取消)
  • audit_remark
  • create_time
  • audit_time

申请状态的流转一定要在代码里做成状态机,不能随意跳转。比如“已拒绝”的申请不能再变回“待审核”,“已领养”的宠物不能再提交新的申请。这里我加了一个关键逻辑:同一只宠物只允许存在一条“待审核”状态的申请记录,防止同一个用户反复提交把后台淹没。

2.3 图片存储与分类标签的补充

图片字段在数据库里存的是相对路径,比如/uploads/pets/20240915_xxxx.jpg,实际文件存在本机磁盘或对象存储里。为什么不在数据库里直接存Base64或者完整URL?因为Base64会让表数据膨胀三分之一左右,查询性能明显下降;存相对路径则方便切换部署环境,只要保证上传目录映射到正确的访问路径就行。

分类标签我没有单独建“标签表-宠物标签关联表”,而是在宠物表里加了一个tags字段,直接用逗号分隔的短字符串。考虑是这个平台的标签量级并不会特别大,也就是“亲人”“已疫苗”“需特殊照顾”这类几十个以内,用like查询完全扛得住。等标签涨到几百上千再拆表不迟。

3. 核心业务闭环:注册登录、宠物发布、领养审核

3.1 用户端:从“刷到一只宠物”到“提交申请”

用户端的核心链路是这样的:注册登录,首页按分类浏览宠物,详情页看照片和健康信息,喜欢的话点“申请领养”,填写申请表单后等待管理员审核。

为了让这个链路顺畅,我在前端做了两件事。第一是宠物列表采用瀑布流卡片布局,因为宠物照片普遍是竖图偏多,瀑布流比等高的grid卡片视觉上更自然;第二是详情页加入了“相似宠物推荐”,按物种和品种标签匹配。这两个功能对用户留存有实打实的影响,实测上线后详情页跳到申请页的转化率明显提高。

申请表单里我预览强调了几点:申请人名字必须和用户真实姓名一致,手机号要能收到短信验证;家庭住址要写清楚。后台审核人会根据这些信息初步判断申请者是否有条件养宠物。前端做好必填项校验还不够,后端同样要校验一遍,而且这是安全底线,不能只依赖前端。

3.2 管理端:审核、上下架与用户管理

管理端界面我做得比较克制,左边导航栏三块:宠物管理、申请审核、用户管理。宠物管理除了常规的增删改查,还需要支持一键下架和批量导入,批量导入用Excel模板,后台解析后逐条写入数据库并做字段校验。

申请审核列表我默认按“待审核”优先排列,点击进去能看到宠物基本信息、申请人填写的表单、以及该用户的申请历史。这样设计是为了辅助管理员判断:如果某个人一周内申请了8只宠物,大概率只是“看了看”而不是真的要养,这时候拒绝理由可以提示“请确认领养意愿后再提交”。

审核通过后的操作是事务性的:先更新申请状态为“通过”,再把宠物表的status改成“已领养”。这两个步骤必须在一个@Transactional里完成,否则可能出现“申请状态显示通过但宠物还在待领养列表”的脏数据。

3.3 领养状态的细节:过期、取消与回收

一个容易遗漏的点是“待审核”申请的有效期。有的用户提交申请后就不管了,宠物信息也不更新,导致后面真正想领养的人无法申请。我加了一个定时任务,每天扫描超过7天未处理的“待审核”申请,自动标记为“已过期”,同时把宠物恢复到“可申请”状态。定时任务我直接用Spring自带的@Scheduled,没有引入额外的分布式任务框架,单机部署场景完全够用。

另外,用户如果电话联系管理员说“我不申请了”,管理员在后台可以选择“取消申请”,这样宠物的状态能快速回收,避免占用名额。

4. 从源码到可运行:完整搭建手册

4.1 环境准备与版本对应

我实际验证过一套比较稳定的环境组合:

  • JDK:8或11(SpringBoot2.x在这两个版本下都没问题)
  • Node.js:16.20+(Vue3对Node版本有要求,太老的版本跑不起来)
  • MySQL:8.0.20以上
  • Maven:3.6.3以上
  • IDE:后端用IntelliJ IDEA,前端用VS Code

社区版IDEA也能跑,不过插件支持会少一些。特别提醒,如果本机装了多个JDK,一定要在Spring Boot的启动配置里明确指定JAVA_HOME,否则经常出现“编译时用了17、运行时用的8”这种诡异问题。

4.2 前端运行:依赖安装与接口代理

拿到前端源码后,先看根目录有没有package.json。安装依赖:

npm install npm run dev

如果npm install报错,八成是源的问题。我建议先设置国内镜像源再重装:

npm config set registry https://registry.npmmirror.com

前端项目里有一个环境配置文件,比如.env.development,里面长这样:

VITE_API_BASE_URL=/api

开发环境我采用的是Vite代理方案,把/api转发到后端的8080端口。这样做的好处是前端代码里不会出现跨域绝对地址,所有请求都走相对路径,等部署到生产环境时只要Nginx配一条转发规则就能无缝切换。

4.3 后端配置:application.yml里的关键项

后端配置全部集中在application.yml里。我最关注的是这几个部分:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pet_adoption?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

characterEncoding=utf8和serverTimezone=Asia/Shanghai这两项必须配,不然很容易出现中文乱码和数据库时间差8小时的问题。map-underscore-to-camel-case: true让数据库的create_time自动映射成Java类的createTime,不用在每个实体上手动写@TableField。

数据库初始化脚本在源码的sql目录下,我建议用Navicat或命令行按顺序执行,先建库再建表。脚本里如果带了测试数据,是好事,能让你开发时有东西看,不用自己在后台一张张添加。

4.4 “含文档”应该怎么看才对

源码附带的文档一般会有这几份:系统设计说明书、数据库设计说明、接口文档、部署文档。很多人拿过来直接跳过文档就开跑,遇到问题再去翻,效率反而低。

我的建议是:先读部署文档,确认环境和启动步骤;再读数据库设计说明,理解表结构和关联关系;最后把接口文档当作开发时的“字典”,联调时随时查阅。系统设计说明书我一般放在最后看,因为带着代码印象去看它,能比自己设计者更快速对应到实现细节。

如果接口文档是Swagger生成的,启动后端后访问http://localhost:8080/doc.html就能在线调试接口,比拿着postman自己填JSON方便得多。

5. 实战中遇到的坑与优化建议

5.1 懒加载引发的“Session关闭”问题

这是我第一次联调时踩得最狠的坑。宠物实体里关联了用户实体,而关联关系配置成了默认的懒加载。当前端请求宠物列表时,后端在事务里查询宠物,等JSON序列化的那一刻事务早就结束了,结果直接抛异常。

解决办法有三种,我最推荐的是不要直接在实体上做关联,而是在查询时用VO对象接收结果,DTO/VO里只放你需要展示的字段。比如:

public class PetVO { private Long id; private String name; private String species; private String nickname; // 发布人的昵称 }

然后在Mapper里写一个小结果集SQL来填充,避免实体之间的强耦合。这对接口性能也有好处,因为只查必要字段,而不是把整个关联对象树都加载出来。

5.2 文件上传路径的“假成功”

上传宠物照片时,本地测试一切正常,部署到服务器后就发现图片打不开。排查到最后的问题是:本地Windows路径用反斜杠,服务器Linux路径用正斜杠,而代码里直接把路径和文件名拼接了。

这个问题的标准解法是不要自己拼路径字符串,用Spring提供的Resource和ResourceUtils类来处理。还有一个小坑是上传目录如果放在项目内部,执行npm run build或重新部署jar包时可能被覆盖。正确做法是把上传目录配置成外部路径,比如/data/pet-upload/,再通过配置类映射到web访问路径。

5.3 并发下的重复申请与重复审核

前端按钮加disabled只是防君子不防小人,网络慢的时候用户连点三次,后端会收到三条一样的申请记录。我在adoption_apply表里加了pet_id + user_id + status的唯一索引,但“待审核”状态的唯一性还不够,因为数据库无法在唯一索引里对status = 0做部分索引判断。

最终我用同步锁解决:在服务层对petId.toString().intern()加锁,同一只宠物的并发申请串行化。这样既不会锁住整个业务,又保证了同一时间只有一条申请能插入。

5.4 接口安全:防刷、防泄露管理员数据

管理端接口一定不能裸奔。我做了三个基础安全措施:

  • 登录后签发JWT,前端存到localStorage里,每次请求都带Authorization头。
  • 后端写一个拦截器,校验JWT,再把解析出的用户ID放到ThreadLocal里供业务层使用。
  • 管理员接口额外校验role字段,普通用户即使拿到管理端接口地址也会被拦截器拒掉。

另外就是密码和敏感信息不直接打日志,logback配置里把密码字段设置成占位符“******”,防止排查问题时把用户隐私打到日志文件里。

6. 上线部署的补充经验

6.1 Nginx静态资源与反向代理

前端构建产物在dist目录,部署时我直接把它指到Nginx的html目录,Nginx配置里加两条核心规则:

server { listen 80; server_name your-domain.com; location / { root /opt/pet-adoption/dist; try_files $uri $uri/ /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; } location /uploads/ { alias /data/pet-upload/; } }

try_files ... /index.html这段是为了支持Vue Router的history模式,否则直接刷新某个子路由页面会404。/uploads/的alias路径和上传目录保持一致,图片才能正常访问。

6.2 生产环境的MySQL调优重点

MySQL8.0在新装环境里,几个默认值值得调一下:

  • innodb_buffer_pool_size:如果机器内存有8G,我习惯设为4G左右,对查询性能提升立竿见影。
  • max_connections:默认151对较大并发量来说偏小,调到500比较稳妥。
  • 慢查询日志打开,long_query_time设成2秒,方便上线后定位慢SQL。

宠物列表页如果数据量超过几万条,建议在species、status和create_time上建联合索引。这个索引对筛选和排序都有明显优化效果。

6.3 服务器部署时的“最小化改动”原则

拿到含文档的源码,很多人喜欢先自己改数据库名、改密码、改端口。我的经验是:第一次跑通之前不要改任何非必要的配置。先把默认值原样跑起来,确认能登录、能看到宠物列表,再逐步改成你自己的环境参数。这样能非常快速地区分“配置问题”和“代码问题”,免得改了一堆地方后报错了都不知道是哪一个改出来的。

最后再分享一个小技巧:启动后端时加上--spring.profiles.active=prod,配合不同的配置文件,把开发环境、测试环境、生产环境的数据源和日志级别分开管理。这个习惯我从这个项目就开始养成,后来接手其他SpringBoot项目都这么干,省掉了无数“跑着跑着发现连的是测试库”的尴尬。

整套项目从数据库设计到前后端联调再到服务器部署,链路不算短,但每一步都留有余地,源码文档齐全的情况下,整个搭建时间其实能压缩在一天以内。如果你想深入了解某个模块的实现细节,或者部署时遇到奇怪的报错,按上面这些排查思路走一遍,大概率能把问题定位在“配置、代码、环境”三选一里。每一步都扎实走下来,这套平台后续接微信小程序或者APP都留了接口扩展的空间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询