
最近整理了一套 SpringBoot Vue MySQL 的 web 宠物猫认养系统源码标题里写的是“可直接运行”我实际部署验证了一下确实没吹牛。这个项目不是那种只有登录注册的架子工程而是把用户端、认养申请、管理员审核、猫咪上下架、留言评价这一整条业务链路都串起来了。后端用 SpringBoot 提供 REST 接口前端用 Vue 做页面交互数据库用 MySQL 存业务数据典型的单体前后端分离结构。如果你是刚学完 Java 和 Vue、正在找毕设项目或者想练手完整全栈流程的开发者这套代码非常适合作为参考模板。我自己在跑通这套系统的过程中踩了不少坑也补了不少细节。这篇文章就按照项目的实际形态从模块拆解、数据库设计、核心流程实现、本地部署和排错这几个角度来写记录的是我在整理和二次开发过程中的真实经验不是官方文档式的说明。需要先说明的是项目里有一部分实现的取舍会更贴近“教学演示”场景比如文件上传使用的是本地磁盘存储定时任务只做了简单的待办提醒。在理解这些设计意图之后你完全可以按照自己的需求去改造。1. 项目定位与整体架构拆解1.1 这个系统实际解决什么问题宠物猫认养系统的核心价值是把“发帖领养”这个线下行为搬到线上同时加一道审核流程保证猫咪不会随意被领走。我见过很多线下领养的信息只是发在群里领养人靠自觉填个表后续回访基本靠缘分。这套系统做到的是猫咪信息集中展示包含品种、年龄、性别、疫苗、绝育状态、图片和性格描述。用户注册后可以在线发起认养申请填写个人住址、养猫经验、经济来源等信息。管理员在后台审核申请审核通过后进入签订协议和回访状态。每一只猫都有独立的认养进度时间线避免一只猫被多人重复认领成功。实际上这套流程很像电商里的“商品浏览-下单-审核发货-售后回访”只不过商品换成了猫咪。理解这个类比之后你看代码里的状态流转会顺畅很多。1.2 角色权限与信息流转逻辑系统里划分了三种角色普通用户、送养人也可以叫管理员、超级管理员。严格来说实际源码里是通过用户表的 role 字段做了区分默认注册用户是 role1普通用户后台预设账号是 role2管理员。前端路由根据这个字段动态生成可访问菜单。信息流转这条线我画个文字流程你感受一下管理员登录后录入猫咪信息包括上传图片、填写健康档案、设定认养条件猫咪状态标记为“待领养”。普通用户浏览猫咪列表进入详情页查看完整信息点击“申请认养”按钮弹出申请表。申请表提交后数据写入认养记录表状态变成“待审核”同时管理员账号收到一条待办提醒。管理员在后台看到待审核列表点击查看申请人的详细资料选择通过或拒绝。通过后状态变为“待签协议”管理员可以在线下沟通节点更新状态比如“已签协议”“回访中”“已完成”。猫咪状态同步变化一旦认养完成猫咪在列表里就自动下架。整个链路是完整的没有出现那种“申请提交了但后台看不到”的半截子功能。这也是我判断这套源码质量还不错的第一个依据。2. 后端核心设计SpringBoot 的模块划分与关键配置2.1 工程结构与核心依赖选型后端工程是一个标准的 Maven 单模块项目包结构大致如下com.example.catadopt ├── config // 跨域配置、拦截器配置、文件上传配置 ├── controller // 控制层 ├── service // 业务层 ├── mapper // 数据访问层MyBatis Plus 的 Mapper 接口 ├── entity // 实体类 ├── dto // 接收参数的 DTO 对象 ├── vo // 返回前端的 VO 对象 ├── common // 统一响应封装、全局异常处理、自定义异常 └── util // JWT 生成与校验工具依赖选型上核心的几个是SpringBoot 2.7.x稳定且兼容性最好。MyBatis Plus 3.5.x提供 BaseMapper 和 LambdaQueryWrapper大幅减少 SQL 编写量。JWTjava-jwt用于登录态生成与校验。Lombok简化实体类的 getter/setter。Hutool用来处理图片验证码、ID 生成等杂项工具。MySQL Connector 和 Druid 连接池。这里我重点说下 MyBatis Plus。对于这种中小型管理系统手写 XML SQL 反而拖慢开发速度。用它的 LambdaQueryWrapper 可以写出类似lambdaQuery().eq(User::getRole, 1).list()这种语义化查询代码后期的维护成本很低。如果你是第一次接触建议去看一下它官方文档里的条件构造器这套源码里基本上每张表都用到了。2.2 application.yml 里的几个关键配置我贴一份常用的配置片段你在本地部署时可以直接对照修改server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cat_adopt?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource servlet: multipart: max-file-size: 10MB max-request-size: 20MB jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto有几个要点值得单独说一下。数据库连接串里的serverTimezoneAsia/Shanghai必加否则 Java 8 时间类型在连接 MySQL 8.x 时会直接报错或者时间字段出现在数据库里和本地时间相差 8 小时。这属于新手必踩的坑。map-underscore-to-camel-case开启之后数据库里的create_time才能自动映射到实体类的createTime字段。如果你关闭这个配置所有涉及时间和其他下划线字段的查询结果全会变成 null排查起来很让人头疼。jackson.date-format是控制接口返回 JSON 时日期格式的。不加的话前端拿到的时间是一个时间戳数组很反直觉。文件上传的限制也建议保留虽然猫咪图片其实不会太大但 10MB 的硬限制可以有效避免有人上传超大的原图把服务器磁盘打满。2.3 统一响应与异常处理的思路后端接口返回格式统一为{ code: 200, message: 操作成功, data: {} }这套源码里定义了一个 Result 类controller 的方法基本返回Result.success(data)或者Result.error(msg)。这种封装的好处是前端可以统一在 axios 拦截器里判断 code而不是每次都要用 try-catch 单独处理。全局异常处理用的是RestControllerAdvice注解。我验证过业务异常比如申请时提示“该猫咪已认养”会抛出自定义 BusinessException统一被捕获后返回友好的错误信息而不是直接甩给前端一个 500 的 ServletException 堆栈。我建议你以后写任何 SpringBoot 项目都先把这个统一结果和全局异常处理的架子搭起来。这套代码最大的参考价值也在这里它不只是一个功能 demo而是一个有工程化意识的雏形。3. 数据库建模MySQL 表结构与状态流转设计3.1 六张核心表的字段设计这套系统的数据库一共 6 张表我把核心字段和用途整理成表格方便你对照源码里的sql初始化脚本表名核心字段用途说明userid, username, password, phone, role, avatar, create_time用户登录、角色区分、个人资料catid, name, breed, age, gender, vaccine, neuter, health, status, cover_image, create_time猫咪信息及领养状态adoption_applyid, cat_id, user_id, reason, address, experience, status, create_time用户的认养申请表commentid, cat_id, user_id, content, create_time猫咪详情页的留言互动noticeid, user_id, title, content, is_read, create_time系统通知与审核结果回执adminid, username, password, role后台管理员账号也可并入 user 表设计上的亮点在于猫咪表和申请表之间没有做外键约束而是通过 service 层逻辑去保证一致性。这个设计对教学项目来说是对的——数据库靠程序控制逻辑反而更灵活也避免了 MyBatis Plus 级联查询带来的复杂度。有一个细节要注意猫咪的status字段我建议额外设计一个“草稿”状态管理员录入猫咪时先保存草稿核对图片和疫苗信息后再发布为“待领养”。源码里没有这个设计但我第二次跑通后自己加了对实际业务非常有用。3.2 认养流程的状态机设计认养流程是整个系统的核心。我在源码里看到的状态枚举比较简单0-待审核1-已通过2-已拒绝3-已签协议4-回访中5-已完成。这套状态机的核心在于只有认养申请处于“已通过”之后才能推进到签协议和回访环节。也就是说用户提交申请后管理员拒绝猫咪状态不变仍然可以被其他人申请一旦管理员通过猫咪状态立即改为“认养中”其他人再点申请就会提示“该猫咪正在被认养”。这个细节写起来不难但很容易被忽略。很多同类项目里“已通过”之后猫咪状态还留着“待领养”导致后续操作直接乱套。我自己在测试的时候就遇到过一次通过申请后猫咪在列表里仍然可被点击申请。排查后发现是 service 更新 state 的位置写错了更新申请表状态和更新猫咪状态的代码之间没有用事务包裹一条失败另一条还是提交了。我建议你把这个逻辑放在一个事务里示例代码如下Transactional(rollbackFor Exception.class) public void approveApply(Long applyId) { AdoptionApply apply adoptionApplyMapper.selectById(applyId); if (apply null || !apply.getStatus().equals(0)) { throw new BusinessException(申请不存在或已处理); } apply.setStatus(1); adoptionApplyMapper.updateById(apply); Cat cat catMapper.selectById(apply.getCatId()); cat.setStatus(1); // 认养中 catMapper.updateById(cat); }Transactional必须加在 public 方法上而且要确保是 Spring 管理的事务方法否则事务不会生效。这个坑我在自己写项目时踩过无数次。4. 前端要点Vue 页面组织与接口联调4.1 项目结构与权限路由前端是 Vue 2 Element UI 的技术栈脚手架用的是 Vue CLI。整体结构不算复杂重点看几个目录src/views按页面维度拆分文件夹比如home、catDetail、userCenter、admin。src/api按模块封装接口请求方法比如api/cat.js里封装了 getCatList、getCatDetail、applyAdoption 等。src/router前端路由表根据用户角色动态生成可访问路由。src/utils/request.js封装了 axios 实例统一设置 baseURL 和请求头。权限路由这块源码用的是“登录后根据后端返回的 role 字段跳转不同首页”的简单方案。普通用户看到的是“猫咪列表-我的申请-个人中心”管理员看到的是“猫咪管理-申请审核-用户管理”。没有做那种复杂的路由守卫权限判断但对这套系统来说完全够用。前端有个细节做得比较到位就是列表页和详情页用到了同一套猫咪卡片组件CatCard.vue避免了复制粘贴一大段卡片样式代码。你自己写 Vue 项目的时候这种复用组件的思想要刻意培养。4.2 发起认养申请的前后端联动前端这张申请表是整个交互里逻辑最重的一块。用户从猫咪详情页点“申请认养”会弹出一个对话框表单里包含申请理由、居住情况、养猫经验、收入情况。提交后前端调applyAdoption接口传的是catId和表单数据。我在跑通流程的时候特意看了请求参数格式发现源码里后端接收的是一个 DTO 对象而不是逐个字段接收PostMapping(/apply) public Result applyAdoption(RequestBody AdoptionApplyDTO dto) { return adoptionApplyService.submitApply(dto); }DTO 里包含了catId、reason、address、experience等字段。这种设计比写一堆RequestParam更规范因为表单字段一多参数接收就变得难以维护。前端 axios 封装时需要注意的是POST 请求一定要指定Content-Type: application/json否则后端RequestBody会直接拿不到参数。Element UI 的this.$refs.form.validate()这一步要做在接口请求之前避免空表单提交源码里这个校验已经写上了。4.3 管理后台的表格与审核逻辑管理员后台的核心页面是申请审核页前端用的是 el-table 展示所有申请记录每条记录右侧带“通过”和“拒绝”按钮。这个页面里比较值得学习的是操作列的隐藏逻辑。比如只有状态为“待审核”的申请才显示这两个按钮其他状态的记录只能查看详情。写完一个管理后台之后你会发现把操作的可见性和状态绑定比在接口层写一堆 if 判断要优雅得多。审核按钮的交互流程是这样的点击“通过”或“拒绝”前端弹出二次确认对话框。确认后调用adminApproveApply接口。成功后刷新当前列表数据。同时给申请人发送一条站内通知通知内容写到notice表里。第一次跑通时我以为通知消息是前端调用后端单独接口触发的看了代码才发现是在审核接口内部调了noticeService.addNotice()同步写入数据库。这种方式在单体项目里是最高效的不需要引入消息队列。5. 本地部署与常见报错排查5.1 十分钟跑通项目的标准步骤我在本地用的是 IDEA Navicat Node 14 的环境。跑通这套项目只需要四步第一步建库。在 Navicat 里新建一个名为cat_adopt的数据库字符集选utf8mb4然后导入项目自带的cat_adopt.sql文件。导入完成后确认六张表以及表里的几条测试数据都在。第二步改后端配置。打开application.yml把数据源的用户名和密码改成你自己的。连接串里的端口、数据库名也要检查一遍。第三步启动后端。用 IDEA 直接运行主类即可。正常情况下控制台会看到 Spring Boot 启动成功的日志端口默认 8080。因为源码里已经配置了 MyBatis Plus 的 SQL 日志你可以在控制台看到实际执行的每条 SQL方便核对。第四步启动前端。进入前端根目录依次执行npm install npm run serve启动成功后访问http://localhost:8081用源码里提供的管理员账号登录后台或者注册一个普通账号体验用户流程。这里有一个部署时的细节前端的vue.config.js里配置了开发环境代理把/api前缀的请求转发到http://localhost:8080。你如果改了后端端口要记得同步改这儿的 target。否则会出现浏览器里能看到前端页面但所有接口都报 404 或 504 的情况。5.2 高频报错对照表我把部署和二次开发过程中遇到的典型问题列成了表格你可以直接对照排查。现象原因解决办法启动时提示 Access denied for user root后端数据库账号密码不正确修改 application.yml 中的 username/password接口返回 500 且控制台报 unknown database数据库名不存在或连接串里的库名写错确认 cat_adopt 数据库已创建字符合前端控制台大量 Request failed with status code 404代理没生效或后端没启动确认后端启动成功检查 vue.config.js 的代理 target图片上传后页面不显示文件存到了后端磁盘目录但前端拼的 baseURL 不对检查文件上传路径映射和图片 URL 拼接规则登录后提示 Token 已过期服务器时间与本地时间不一致检查 JWT 过期时间生成逻辑和数据库时间配置时间字段差了 8 小时MySQL 连接串缺少 serverTimezone 配置在 url 后面追加 serverTimezoneAsia/Shanghai端口被占用上一轮的服务没关掉Windows 执行 netstat -ano 查占用进程后结束或者直接改 server.port在我的使用习惯里部署任何前后端分离项目都先从前端请求入手比较快。打开浏览器开发者工具看网络请求如果发现请求的 URL 前缀不对基本就是代理配置的问题如果 URL 正确但没响应再看后端日志能少走很多弯路。另外首次启动时如果出现 Lombok 相关的诡异报错建议直接换成项目 pom 里指定的 Java 版本。很多奇怪的问题都是编译版本不一致引起的不是项目代码的问题。说一个我在二次开发时比较在意的点这套系统预设的测试数据只有几条但如果你想拿它做演示或者答辩建议自己再录入 8-10 只特点差异比较明显的猫咪数据包括不同品种、不同年龄、不同健康状态。这样演示认养流程时前端页面的展示效果会好得多。最后再分享一个小技巧。如果你打算在这个项目基础上再做扩展优先从消息通知和回访记录这两个模块下手。前者可以接一个邮件推送后者可以做成每次回访填写记录的小表单。这两个功能是认养系统最容易被评审老师和用户关注到的业务点做出来的效果会非常加分。