
代码这东西有时候光看文档是学不进去的必须得有个具体的项目抓手才行。之前一直在折腾 Java 后端的东西Spring Boot 玩得还算溜但前端一直是我的短板Vue 只是停留在“能看懂”的程度。直到我下决心做了这个“基于 SpringBoot Vue 的动物领养平台管理系统”才算是把前后端分离开发的整条链路给彻底打通了。今天就把这个项目的设计与实现过程完整写出来包括技术选型的心路历程、数据库怎么设计、核心代码怎么写、还有我踩过的一堆坑希望能给正在做类似练手项目或者毕业设计的同学一点参考。这个系统说白了就是一个连接“想领养宠物的人”和“待领养动物”的线上平台管理员可以发布动物信息、审核用户的领养申请普通用户能浏览动物列表、查看详情、在线提交领养申请还能在个人中心管理自己的申请记录。技术上就是标准的 Java 全栈组合——SpringBoot Vue MySQL MyBatis没有引入太复杂的东西但前后端联调、权限控制、文件上传这些实战中一定会遇到的场景全都有。1. 项目整体设计与技术选型思路先说为什么选这套技术栈而不是别的方案。如果你是一个人在开发这种管理系统最忌讳的就是在技术选型上追求“大而全”或者“新而炫”把微服务、分布式、容器编排全上来了结果业务还没写几行就得先折腾架构项目基本就烂尾了。SpringBoot Vue 这套组合在中小型管理系统里几乎是事实标准后端开发效率高、生态成熟前端组件化开发上手快最关键的是遇到任何问题都容易搜到解决方案。1.1 后端核心框架选型SpringBoot 的取舍市面上 Java 后端框架选项不少SSHStruts2 Spring Hibernate已经过时了SpringMVC JSP 的传统单体方案开发体验太差而 SpringBoot 则把配置简化到了极致。它内嵌 Tomcat打成一个 jar 包就能直接跑不用单独部署 Web 容器这对本地开发调试来说体验提升是颠覆性的。有一点提醒一下想用新版本的朋友现在 SpringBoot 3.x 已经出来很久了但它基于 JDK 17并且把javax包名改成了jakarta很多老教程和老配置都会失效。我自己做这个项目的时候选的是SpringBoot 2.7.x JDK 8/11别看版本“老”这是目前整个生态兼容性最稳的一个组合主流教程、面试题、公司旧项目基本都扎堆在这个版本区间。如果你只是为了把系统做出来跑通别追求版本最新不然光一个spring-boot-starter-parent版本号冲突就能让你耗掉一晚上。1.2 持久层方案为什么是 MyBatis 而不是 JPA说句实话当初在 MyBatis 和 Spring Data JPA 之间我也摇摆过一阵。JPA 的自动建表、CRUD 全部交给框架代码量确实少但一旦查询条件复杂起来或者需要多表关联加上各种动态条件JPA 的 Query DSL 和 Query 注解就会让 SQL 变得很别扭而且出了问题极难排查。MyBatis 的思路就非常纯粹SQL 是你自己写的每一个查询都在你的完全掌控之下。比如动物的列表页我需要根据物种、年龄、性别、是否已领养等多个条件组合筛选搜狗搜索这种查询用 MyBatis 动态 SQL 拼条件就是几分钟的事用 JPA 去拼 Specification 就没那么直观了。另外MyBatis 的 SQL 复用、缓存机制这些面试常考的点你在做项目的过程中能亲手摸一遍对理解框架底层也很有帮助。1.3 前端方案Vue 2 Element UI 的稳定组合前端我选了 Vue 2配合 Vue Router、Vuex 和 AxiosUI 组件库配套用 Element UI。现在 Vue 3 和 Element Plus 当然是主流但 Vue 2 的生态成熟度和中文资料量依然是最庞大的。特别是对于后端开发者来说你的主要精力应该花在业务逻辑和数据交互上而不是花一整天去调前端框架的兼容性问题。Element UI 的表格、表单、弹窗、上传组件都是封装好的拖过来改改就能用能帮你把大量的前端开发时间压缩到最少。这个项目的前端虽然看起来页面不少但真正需要手写样式的其实没几处从“前端劝退”到“能独立写页面”靠的就是这种成熟的组件库。1.4 功能模块与角色权限梳理这个系统的用户角色非常简单就两类管理员和普通用户。设计权限之前一定要先把业务场景想清楚别一上来就整 RBAC 那套多角色多权限的模型项目复杂度会成倍上升。管理端和用户端我拆分成了两个不同的登录入口和路由空间登录时根据用户的role字段做跳转这样做最直观也最不容易出逻辑混乱。整个系统的核心模块我大致框定为这几块用户管理注册、登录、个人信息修改、动物管理发布动物、编辑、下架、领养申请管理提交申请、审核、状态流转、系统公告管理以及管理员后台的登录首页数据统计。如果把这些模块全部实现完你的 CRUD 熟练度、关联查询能力、状态流转设计能力基本都能覆盖到了。2. 核心功能模块与数据库设计要点很多新手拿到项目需求后第一件事就是写代码这是本末倒置的。一个系统能不能撑得住业务扩展数据库表设计占了七成。我把所有需求在纸面上画成表结构图反反复复改了三版才开始动手写代码后来的开发效率出奇的高——因为表结构清楚了业务逻辑的底子就牢了。2.1 数据表结构全景解析这个系统我一共建了六张核心表不算多但足够覆盖所有业务场景。每张表都必须在建表之初就预留必要的字段比如常用的create_time、update_time不然后面做列表排序和操作日志的时候你就会发现源头数据缺失了。CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名/登录账号, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, nickname varchar(50) DEFAULT NULL COMMENT 昵称, phone varchar(20) DEFAULT NULL COMMENT 联系电话, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, role tinyint(1) NOT NULL DEFAULT 0 COMMENT 角色0普通用户 1管理员, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;动物表animal是系统的主数据表字段包括动物名称、种类猫/狗/其他、品种、性别、年龄、毛色、是否绝育、是否打过疫苗、健康状态描述、所在城市、封面图片路径、领养状态0待领养/1已领养/2已下架、性格描述等等。这里有一个业务细节值得注意“已领养”状态不应该直接由用户或管理员手动去改而是由领养申请审核通过这个行为自动触发这样能保证整条操作链路的逻辑自洽。领养申请表adoption_application是连接用户和动物的桥梁也是整个系统业务逻辑最重的表。核心字段包括user_id、animal_id、apply_reason申请理由这个字段对审核人做判断至关重要、status0待审核/1已通过/2已拒绝、create_time、update_time。为了避免同一个用户对同一只动物重复提交申请这里我还加了user_id animal_id的联合唯一索引。其余还有系统管理员发布的公告表notice、用户与管理员交流的留言表message以及后续扩展可能需要用到的操作日志表log。建表时统一使用 InnoDB 引擎和 utf8mb4 字符集前者支持事务后者能存下生僻字和表情符号对中文业务系统来说这两点都是底线。2.2 领养状态机的设计保证业务不乱的关键别看这是个练手级项目领养业务中“状态”的流转反而最考验设计功底。如果只是简单地把状态字段设置为可随意改写的数字任何一步操作都能直接把它改得乱七八糟——比如用户一提交申请管理员还没审核呢动物就被标成了“已领养”这明显是不合理的。我采用的方案是对状态的操作做接口级约束用户提交领养申请时后端只允许创建status 0待审核的申请记录并且把这个动物状态改为“审核中”借用原业务的2号状态对应“申请中”此时动物在前端列表中的展示要带标签但不可重复申请管理员点击“通过审核”后端事务里同时更新申请记录状态为 1 和动物状态为 1已领养确保要么都成功、要么都失败点击“拒绝”则只把申请状态改成 2动物状态回归待领养。这一段逻辑里用到了Transactional事务注解正好也是个很好的练手点。2.3 接口设计的 Restful 风格约定前后端分离的项目接口约定直接决定了联调是否顺畅。我定义了统一返回体Result包含code200成功/500失败/401未认证、msg、data三个字段。所有的接口路径都遵守/api/**开头后端接口分成/api/admin/**需要管理员权限和/api/user/**登录用户可访问两组再配合拦截器统一做登录校验逻辑非常清晰。举个例子动物列表查询的接口设计为GET /api/animal/list?pageNum1pageSize10typecatstatus0这种条件查询 分页的写法是后端接口最标准的形态。前端通过 axios 封装的实例统一在请求头里带token后端接口通过拦截器去解析校验这个token这套流程走到位了你就会发现给系统加任何新功能都变得很顺滑。3. 后端核心实现与关键代码深度拆解数据库搞定之后就要开始写真正的业务代码了。这一部分我把后端里面最具代表性的几个环节拿出来拆开讲——登录鉴权、文件上传、动态 SQL 查询、以及领养申请的审核事务。这些都是面试的时候被反复问到的点也是实际开发中出问题最多的点。3.1 注册登录模块BCrypt加密与Token鉴权用户密码直接明文存储到数据库这在实际项目中是绝对不能接受的万一数据库泄露所有用户的密码全部直接暴露。这里我用的是 Spring Security 中的BCryptPasswordEncoder它不像是 MD5 那样确定性地对同一个密码生成同一个摘要而是每次加密都会带一个随机盐即使两个人密码完全相同存到数据库里的密文也是不一样的极大地提高了安全性。// 注册时加密密码 String encodedPwd new BCryptPasswordEncoder().encode(user.getPassword()); user.setPassword(encodedPwd); // 登录时校验密码 BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); if (!encoder.matches(rawPassword, user.getPassword())) { throw new RuntimeException(用户名或密码错误); }Token 鉴权这块我没有特意去集成 JWT 框架而是用了一个更轻量的思路登录成功后服务端生成 UUID 字符串作为token以user:token:用户ID为 Key 存到 Redis没有 Redis 的时候用 ConcurrentHashMap 内存缓存也一样能跑。每次请求进来拦截器从请求头的Authorization字段里取出token并解析出用户身份校验通过才放行。因为不用处理 JWT 的过期时间、签名算法这些细节整个代码量大幅缩小而且逻辑单线程好排查。3.2 动物信息发布图片上传与静态资源映射动物领养平台最核心的信息载体就是图片用户看列表时第一眼关注的就是照片。图片上传我实现的是一个通用接口POST /api/upload/image接收 MultipartFile 文件参数然后用UUID.randomUUID()生成不重复的文件名按日期分目录存储在本地磁盘比如D:/upload/2025/xx/这样既避免了一个目录下文件过多影响性能也好做后续的定期清理。本地存储有一个容易踩坑的点前端页面上展示的是图片的 URL但这个图片不是放在后端项目的静态资源目录里的直接访问是 404。解决方案是给后端添加一个资源映射配置类把磁盘路径映射成虚拟 URL 路径比如请求/files/2025/xx/xxx.jpg时Spring Boot 自动从磁盘上对应位置读取文件返回这套配置几行代码就能写完但很多新手搞不定。3.3 动物列表的动态条件查询动物列表页用到了两个维度的筛选条件一是动物自身的属性种类、性别、年龄范围、所在城市二是用户的搜索关键词按名称模糊查询。如果每一种条件的组合都写一个独立接口你的 Controller 马上就会爆炸。MyBatis 的where标签 if标签来这里简直就是绝配select idselectAnimalList resultTypemap SELECT a.*, u.nickname AS publisher_name FROM animal a LEFT JOIN user u ON a.publish_user_id u.id where if testtype ! null and type ! AND a.type #{type} /if if testkeyword ! null and keyword ! AND a.name LIKE CONCAT(%, #{keyword}, %) /if if teststatus ! null AND a.status #{status} /if /where ORDER BY a.create_time DESC /select这里有个细节必须强调动态 SQL 拼接不等于直接String concatenationMyBatis 用的是预编译#{}占位符底层是PreparedStatement的参数绑定这样天然规避了 SQL 注入问题。如果图省事用${}去拼接传入的排序字段或者表名你把接口服务到公网以后会有无数人用 SQL 注入工具分分钟把你库里的数据拿走别问我是怎么知道的。3.4 领养申请审核的数据库事务一块完整的 OAuth 协议流程中最容易出错的是回调地址的拼接。 审核操作横跨了两个数据表必须用事务把多步写操作包起来保证任何一步失败之前的操作都自动回滚。Spring 里加个Transactional注解传播行为、隔离级别默认值就可以满足大部分情况。Transactional(rollbackFor Exception.class) public void auditAdoption(Integer applicationId, Integer auditStatus, Integer operatorId) { Application application applicationMapper.selectById(applicationId); if (application null || application.getStatus() ! 0) { throw new RuntimeException(领养申请不存在或已处理); } // 只有动物存在且仍处于待领养/申请中状态才能继续走审核逻辑 Animal animal animalMapper.selectById(application.getAnimalId()); if (animal null) { throw new RuntimeException(关联的动物不存在); } if (auditStatus 1) { application.setStatus(1); applicationMapper.updateById(application); animal.setStatus(1); animal.setAdopteeUserId(application.getUserId()); animalMapper.updateById(animal); } else if (auditStatus 2) { // 拒绝只更新申请状态把动物“申请中”状态回滚为“待领养” application.setStatus(2); applicationMapper.updateById(application); animal.setStatus(0); animalMapper.updateById(animal); } // 发送站内信/系统公告通知申请人审核结果 messageMapper.insert(new Message(...)); }事务的rollbackFor Exception.class这个细节我给个特别提醒Spring 的事务默认只在遇到运行时异常RuntimeException 和 Error时回滚如果业务方法里抛出的是自定义的普通 Exception默认是不会回滚的。代码里所有业务异常我统一继承RuntimeException这样每条规则都能被可靠地处理。4. 前端页面设计与交互流程实现后端接口全部调通了以后剩下最重的活就是把前端页面搭起来。我对前端的要求一直是“能跑就行但别太丑”Element UI 在这方面帮我解决了 70% 的样式问题。4.1 Vue 项目搭建与路由设计使用 Vue CLI 创建项目是标准的起步方式先npm install -g vue/cli安装脚手架然后执行vue create animal-frontend生成项目骨架。目录结构上我会把src里的内容按功能拆分api目录统一放 axios 请求封装views目录放页面级组件router管路由store管全局状态。路由权限是整个前端安全链路上的重要一环。虽然后端已经做了接口拦截但前端如果不做处理就会有各种诡异问题——比如用户直接手动修改 URL 跳去/admin/dashboard后端 401 返回以后页面还是白屏的。我实现的方式是在router.beforeEach全局前置守卫里检查目标路由的meta.requiresAuth结合当前 vuex 里存储的userInfo.role来判断是否能进入该路由不满足条件就next(/login)并提示无权限。管理员和普通用户的路由都是动态拼接的通过router.addRoutes实现。4.2 毛孩子列表页卡片式展示与条件筛选用户端的首页核心就是待领养动物的卡片列表。我用 Element UI 的el-rowel-col栅格布局做三列展示每张卡片上展示封面图、动物名称、主要标签种类、性别、年龄、领养状态。点击卡片跳转到详情页需要带上动物 IDVue Router 处理成/animal/detail?id123查询参数即可。查询条件我用的是el-select和el-input当条件值变化时重新请求接口把pageNum重置为 1 并且用新参数重新拉取数据。这里有一个交互细节要重点处理图片加载失败的兜底。动物图片可能来自网络图或者长期未维护的外链一旦加载失败整个卡片都会稀碎所以我对每个img标签绑定了error事件把默认图替换成本地的一张猫咪占位图这个小改动让整个页面稳定性瞬间提升了一个档次。4.3 领养申请表单前端校验到后端校验的双保险领养申请页面的核心是一个表单需要用户填写自己当前的居住情况、养宠经验、申请理由。前端我用el-form的rules属性加了必填和长度校验比如申请理由最少写 20 字理由写得太敷衍的申请会在前端直接被打回。但前端校验只是做用户体验的真正的安全边界必须落在后端校验。后端在接收申请时我会校验用户是否登录、动物是否存在、动物当前状态是否处于可申请状态0 或申请中、当前登录用户是否已经申请过这只动物。如果后端这层校验写的扎实如果未来有人避开前端直接调你的接口系统也能稳稳兜住。4.4 管理后台数据概览与审核工作台管理员的页面相对更加“后台化”。登录后跳转/admin/dashboard顶部是大数字卡片——今日新增用户数、今日新增动物数、待审核申请数、总领养成功数数据来源是一个聚合查询接口selectCountDashboard后端写几条带COUNT(*)和GROUP BY的 SQL 就能完成。管理员的动物列表复用el-table表格组件每一行都可以对动物进行“编辑 / 下架”操作。领养申请审核页则是这个平台最紧张的一块工作台表格里展示所有待审核的申请每条申请旁边有“通过”和“拒绝”两个按钮点击后弹 Dialog 确认二次确认通过后自动刷新动物状态。这里实践中有个重要经验审核操作一定不能做成在表格内直接改数据然后调 update 接口必须走后端完整的auditAdoption方法否则状态机的约束就形同虚设了。5. 环境配置、联调部署与完整实操流程一个项目哪怕代码写得再漂亮如果别人拉下来跑不起来基本等于零。我见过太多项目文档写了“请自行配置环境”这对新手来说是一句灾难。这里把整套环境搭建和部署运行的步骤完完整整写出来照着做就一定跑得起来。5.1 后端环境配置与数据库初始化后端方面我最建议用的 IDE 就是 IntelliJ IDEA装好之后直接导入 Maven 项目。JDK 8 或者 11 都行Maven 仓库建议配置阿里云镜像不然拉依赖的速度会让你怀疑人生。打开src/main/resources/application.yml需要修改的内容集中在数据源和上传路径两处server: port: 8080 servlet: context-path: / spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/animal_adoption?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的数据库密码 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case: true这个配置必须开它能自动把数据库的create_time映射成 Java 实体里的createTime省去一堆注解。每次启动项目前先用 Navicat 或者命令行执行项目里准备好的init.sql脚本建库建表再顺手往用户表里插一条管理员账号role1MD5 或者 BCrypt 加密后的密码随便填一个能登录的即可。5.2 前端环境配置与代理转发前端需要安装 Node.js14.x 以上Vue 项目启动后默认跑在 8080 端口为了跟后端错开我配置 devServer 跑在 3000 端口同时还要做一个核心配置——代理转发。前端在开发环境下请求/api开头的接口会默认请求前端服务器http://localhost:3000/api而实际上后端接口地址是http://localhost:8080/api跨域问题此时就会显现。在vue.config.js里配置devServer.proxy把/api的请求全部转发到http://localhost:8080这个配置一加上开发联调阶段就不需要后端去做 CORS 跨域放行了。devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }有一个特别隐蔽的坑后端接口返回的图片 URL 是/files/xxx.jpg这种形式前端代理如果没有配置/files的转发规则图片请求就会仍然发给 3000 端口导致 404。我的解决方案是在代理规则里再加一条/files同样指向后端这样图片在开发环境就能正常加载了。5.3 打包部署开发全部搞定之后生产环境部署的路子也非常直接。后端执行mvn clean package -DskipTests打包成animal-backend.jar扔到装了 JDK 的服务器上java -jar animal-backend.jar就行前端执行npm run build产物是dist静态目录。最大的部署坑在于前端的接口地址生产环境构建出来的 JS 里不会带着localhost:8080需要在前端项目里建.env.production文件把VUE_APP_BASE_URL /api配成相对路径再由 Nginx 通过反向代理把/api转发到本地 8080 端口这样一个域名就能同时复用前端静态资源代理和后端接口跨域问题也被彻底消解。6. 常见问题与排查技巧实录这一节算是整个项目里最有价值的部分了因为我上面写的那些配置和代码全都是从一个个具体的问题里摸爬滚打出来的。新手时期遇到的这些问题随便一个都能卡住一整天我把它们整理成了速查表格你们对号入座即可。6.1 启动类问题速查表问题现象根本原因解决方案启动直接报Failed to configure a DataSource没连上数据库检查 MySQL 是否启动、密码是否正确useSSLfalse先加上后端启动成功但访问接口 404Mapper注解漏了或 mapper XML 路径配置错Mapper 接口加Mapper确认mapper-locations指向真实路径前端npm run serve报sass相关错误Node 版本过高/过低切换 Node 14 或者直接删除node_modules重新 install前端请求接口 404 但接口能 Postman 调通代理正向配置丢了检查vue.config.js确认proxy规则覆盖了所有需要代理的路径前缀图片上传成功但页面显示 404静态资源映射没配或代理漏配配置WebMvcConfigurer的 addResourceHandlers前端代理加/filesMyBatis 报Invalid bound statement (not found)Mapper XML 没有被扫描到检查mapper-locations路径确认 XML 中 namespace 与接口全限定名一致6.2 排查技巧与经验心得排查问题的时候最忌讳的事儿就是盯着报错信息看一次就瞎改代码改完还不行再改越改越乱。我的经验是先复现、再定位、最后才动手。前端报错优先看浏览器开发者工具Network面板看请求有没有发出、状态码多少、返回体是什么后端报错优先看控制台完整的堆栈信息定位到具体是聊到了哪一层出的错然后带着这个信息去搜索。很多问题搜一次带异常类的关键词组合比如“MyBatis Invalid bound statement”精准程度会高很多。另一个经验是补偿性的数据修正代码要果断写。比如我做状态机的时候审核通过的动作因为一次脏数据把动物状态改错了于是我直接在代码里加了启动时数据修正的脚本扫描所有“已通过领养申请”对应的动物状态不一致的自动重置。这个看似不优雅但业务就是这样脏数据不及时清代价会更大。6.3 关于性能与安全的几个额外建议这种管理系统虽然数据量不大但有些好习惯值得从现在开始养成。数据库连接池、SQL 执行耗时这些监控点位提前埋在代码里以后排查线上问题会快很多上传文件要做格式和大小白名单校验后缀名不能只判断最后一个点之后的字符所有接口校验好参数空指针密码永远不能明文入库对外的管理接口一定要做权限控制否则系统上线以后被别人扫一下就只剩尴尬了。7. 写在最后的一点心得和扩展建议复盘整个动物领养平台的开发过程最大的收获并不是写了几千行代码而是通过一整套完整系统把“前后端分离开发”的完整链路彻底串了一遍从数据库设计开始到后端接口开发、动态 SQL 构建、事务处理、权限控制再到前端页面编写、接口联调、打包部署每一步都踩过坑、填过坑然后再回看那些面试题和框架文档理解就完全不一样了。最后分享一个小技巧做这种全栈项目的时候接口文档一定不能偷懒每写完一个后端接口就把请求路径、参数、返回规则随手记到一个 Markdown 文件或者直接用 Swagger 注解生成在线文档。即便项目里只有你一个人在开发半个月后再来看自己写的接口也可能想不起字段含义更别说未来答辩的时候评审老师伸手就要看接口文档了。如果你也想拿这个项目练手我的建议是不要只满足于把功能基本跑通试着把它一步一步升级成一个“能接得住流量”的正式项目给密码改造成 Spring Security JWT 的完整方案给图片迁移到 OSS 对象存储给数据库查询加上 Redis 缓存再给前后端代码加上 Docker 容器化打包。你会发现每往前多走一步自己的技术视野就会往外扩一大圈。系统本身做完了以后我还给它做了一个很有意思的延伸——在“领养成功”节点自动给用户发送一封欢迎养宠邮件顺便推送几条科学的养宠指南。这些细节虽然技术难度不高但能让整个系统从“课设作品”真正变成“有价值的产品”做项目的乐趣也在这里面。