基于Spring Boot+Vue的流浪猫狗救助系统开发实战

发布时间:2026/9/14 12:42:34
基于Spring Boot+Vue的流浪猫狗救助系统开发实战 第一次看到“基于Spring BootVue的流浪猫狗救助系统”这个题目很多人第一反应是又一个毕业设计。但如果你真的去流浪动物救助站蹲上一天会发现救助站缺的不只是猫粮狗粮更缺一套能把救助、领养、回访、捐赠串起来的工具。志愿者用Excel登记动物信息领养申请在群里接龙猫狗照片散落在每个人的手机相册里——救助系统要解决的正是这些真实又琐碎的痛点。基于Spring BootVue做一套流浪猫狗救助系统不是为了炫技而是用尽量低的成本帮救助站把信息流跑通。这个系统能做哪些事简单来说动物一进站就录入档案待领养列表在前端页面动态展示领养人线上提交申请管理员审核后签订协议再按计划回访全过程可追溯顺手还可以管志愿者排班、物资捐赠和公告发布。它适合三类人一是课程设计/毕业设计需要完整项目经验的学生二是想给本地救助站做一套内部工具的开发者三是想了解前后端分离项目完整流程的初学者。下面我会按真实开发顺序把技术选型、数据库设计、联调坑点、部署上线逐个说清楚。1. 流浪猫狗救助凭什么用Spring Boot Vue1.1 这个系统到底要解决什么问题先还原一个典型场景周末早上有人在路边发现一只被遗弃的小猫拍了张照片发到本地动物救助群。管理员看到消息后先在群公告里记一笔再联系志愿者把猫送到合作宠物医院。小猫治疗两周后回到救助站管理员要给它拍新照片、更新疫苗信息、写领养文案发布到朋友圈和公众号。接着陆续有人来询问有人想领养。管理员需要翻聊天记录确认有没有人先问了安排线下看猫最终签一份手写的领养协议约定三个月后回访。这套流程里信息散落在微信群、Excel表、纸质协议和手机相册中。一旦动物数量超过三四十只问题就来了某只猫疫苗打没打哪位志愿者负责跟进上个月领养出去的小狗该回访了没捐赠的猫粮还剩多少这些问题在聊天记录里根本翻不到只能在多个表格间来回切换费时费力还容易漏。流浪猫狗救助系统的核心价值就是把这个链条数字化。动物登记、医疗记录、在站状态、领养申请、协议签署、回访计划全部在同一套系统里流转。每一个状态变化都有时间记录每一个待办事项都有负责人。对于救助站来说这比一个花哨的前端页面重要得多。1.2 技术选型不是跟风是算过账的为什么选Spring Boot Vue而不是其他组合我是从开发效率、运行成本、维护难度、资料丰富度四个维度算过这笔账的。开发效率方面Spring Boot的最大优势是自动配置和内置Tomcat。你不需要像传统SSM那样写一堆XML配置一个Spring Boot应用写个main方法就能跑起来。加上MyBatis-Plus这类增强工具单表的增删改查几乎不用写SQL。前端Vue的组件化开发配合Element UI/Element Plus组件库后台管理页面基本是拼积木。救助系统这种典型的管理类应用用这套组合能在很短时间内搭出完整骨架。运行成本和维护方面救助站不是高并发场景用户量可能就几百人数据量也难以达到百万级。单体应用完全够用不需要一上来就拆微服务、上消息队列。Java生态稳定社区庞大哪怕几年后接手的人不是最初的开发者也能在网上找到大量资料。所有框架和组件都是开源的服务器只需要一台普通的云主机整体成本非常可控。资料丰富度也很重要。搜一下“springboot”“vue”相关教程、面试题、开源项目、踩坑笔记铺天盖地。这意味着开发过程中遇到问题基本都能搜到解决方案。对于学生党或者刚入行的开发者这是隐形成本但往往是决定项目能否顺利交付的关键。1.3 同类方案对比为什么不用PHP或纯JSP我见过不少救助站的老系统是用传统JSP做的也有用PHP的甚至还有用Access数据库的。它们都能跑但维护起来各有各的痛。技术方案前后端分离开发效率长期维护适用场景Spring Boot Vue是高好中小型管理系统、前后端分离应用传统 JSP/Servlet否中差老项目遗留、简单页面渲染PHP可以但少见中高一般内容站、论坛、快速原型Python Django可以高好数据分析和Web应用国内岗位相对少传统JSP是前后端耦合的模式Java代码和HTML混在一起。改一个按钮样式可能要重启整个应用前端和后端开发者也很难并行工作。PHP起步快但很多救助站找的兼职维护人员不熟悉PHP生态时间一长就没人敢动老代码。Django本身很优秀Python写起来也舒服但国内Java方向的招聘岗位和应用案例明显更多学生做完这个项目简历上的技术栈也更贴近市场需求。Spring Boot Vue这套组合最大的好处是前后端通过Restful接口通信前端只管页面后端只管数据。救助系统要求界面灵活、需要不断调整领养流程和展示样式这种分离模式改起来非常舒服。2. 救助系统最核心的不是代码是业务状态流转2.1 从领养流程反推出来的角色权限很多新手拿到这个题目第一件事就是建表写CRUD。但我建议先想清楚谁会使用这个系统每个角色能做什么从领养流程来看至少需要这几类身份。普通访客可以浏览动物列表、查看领养须知、联系救助站但不需要登录。领养申请人需要注册登录提交领养申请查看申请进度签署电子协议。志愿者/录入员负责录入救助记录、维护动物档案、更新疫苗信息、上传照片。管理员审核领养申请、管理用户、管理公告、分配回访任务、查看捐赠数据。超级管理员管理员以外的系统配置比如角色分配、数据备份。为什么权限要按角色来设计而不是直接给每个人开不同功能因为救助站志愿者的流动性非常大一个人可能这个月是录入员下个月变成管理员甚至同时身兼多个角色。如果给每个用户单配权限管理员光维护权限就要累死。用RBAC基于角色的访问控制模型把权限挂在角色上用户和角色多对多关联换角色只需改关联关系方便得多。2.2 数据库表设计一张领养申请单要关联多少数据救助系统的核心实体包括用户、角色、动物、领养申请、救助记录、捐赠记录、回访计划。下面是一套直接用过的表结构参考总共七张核心表。表名关键字段说明sys_userid, username, password, phone, avatar, status, deleted, create_time用户表密码存BCrypt哈希sys_roleid, role_code, role_name角色表code用于后端判断sys_user_roleuser_id, role_id用户角色关联表animalid, name, breed, gender, neutered, vaccinated, health_status, photo_url, area, status, create_by, deleted, create_time动物档案表adopt_applicationid, user_id, animal_id, apply_reason, status, auditor_id, audit_time, audit_opinion, agreement_url, create_time领养申请表rescue_recordid, animal_id, rescuer_name, rescuer_phone, location, rescue_time, injury_desc, handle_result, create_time救助记录表donationid, user_id, donation_type, amount, material_desc, donation_time捐赠记录表visit_planid, application_id, plan_time, actual_time, visitor_id, result, remark回访计划表设计时有几个容易忽略的细节。动物表里不要直接存“领养人ID”因为领养人和动物之间的关系是动态的应该通过adopt_application表去关联。一只动物可能被申请多次只有审核通过的那一条才有效存冗余字段会导致数据不一致。捐赠记录允许匿名所以user_id允许为空用donation_type区分资金还是物资。回访计划和领养申请关联而不是和动物关联因为一只动物被领养后可能换主人每一段领养关系都需要独立的回访记录。每张表都要带deleted逻辑删除标记和create_time创建时间这是管理系统的标配。状态字段统一用int类型存储不要在数据库里直接写中文方便代码里做枚举判断也避免中文乱串。2.3 状态机设计让动物从“待领养”到“已回访”不留死角状态流转是这个项目里最有业务价值的部分。一只流浪动物进入系统后状态可能经历这样一条链路待审核 - 待领养 - 申请中 - 待签订 - 已领养 - 回访中 - 完成另外一个分支是“暂停领养”或“已离世”用于动物生病治疗或不幸去世的场景。为什么一定要定义清晰的状态机因为领养申请不是简单的“提交-通过-结束”。管理员审核通过后还需要领养人到线下签协议签完协议动物才真正离开救助站离开之后还要定期回访。如果状态随便传前端把“已签约”的动物又展示在待领养列表里救助站就会收到大量无效申请。核心代码里要做一个状态校验不能用前端传来的状态作为唯一依据。比如提交领养申请时后端必须判断当前animal的status是不是“待领养”并且没有其他审核中的申请。我建议用“乐观锁”思路直接用一条更新语句抢占状态UPDATE animal SET status 2 WHERE id ? AND status 1如果影响行数为0说明动物已经被其他人申请了直接返回“该动物已被申请”。这种写法比自己查一次再更新一次更安全避免两个用户同时提交申请时产生并发冲突。在实际项目中我会把状态定义成枚举类或常量类集中管理禁止前端传字符串状态。后端通过统一的校验方法检查当前状态和前置状态。这样即使以后业务流程调整也只需要改一个地方。3. 前后端联调中真正消耗时间的四个问题3.1 接口协议统一返回体、错误码与分页格式前后端分离项目里最怕的就是每个接口返回格式都不一样。有的接口返回{code:200, message:success, data:...}有的接口直接返回一个数组前端没法统一处理只能每家单独适配。所以第一步是统一返回体。我习惯用这样一个结构public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }code为200表示成功401表示未登录403表示无权限500表示服务器异常。前端在Axios响应拦截器里统一处理service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 系统异常) if (res.code 401) { router.push(/login) } return Promise.reject(new Error(res.message)) } return res }, error { Message.error(error.response?.data?.message || 网络错误) return Promise.reject(error) } )分页接口也要统一MyBatis-Plus的Page对象默认带records和total字段直接放到Result的data里返回。前端从res.data.records和res.data.total取值展示表格和分页器。这套协议一旦定下来前后端都能快速推进。3.2 登录鉴权JWT令牌放在哪里才安全救助系统的领养申请和回访记录涉及用户身份信息必须做登录鉴权。我用的是Spring Boot JWT的方案理由很简单无状态、好扩展、前端也好处理。流程是用户提交用户名密码后端校验通过后生成一个token返回给前端。前端把token存在localStorage里每次请求在请求头带上Authorization: Bearer token。后端通过过滤器或拦截器解析token拿到当前用户ID和角色。JWT本身由Header、Payload和Signature三部分组成服务端不需要存储会话状态。但要注意几点。第一不要把密码、身份证号这类敏感信息放进JWT的Payload它只是Base64编码不是加密任何人都能解码看到。第二token过期时间不要设置太长救助站这种场景建议2小时左右过期后前端收到401让用户重新登录。第三尽量使用jjwt这类成熟库不要自己拼签名。Spring Boot侧我习惯用一个拦截器统一处理Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } if (!JwtUtil.verify(token)) { response.setStatus(401); return false; } return true; } }然后注册拦截器放行登录接口和静态资源路径。这个方案比Spring Security省不少配置又能满足前后端分离的需求。3.3 图片上传本地存储与访问映射的经典坑动物照片上传是这个系统绕不开的功能。前端用Element UI的Upload组件后端接收MultipartFile保存到服务器本地目录。看起来简单但几乎每个第一次做前后端分离的人都会踩同一个坑文件确实上传成功了数据库里也存了路径但用img src/upload/xxx.jpg访问时就是404。原因是Spring Boot默认的静态资源映射只覆盖classpath:/static你上传到磁盘的目录并没有被映射为可访问路径。解决办法是在配置类里手动添加资源映射Configuration public class WebConfig implements WebMvcConfigurer { Value(${file.upload-path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }比如uploadPath配置为/usr/local/rescue/upload那么前端访问/upload/20250301/cat.jpg时后端会映射到/usr/local/rescue/upload/20250301/cat.jpg。生产环境里我还会用Nginx单独托管upload目录这样后端就不用承担静态文件读取的压力图片访问也更稳定。上传路径一定要带上日期分组否则时间一长目录里全是文件找起来能崩溃。3.4 跨域与代理开发环境用proxy生产环境靠Nginx前后端分离一定会遇到跨域问题。前端跑在http://localhost:8080后端跑在http://localhost:8081浏览器的同源策略会把请求拦截。开发环境最简单的解法是Vue CLI的proxy代理在vue.config.js里配置module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } }这样前端发axios.post(/api/login)时Vue开发服务器会把请求转发到http://localhost:8081/api/login浏览器根本感知不到跨域。注意后端接口统一加/api前缀方便proxy匹配。生产环境部署时一般是前端打包成dist目录由Nginx托管80端口同时Nginx把/api下的请求反向代理到后端服务端口。后端不需要配置CORS因为浏览器访问的是Nginx的域名Nginx和后端之间属于服务端通信不存在跨域问题。不要在Spring Boot里写CrossOrigin(origins *)放开所有跨域来源尤其是生产环境。这种写法等于允许任何网站通过你的浏览器端调用接口容易引发安全风险。4. 手把手从零搭建这套系统踩坑版4.1 环境准备IDEA创建Spring Boot项目版本别追新先准备环境JDK 8或JDK 11、Maven 3.6、IDEA社区版或专业版。打开IDEA新建工程时选择Spring Initializr然后填写Group和Artifact。版本这里我要特别强调Spring Boot建议选择2.7.xJava版本选8。现在官网默认已经是Spring Boot 3.x甚至更高如果你直接默认创建会遇到几个很烦的问题Spring Boot 3.x要求JDK 17以上老电脑装新版JDK可能带不动javax.servlet命名空间换成了jakarta.servlet网上90%的老教程代码直接报错很多第三方依赖还没跟上新版本整合起来泪流满面。核心依赖这样加dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency 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 groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency创建完工程后先跑一个测试接口确认Spring Boot能启动再继续往下写业务。别一上来就写一堆代码最后连启动都启动不了排查半天才发现是版本问题。4.2 Vue工程初始化与依赖安装前端用Vue CLI创建工程。如果你是按网上主流教程学习我建议使用Vue 2 Element UI不是因为它比Vue 3先进而是因为相关教程最多出了问题最容易搜到答案。Vue 3 Vite Element Plus也很好适合已经熟练的开发者。创建命令npm install -g vue/cli vue create rescue-frontend cd rescue-frontend npm install axios vue-router3 vuex3 element-ui npm run serve两点提醒。第一Node.js版本不要随便追新建议用14/16/18这些长期支持版本否则老项目里node-sass这类依赖会编译报错。第二npm安装依赖慢是很常见的问题可以先设置淘宝镜像npm config set registry https://registry.npmmirror.comElement UI安装后在main.js里全局注册然后按需建立views目录登录页、动物列表页、动物详情页、领养申请页、后台管理页等。项目初期不需要把所有页面都做出来先把路由和布局框架搭好能跑通全流程最重要。4.3 后端代码生成与业务封装写CRUD前先想清楚一件事用MyBatis-Plus的代码生成器可以快速生成entity、mapper、service、controller基础代码这没问题。但我要提醒你生成的controller只是基本的CRUD不能直接用于业务。领养申请提交时的状态抢占、动物状态变更时的流转校验、志愿者只能修改自己录入的档案这些业务逻辑必须写在service层。以“提交领养申请”为例核心逻辑至少包括三步检查动物是否处于可领养状态、检查用户是否有未完成的申请、插入申请记录并更新动物状态。我推荐用事务加状态更新的方式Transactional(rollbackFor Exception.class) public boolean submitApplication(AdoptApplication application) { // 抢占动物状态 int update animalMapper.updateStatusByIdAndStatus( application.getAnimalId(), 1, 2); if (update 0) { throw new RuntimeException(该动物已被申请请选择其他毛孩子); } application.setStatus(1); // 申请中 return adoptApplicationMapper.insert(application) 0; }这里的关键是先更新动物状态再插入申请记录。如果先插入申请再改状态两个请求同时进来时可能都通过了状态检查逻辑就乱了。我见过很多人把业务逻辑全写在controller里controller几十行起步service层空荡荡。一旦页面增加一个字段前后端都要改半天。正确的做法是controller只负责参数接收和权限校验业务判断都下沉到service。4.4 联调冲刺从登录接口到第一个领养申请跑通前后端都搭好后联调阶段最容易出问题。建议按这个顺序跑通核心链路登录 - 获取动物列表 - 提交领养申请 - 管理员审核。首先准备一个前端api模块封装axios实例统一加token// api/request.js import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) export default service然后登录页面调用/auth/login接口后端返回token。拿到token后存localStorage再请求/animal/list获取动物列表。提交申请时调用/adopt/apply。管理员在后台看到申请后审核通过或驳回。联调遇到的绝大多数问题可以归为五类接口路径不一致、参数名大小写不对、日期格式不统一、前端没带token、后端分页格式和前端预期不一样。建议使用Swagger或knife4j生成接口文档后端自测通过后再让前端对接。我自己习惯前端代码还没开写时先用Apifox把接口跑一遍确认返回结构没问题再给前端对接能省一半联调时间。5. 离真正能用还差一步打包、部署与数据安全5.1 Spring Boot打包与启动参数开发环境跑通后下一步是部署到服务器。后端打包mvn clean package -DskipTests打包完成后target目录下会生成一个jar包传到服务器后启动nohup java -jar rescue-backend-0.0.1-SNAPSHOT.jar \ --spring.profiles.activeprod app.log 21 --spring.profiles.activeprod会加载application-prod.yml里的配置。生产环境和本地环境必须分开本地数据库连接、日志级别、文件上传路径都不同。切换环境只需改一个参数不用改代码重新打包。生产环境配置里数据库连接池参数要关注比如HikariCP的maximum-pool-size设置成20左右就够了不用太大。JVM参数也可以限制一下老服务器2G内存时建议java -Xms256m -Xmx512m -jar rescue-backend.jar否则一个Spring Boot应用启动就要占掉1G多内存服务器上再跑Nginx和MySQL就非常紧张了。5.2 Vue项目打包与Nginx反代配置前端打包非常简单npm run build执行后会在dist目录生成静态文件把这些文件传到服务器然后用Nginx托管。一个完整的server配置如下server { listen 80; server_name your-domain.com; gzip on; gzip_types text/plain text/css application/json application/javascript; root /usr/local/rescue/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081; 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 /upload/ { alias /usr/local/rescue/upload/; } }这里最关键的是try_files $uri $uri/ /index.html;因为前端用的是history路由如果少了这一行刷新子页面时Nginx找不到对应的文件会报404。另一个注意点proxy_pass后面的URL不要带/api要让请求原样转发到后端的/api前缀上。比如前端请求/api/loginNginx转发到后端就是http://127.0.0.1:8081/api/login和后端Controller的映射保持一致。5.3 数据安全备份、脱敏与操作日志系统能跑起来只是第一步数据安全才是长期运营的基石。领养人提交的申请里可能包含手机号、住址、身份证照片这些都不能裸奔。密码必须用BCrypt加密存储Spring Security的BCryptPasswordEncoder直接可用前端永远拿不到明文密码。数据库里存的是哈希即使被拖库也不会直接泄露密码。数据库要定期备份写个crontab脚本最简单0 2 * * * mysqldump -uroot -pXXXX rescue_db /backup/rescue_db_$(date \%F).sql备份文件保留最近30天即可。接口层要防SQL注入MyBatis-Plus的QueryWrapper和LambdaQueryWrapper默认使用预编译基本能防。但要避免在数据库查询中直接拼接字符串尤其是排序字段、动态表名这类地方。手机号等敏感信息在列表页要做脱敏显示比如138****1234。字段不脱敏管理员无意识把列表截图发到工作群也是风险。最后用AOP切面记录操作日志把“谁在什么时间审核了哪个申请”“谁修改了哪只动物档案”存下来。救助站志愿者流动大一旦出现问题日志能快速定位到人也能防止管理员权限被滥用。日志里需要记录操作人、操作类型、接口路径、请求参数、耗时和IP地址。小项目不用上ELKMySQL里建一张日志表定时清理过期的日志就够了。如果你打算正式对外使用域名加HTTPS证书是必须的。现在免费的SSL证书很多Nginx配置好证书后浏览器地址栏的小锁能给用户多一层信任感。最后说点开发之外的话。我见过太多人把这个题目当成纯CRUD练习结果做完发现除了表结构多一点和图书管理系统没区别。真正让这个系统有价值的地方是把救助站的工作流程理清楚了。如果你准备做类似项目我建议先花两天时间泡在救助站或者找志愿者聊一聊看看他们每天做决策时最缺什么信息。我当初就是因为在群里看到志愿者反复问“这只猫疫苗打了吗”“预定领养的人回访了吗”才意识到待办提醒和回访计划比列表展示重要得多。后面的开发方向可以再考虑对接微信公众号、接入地图展示救助点但前提是先把核心流程跑顺。