1. 从小区流浪猫说起:这套系统到底解决的问题是什么
我最初关注流浪动物救助,是因为小区楼下那只橘猫。它有固定的喂食点,有志愿者拍照发朋友圈,但信息散在十几个群里,今天谁喂了、明天猫在哪、有没有生病,全靠零散聊天记录撑起来。想领养的人找不到靠谱的宠物来源,救助站却看不到足够多的申请流量,两边信息完全不对称。
后来我整理了一套用 Java SpringBoot+Vue3+MyBatis 做的前后端分离流浪动物救助平台源码,配合 MySQL 数据库存放全部业务数据,算是把这类公益场景里最常见的"信息孤岛"问题用一套标准 Web 系统串了起来。它不是一个只改改展示面的静态网页,而是一套可申请、可审核、可管理、可统计的完整业务系统。
这套源码适合三类人:一是做课程设计或毕业设计的学生,需要一个能讲清楚业务闭环、能演示前后端交互的完整项目;二是刚入行的 Java 全栈开发者,想看看 SpringBoot+Vue3+MyBatis 在实际业务里怎么协作;三是公益组织或小型动物救助站,想低成本把救助流程线上化。
先说结论:这套系统的核心价值不是把某个页面做得漂亮,而是把"发现流浪动物—提交救助—建立档案—发布领养—审核领养—回访记录"这条救助链路,用真实的业务表结构和接口逻辑固定下来。下面我把业务设计、数据库结构、接口实现、部署踩坑、二次开发方向依次讲清楚。
1.1 平台里的三类角色,边界是怎么划分的
系统整体分为三个端,对应三种权限身份,这也是绝大多数管理类系统的通用骨架:
| 角色 | 典型入口 | 核心权限 |
|---|---|---|
| 普通用户(公众) | H5/PC前台 | 查看宠物列表、提交救助申请、发起领养申请、在线捐赠、查看公告 |
| 救助站/志愿者 | 后台管理 | 处理救助申请、维护宠物档案、审核领养申请、记录回访情况 |
| 系统管理员 | 后台管理 | 用户管理、角色权限配置、公告发布、数据统计、基础分类维护 |
权限控制不是只在前端菜单上做隐藏,后端接口上必须有过判断。比如普通用户提交领养申请,只能操作/api/adopt/apply这类接口;管理员修改宠物状态,要走/api/admin/pet/update,并且由拦截器校验 JWT 里的角色字段。前端隐藏只能防君子,后端校验才防得住直接请求接口的绕过操作。
1.2 救助业务的主流程闭环
整个业务链条我建议这样理解:
一个用户在路上发现了流浪猫狗,他在前台填写救助申请,带上定位、照片和简单描述。救助站审核通过后,系统为该宠物创建档案,包括品种、性别、绝育状态、疫苗记录、健康状况和当前安置位置。档案公开后,有意向的领养人提交申请,填写家庭情况、养宠经验、居住条件等信息。救助站对申请进行审核,审核通过后约定线下回访,回访结果录入系统,最终确认领养关系。
这套流程走下来,每一笔业务动作都有迹可循。哪怕只是做一个课设项目,面试官或者评委问"你的数据是怎么流转的",你都能用这条链路讲出完整逻辑,而不是说"我做了个增删改查"。
2. 技术选型不是赶时髦,而是这套组合最省心
很多人一上来就问"为什么不微服务""为什么不用 Redis 缓存""为什么不用 Vue2",这类问题在答辩时也确实高频。我的回答很直接:对于救助平台这种中小规模业务系统,SpringBoot+Vue3+MyBatis+MySQL 是性价比最高的组合,学习成本、部署成本、维护成本都在合理范围。
SpringBoot 解决了配置地狱的问题,内嵌 Tomcat,一个java -jar就能跑后端;MyBatis 保持 SQL 的灵活性,复杂联表查询写在 XML 里,调优直观;MySQL 开源免费,配套 Navicat 管理工具成熟,社区资料多到搜不完;Vue3 的组合式 API 在组件复用和逻辑组织上比 Vue2 的 Options API 舒服太多。
还有一个现实因素:市面上绝大多数的课设、毕设、中小型管理系统源码都在用这套技术栈,遇到问题你几乎能搜到现成答案。用冷门框架确实显得"高级",但出了问题你可能连报错信息都看不懂。
2.1 SpringBoot 在这套源码里承担了什么角色
后端按经典三层结构组织:Controller 层只做参数接收和结果封装,Service 层写业务逻辑和事务管理,Mapper 层通过 MyBatis 与 MySQL 交互。包结构大致是:
com.rescue.platform ├── controller // 接口入口 ├── service // 业务逻辑 ├── mapper // MyBatis Mapper接口 ├── entity // 数据库实体 ├── dto // 参数传输对象 ├── vo // 视图返回对象 ├── config // 配置类,跨域、拦截器等 ├── utils // 工具类,JWT、密码加密等 └── common // 统一返回结果、异常处理特别说一下事务。提交救助申请时,需要插入申请主表和相关的图片附件记录;审核通过时,需要更新申请状态并插入宠物档案。这些操作要么全成功,要么全回滚,Service 方法上用@Transactional标注即可。实际整理源码时,我特意在"提交救助申请"和"审核通过建档"这两个方法上加了事务演示,因为这是面试官最喜欢问的问题:你项目里哪里用到了事务,为什么用。
2.2 前端用 Vue3 的哪些能力组织页面
前端部分用的是 Vue3+Vite+Element Plus+Axios。Vite 比 Webpack 快很多,尤其是冷启动和热更新体验,开发效率明显提升。
页面按模块划分:
src ├── api // 接口请求封装,按模块拆分 ├── router // 路由与导航守卫 ├── store // Pinia状态管理,保存用户信息 ├── views │ ├── home // 前台首页,宠物展示 │ ├── rescue // 救助申请相关 │ ├── adopt // 领养申请相关 │ ├── user // 个人中心 │ └── admin // 后台管理页面 ├── components // 公共组件,宠物卡片等 └── utils // 请求封装、token存储等组合式 API 让我最明显的感觉是:同一个业务功能的变量、计算属性、方法可以放在一起,不用像 Vue2 一样在 data/computed/methods 之间来回跳。比如宠物列表页,把分页参数、列表数据、加载方法都写在setup里,思路是连贯的。
用defineProps接收父组件传递的宠物对象,用defineEmits触发事件,页面组件之间的通信比 Vue2 的$emit更容易理解。
2.3 MyBatis 为什么保留 XML 写 SQL
MyBatis 最被人喜欢的一点就是"SQL 可控"。救助平台里大量存在多表联查:宠物列表要关联分类表查品种名,领养申请要关联用户表和宠物表查申请人和宠物信息,救助记录要关联用户表查提交人。这些场景如果在代码里拼 SQL,维护起来想哭;用注解虽然简单,但复杂 SQL 混在 Java 代码里可读性很差。
所以我的做法是:单表简单操作(比如根据 ID 查询)用注解,多表联查、动态条件、分页查询全部写在 XML 里。
<select id="selectPetPage" resultType="com.rescue.platform.vo.PetVO"> SELECT p.*, c.category_name FROM pet_info p LEFT JOIN category_type c ON p.category_id = c.id <where> <if test="keyword != null and keyword != ''"> p.pet_name LIKE CONCAT('%', #{keyword}, '%') </if> <if test="status != null"> AND p.status = #{status} </if> </where> ORDER BY p.create_time DESC </select>分页方面直接集成 PageHelper 插件,前端传入pageNum和pageSize,后端返回分页结果对象,前端 Element Plus 的pagination组件无缝对接,这是大多数成熟项目的标准做法。
3. 数据库就是系统的地基:救助全流程的表结构拆给你看
整套系统一共大概十来张表,我挑核心的几张说明设计思路。每一张表都加了create_time、update_time、deleted三个通用字段。逻辑删除字段deleted很关键,救助平台的数据有公益和追溯属性,物理删除会把历史行为删得干干净净,所以全部用逻辑删除,查询时默认过滤deleted=0。
| 表名 | 用途 | 核心字段 |
|---|---|---|
| sys_user | 用户表 | username, password, role_type, phone, real_name |
| category_type | 宠物分类表 | category_name(猫/狗等) |
| pet_info | 宠物档案表 | pet_name, category_id, gender, sterilized, vaccine, health_status, status, cover_image |
| rescue_apply | 救助申请表 | user_id, pet_id, location, description, image_url, status |
| adopt_apply | 领养申请表 | user_id, pet_id, family_desc, experience_desc, address, status |
| review_record | 回访记录表 | adopt_apply_id, visit_time, visit_result, remark |
| donate_record | 捐赠记录表 | user_id, amount, donate_type, message |
| notice_info | 公告表 | title, content, publish_status |
3.1 宠物档案表是整个系统的信息底座
pet_info表几乎关联了所有业务:救助申请通过后,先在它里面插入一条档案;领养申请要关联它的pet_id;前台首页展示的是它的列表;管理员改的是它的状态字段。
字段设计的几个注意点:
- 品种信息用
category_id关联分类表,不要直接存"猫/狗"字符串。如果以后要加"兔""鸟"等分类,只改分类表就行,不用改代码和主子表数据。 - 绝育状态、疫苗状态用
tinyint的 0/1 表示,不要用字符串"是/否",省空间、查询快,前端再映射成文字展示。 status字段定义宠物当前状态:0待安置、1可领养、2已领养、3暂不可领养。这个状态会驱动前端按钮的显示逻辑,比如状态为 1 时才显示"申请领养"按钮。
健康描述health_status这种字段用text类型,因为实际填写内容长短不定,VARCHAR(255) 很容易截断。
3.2 申请与审核表要能回溯业务过程
rescue_apply和adopt_apply是两条核心申请链路,两者都建议保留status字段,值统一约定:0待审核、1通过、2驳回。关联用户用user_id,关联宠物用pet_id。
这里有一个设计细节:领养申请表里必须保存提交时的申请人填写内容,比如家庭情况、养宠经验、居住地址,不能只存一个user_id再实时去查用户表。原因很简单,用户表里的信息可能之后会改,而申请内容作为一次业务行为的快照,必须原样保留。这也是很多初学者容易忽略的地方——只顾着减少冗余,把不该冗余的表也做了冗余。
回访记录review_record是加分项。审核通过之后还需要线下回访,把回访时间和结果写入这张表,一方面让流程更完整,另一方面在答辩时可以强调"我不仅做了审核,还做了审核之后的线下闭环验证",这个细节比泛泛的增删改查有说服力得多。
3.3 通用字段为什么必须加
create_time、update_time这两个字段在业务上太常用了:公告按发布时间排序、宠物列表按建档时间倒序、审核按提交时间排序。MySQL 8 里直接用DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP维护,完全不需要代码里手动填时间。
deleted逻辑删除字段我在前面说过,再补充一个实践细节:在 MyBatis 的 XML 中,每个查询语句都要记得加deleted = 0条件,或者用拦截器做全局处理。如果只删数据不改查询,删除之后页面还是能看到旧数据,这是最容易翻车的点。
4. 从接口到页面:鉴权、跨域和典型功能实现
前后端分离项目的关键难点不在某个页面的布局,而在三个基础设施:登录鉴权、跨域处理、接口约定。这三个东西做好了,业务开发就是往这套框架里填内容。
4.1 登录鉴权的完整链路
这套源码采用 JWT 做无状态认证,流程如下:
- 前端把用户名和密码通过 POST JSON 提交到
/api/auth/login。 - 后端校验用户密码(密码存储用 BCrypt 加密,不能存明文),校验通过后生成 JWT,token 里放入
userId、username、roleType三个关键信息,返回给前端。 - 前端把 token 存在
localStorage,Axios 请求拦截器在每次请求的 Header 里加上Authorization: Bearer <token>。 - 后端自定义拦截器统一校验 token,解析失败或过期直接返回 401,不触发业务代码。
- 前端 Vue Router 导航守卫里检查登录状态,未登录跳转到登录页。
token 里放roleType的核心接口代码大概长这样:
// JwtUtil 生成token,有效期2小时 String token = Jwts.builder() .setSubject(userId.toString()) .claim("username", username) .claim("roleType", roleType) .setExpiration(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();拦截器校验逻辑里特别提醒一下:放行白名单要有登录接口、注册接口、前台宠物列表和详情接口,其余接口全部要求 token。但前台宠物列表是公开数据,不用登录也能看,这样才能让游客先浏览再注册。放行接口列表用配置维护,方便调整。
4.2 一个典型功能拆解:宠物列表分页查询
以最常用的宠物列表功能为例,完整串一遍前后端怎么做。
后端 Controller:
@GetMapping("/api/pet/page") public Result<PageInfo<PetVO>> page( @RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "12") int pageSize, @RequestParam(required = false) String keyword, @RequestParam(required = false) Integer categoryId, @RequestParam(required = false) Integer status) { PageHelper.startPage(pageNum, pageSize); List<PetVO> petList = petMapper.selectPetPage(keyword, categoryId, status); return Result.success(new PageInfo<>(petList)); }统一返回结构Result约定为code、message、data三部分。code=200表示成功,500表示业务失败,401表示未认证,这样前端的响应拦截器可以根据 code 统一弹提示或者跳转登录。
前端接口封装:
// src/api/pet.js import request from '@/utils/request' export function getPetPage(params) { return request({ url: '/api/pet/page', method: 'get', params }) }Axios 实例统一配置baseURL和超时时间,响应拦截器里对 HTTP 状态码和业务 code 做统一处理。这样每个页面只需要关心自己的数据,错误处理写一遍就行。
4.3 跨域问题,三种解决方式对比
前后端分离开发最烦的就是跨域。本地开发时 Vite 默认跑在 5173 端口,后端跑在 8080 端口,浏览器直接请求必然跨域。我先后用过三种方案:
| 方案 | 配置位置 | 适用场景 | 缺点 |
|---|---|---|---|
| 后端 @CrossOrigin 注解 | 每个 Controller 或方法上 | 调试单个接口 | 代码冗余,分散难维护 |
| 后端全局配置类 | WebMvcConfigurer 实现 | 前后端彻底分开部署 | 生产环境暴露端口,需要配合具体域名 |
| Vite proxy 代理 | vite.config.js | 本地开发调试 | 只适用于开发阶段 |
我实际推荐的是开发阶段用 Vite proxy,生产部署后前端构建产物直接放进后端静态目录,由后端的 Tomcat 统一提供服务和接口,同源根本不存在跨域问题。但为了以防万一,后端也保留了全局 CORS 配置,双保险。
5. 源码落地的打包部署:这些坑我替你踩过了
从源码到真正跑起来,中间隔着一堆环境问题。我把自己实际部署时遇到的坑列出来,你按顺序操作能省不少时间。
环境准备清单:JDK 8 或 11、Maven 3.6+、Node.js 16+、MySQL 8.0+、Navicat 或命令行工具。注意不要用 JDK 17 及以上版本配旧版 Lombok,会有编译问题,用 8 或 11 最稳。
5.1 数据库初始化最容易翻车的地方
拿到源码后第一件事不是启动后端,而是建库。在 Navicat 里新建数据库,字符集一定选utf8mb4,排序规则选utf8mb4_general_ci或utf8mb4_unicode_ci。如果用默认的latin1,中文写入会变成乱码,这是新手最常见的翻车点。
导入 SQL 脚本前,检查脚本文件编码为 UTF-8。Windows 下用记事本编辑过的 SQL 文件经常是 GBK 编码,导入后中文全部变问号,排查起来很难受。
后端application.yml数据库连接配置里,MySQL 8 的驱动类是com.mysql.cj.jdbc.Driver,不是老旧的com.mysql.jdbc.Driver。连接串上建议加上时区参数:
spring: datasource: url: jdbc:mysql://localhost:3306/rescue_platform?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: your_password driver-class-name: com.mysql.cj.jdbc.DriveruseSSL=false不要删,否则连接时会有 SSL 报错;serverTimezone不加,日期时间字段会差 8 小时。这两个坑我都在实际部署时碰到过,属于配置里最经典的坑位。
5.2 前端构建产物合并进后端的两种方式
前后端分离项目部署有两种做法,我推荐第二种,省心很多。
方式一:前端打包后部署在 Nginx,后端独立部署,通过 Nginx 反向代理 API 地址。这种方式适合前后端团队分离、后端接口要复用的场景,但需要额外配置 Nginx 的代理规则。
方式二:把前端打包后的dist目录整个复制到后端项目的src/main/resources/static下,后端mvn clean package打出一个包含全部页面和接口的 jar 包,java -jar一键启动。对于课设、内部管理系统和公益组织部署,这是最简单可靠的方案。
前端构建:
npm install npm run build构建完成后检查dist/index.html,确认引用的资源路径是相对路径还是绝对路径。Vite 默认base是/,打包后资源路径是/assets/xxx.js,部署到 Tomcat 根路径没问题;如果你想部署在带子路径的域名上,需要修改 Vite 的base配置。
后端打包:
mvn clean package -DskipTests打出来的 jar 在target目录下,运行:
java -jar rescue-platform.jar启动后浏览器访问http://localhost:8080就能看到整个系统,接口和页面完全同源,跨域问题不存在。
5.3 启动与验收清单
启动顺序和验收点,建议按这个清单过一遍:
- 先启动 MySQL,确认服务监听 3306 端口。Windows 下可以用
netstat -ano | findstr 3306查看端口状态。 - 导入数据库脚本,验证表数量和核心表结构。
- 启动后端 jar,观察日志无异常,Tomcat 默认端口 8080 启动成功。
- 用 Navicat 手动插入一条管理员账号,或者用脚本里的初始化账号登录后台。如果启动端口被占用,日志会报
Port 8080 was already in use,在application.yml里改端口即可。 - 打开首页,能看到宠物列表;未登录时申请领养按钮应跳转登录页;登录后提交领养申请,去后台审核,全流程走通。
验收的关键是最后一步:完整走一遍"用户提交救助→管理员审核→建立宠物档案→用户申请领养→管理员审核→录入回访"全流程。只打开页面看到布局不算跑通,业务闭环能走完才算。
6. 拿到源码之后:二次开发从哪入手,答辩怎么准备
源码是拿来用的,不是拿来收藏的。我见过太多人下载了一套项目,改个标题、换个图片就交上去了,一问业务逻辑三不知,答辩直接被问穿。拿到源码后,我建议你做三件事:读通数据表关系、改一个前端页面、加一个后端接口。
6.1 二次开发建议从这三个方向动手
方向一:给宠物档案加图片上传。目前的源码通常只存一个图片 URL 字段,你可以接入本地文件上传或对象存储服务,前端用 Element Plus 的el-upload组件,后端写一个文件上传接口,把文件保存到本地目录并把访问路径存到数据库。这个功能开发量适中,练手效果好,演示也直观。
方向二:统计数据可视化。后端写一个统计接口,按月份统计救助数量和领养数量,返回折线图数据;按宠物分类统计占比,返回饼图数据。前端用 ECharts 渲染图表。这一个功能就能让项目在提交答辩时脱颖而出,因为它展示了"项目会通过数据驱动决策"的思维。
方向三:消息通知。当救助申请审核通过或领养状态变更时,给用户发送站内信或者邮件通知。这个功能涉及异步处理和状态变更的联动,是很好的复杂度加分点。
动手顺序上,我建议从"改前端页面"和"加后端接口"并行开始,先找一个你觉得不顺眼的页面,从按钮文案、字段校验、新增筛选条件这些细处入手。改完一个页面后,你对这套代码的目录结构、数据流、接口约定都会有直观感受,比看十遍文档都管用。
6.2 答辩高频问题,提前把答案准备好
基于我这几年看课设和毕设答辩的经验,评委对这类项目最常问的问题集中在这几个方向:
| 问题 | 参考回答思路 |
|---|---|
| 为什么用 MyBatis 不用 MyBatis-Plus? | 强调 MyBatis 的 SQL 可控性,XML 便于管理复杂多表联查和 SQL 调优;MyBatis-Plus 适合单表简单 CRUD,但救助流程里有大量自定义联查和动态 SQL |
| 如何保证接口安全? | JWT 无状态认证 + 拦截器统一校验 + 角色权限区分 + 密码 BCrypt 加密存储,三层逐一说清楚 |
| 事务在哪里用到? | 提交救助申请时插入申请表和图片记录,审核通过时更新申请状态并创建宠物档案,用 @Transactional 保证原子性 |
| 数据库表为什么这样设计? | 分类表与宠物表分离避免冗余,申请表单独存储快照数据,逻辑删除字段保留历史轨迹 |
| 前后端怎么联调? | 本地开发用 Vite proxy 代理,生产环境前端构建产物放入后端静态目录,同源部署消除跨域 |
| 如果用户量大了怎么办? | 分页查询已接入 PageHelper;后续可按活动高峰流量引入 Redis 缓存宠物列表热点数据,MySQL 方面可以增加索引、读写分离 |
提前把这些问题想清楚,答辩的时候心里有底,因为这都是真实项目的实际取舍,不是你背出来的八股文。
最后说点实在的。我整理这套源码时反复验证过多次,最深的感受是:前后端分离项目跑通不难,难的是把一条真实业务链路完整落地。你在部署和二次开发过程中遇到的每一个报错,其实都是在帮你加深对这套技术栈的理解。我个人建议你从数据库脚本开始读起,把表结构和业务字段吃透,再去看 Controller 和 Mapper,最后通读前端页面。顺着这条线读完,你不仅能回答"这个项目怎么跑起来",更能回答"这个项目为什么这么设计",这才是源码真正给你带来的东西。