ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MyBatis构建宠物猫领养管理系统全解析

SpringBoot+Vue+MyBatis构建宠物猫领养管理系统全解析 1. 为什么企业机构比普通社区页面更需要一套正经的领养系统先讲一件真实发生在我手上的事。之前有家合作单位一直用微信群加Excel表格做宠物领养登记工作人员在群里发猫咪照片意向人留言后台人工私聊确认再把信息一条条录进表格。听起来好像也能跑但一遇到“同一只猫被多个家庭同时看上”“送养人临时反悔”“领养人填的信息前后矛盾”这些情况整个流程就是一场灾难——聊天记录翻不到底表格版本对不上最后只能靠人去协调。后来我们帮他们落地了一套运行在SpringBootVueMyBatis架构上的宠物猫认养管理系统整个流程才真正从“靠人盯”变成了“靠系统管”。这套系统解决的核心问题并不复杂它把一只宠物猫从入场建档、信息展示、领养申请、背景审核、签订协议到状态更新这条完整链路全部放进了同一个信息流里。每个角色——后台管理员、送养人、领养申请人——看到的是同一个数据源。猫咪是“待领养”还是“审核中”还是“已送养”系统里只有一个状态值不存在两个人同时改出两个答案的可能性。这套东西适合谁如果说你只是想在小范围内发几张猫咪照片找领养那确实没必要上系统一个朋友圈就够了。但如果你面临的是这些场景中的任何一种我建议认真看下去机构或救助站每月要经手几十只甚至上百只猫靠手工台账没法保证信息一致需要留存领养人的身份信息、居住情况、养宠经历涉及数据归档和事后追溯有多个工作人员协作操作需要有账号分级不能每个人都能修改送养状态业务要留痕领养申请被驳回时要知道是谁、在哪个环节、因为什么原因被驳回。这套系统的价值说白了就是两句话对外它把领养信息从“散落在聊天记录里的图片和文字”变成了结构化、可筛选、可随时访问的网页对内它把审核流程变成了带状态流转、带操作日志、带权限边界的规范流程。我自己在跟不少做开发的朋友聊的时候发现大家容易把这类系统想简单了觉得不就是一套CRUD吗其实真做起来比想象中多出很多细节一只是“已预定”还是“已被领走”在业务上语义差别很大一个用户能不能撤回申请、能不能重复提交申请这涉及前端页面怎么设计、后端接口怎么做幂等更别说多图片上传、手机号校验、操作日志记录这些实际工程里避不开的点。这篇博客我会把这套系统的架构选择、后端接口设计、前端交互、数据库规划、部署心得一条条拆开讲都是从实际项目中攒出来的经验希望能帮到正在做类似项目的人。2. 技术栈的分工逻辑SpringBoot、Vue、MyBatis与MySQL各自扛下什么做企业级项目跟做个人Demo最大的区别就是你不能只选“我会的技术”你得选“在多人协作、长期维护、权限管控、性能扩展这些压力下不容易翻车的组合”。这套系统选了SpringBootVueMyBatisMySQL这个组合不是拍脑袋定的是几轮对比之后留下来的方案。下面我把每一层负责的事情、选它的理由、以及替代方案被我否掉的原因都说清楚。2.1 SpringBoot为什么它是这层最省事的选择后端选型时我们其实只认真比较了两条路线SpringBoot单体应用和Spring Cloud微服务。最后选了SpringBoot理由非常明确这个项目的业务规模和团队规模远没到需要微服务的程度。微服务带来的服务注册、配置中心、链路追踪这些基础设施对这个场景基本是纯成本。SpringBoot在这个项目里承担了这些事情提供标准的分层骨架Controller层接收请求Service层处理业务逻辑Mapper层访问数据库内置Tomcat容器Maven打包之后一个java -jar就能启动部署成本很低自带参数校验、异常拦截、定时任务、文件上传这些常用能力的集成方案不需要重复造轮子。我把后端模块划分为五个部分auth负责登录、注册、JWT签发user负责用户信息维护cat负责猫咪档案、图片、状态管理adopt负责领养申请、审核、协议记录admin负责后台统计和运营功能。五个模块各管各的边界Service之间通过方法调用协作不走重复的HTTP调用降低耦合的同时也省掉了不必要的网络开销。2.2 Vue页面很多但真正考验组件化能力前端选型时候的候选有Vue 2、Vue 3、React最后用了Vue 2.7配合Element UI。理由也很实在团队成员对Vue的熟悉度最高Element UI的表单组件和表格组件能覆盖掉管理后台80%的页面需求。Vue层承担了三块核心工作面向领养申请人的H5页面猫咪列表、猫咪详情、提交领养申请、查看申请进度面向后台运营人员的PC管理端猫咪档案管理、申请审核列表、用户管理、统计面板公共基础设施axios请求封装、路由守卫、状态管理、图片上传组件。开发中最需要花心思的不是页面排布而是组件之间的状态同步。举个例子猫咪详情页里用户点了“提交领养申请”按钮提交成功之后这个按钮要立刻变成“已申请”禁用状态。这个状态不只影响当前页面还影响列表页、个人中心页。我们统一把这类状态放进Vuex里用adoptStatus字段管理页面只做读取和提交动作不自己存副本这样就避免了几个页面状态对不上的问题。2.3 MyBatisSQL在手复杂查询不抽象ORM选型对比的是MyBatis和Spring Data JPA。JPA在单表CRUD场景下确实省代码但一到“猫咪列表按品种筛选、按年龄排序、再关联出领养申请数”这种多表统计JPA的自动SQL要么绕弯子要么性能不理想。MyBatis的优势是SQL完全由你掌控复杂查询直接写XML出了问题也好定位。以系统里最常用的一个查询为例——后台的“猫咪档案列表”需要一屏展示猫咪基本信息、当前状态、近一周浏览数、累计申请人数。这个查询用MyBatis写一个关联SQL就能搞定逻辑清晰执行计划也能用EXPLAIN直接分析。JPA想要同样的效果得凑出三次查询再内存拼装代码量不会更少性能只会更差。2.4 MySQL稳定优先事务与索引是生命线存储层用MySQL 8.0没选PostgreSQL也没选NoSQL理由很朴素这套系统的事务特性要求高领养审核涉及状态变更和用户积分变动必须保证原子性。MySQL的InnoDB引擎在事务可靠性上久经验证。数据量方面我们按“每年处理1000只猫咪、每只猫咪关联3条领养申请、每条申请附带5条审批日志”估算三年下来核心表也就几十万行。这个量级完全在MySQL的舒适区里加好索引之后查询都走主键或二级索引响应时长基本都是毫秒级。没必要为了“以后可能会很大”去引入分布式数据库徒增运维负担。个人观点这个技术栈的组合逻辑可以总结成一句话——每个组件都只负责自己最擅长的事彼此之间用标准协议通信。后端不越界去做前端渲染前端不掺合数据存储逻辑数据库专心管数据和事务。这种边界清晰的架构才能在业务需求频繁调整的时候稳住阵脚。3. 后端核心链路从0到1用户体系、猫咪档案与领养审批工作流后端是整个系统的中枢神经系统。下面我按实际开发的顺序把三条最关键的业务链路拆开讲怎么建用户体系、怎么给猫咪建档、怎么设计领养审批的状态流转。3.1 用户体系JWT无状态认证怎么做才安全系统里有两类核心角色管理员和普通用户领养申请人。因为前后端分离认证方案我们用JWT。流程是用户登录成功后后端签发一个有效期7天的Token返回给前端前端存在localStorage里之后每次请求在Authorization头发回。JWT方案有几个地方必须处理好不然容易出现安全问题第一密钥管理。签发Token用的密钥不能写在代码里我放在配置文件的jwt.secret项里并区分开发环境和生产环境的配置文件。生产环境的密钥是随机生成长度超128位的字符串并且刻意设置的复杂度较高防止暴力破解。第二权限拦截。Spring Boot里用一个HandlerInterceptor实现登录校验对/api/admin/**开头的路径做管理员角色校验其他业务接口只校验是否登录。拦截器里从Token解析出userId和role放入ThreadLocal上下文后续Service层直接读取当前操作人信息。第三密码存储。用户密码用BCryptPasswordEncoder做哈希存储不存明文。这个组件自带的盐机制能有效抵抗彩虹表攻击。注册和修改密码两个接口都做复杂度校验至少8位且包含字母和数字。3.2 猫咪档案别把基本信息表设计成聊天记录猫咪档案的核心表叫cat_info字段包括cat_name、breed品种、gender、birth_date、vaccine_status疫苗状态、neutered_status绝育状态、health_desc健康描述、status业务状态、cover_img封面图、detail_imgs详情图列表等。这里有一个从实际需求里逼出来的设计决策图片到底存数据库还是存文件服务器我们最终方案是图片文件上传到服务器本地目录数据库只存访问路径。为什么没人把图片直接存到MySQL因为图片是二进制大对象塞进数据库会让表体积迅速膨胀备份和查询都变慢。设计图片存储时我采用了“本地磁盘存储图片数据库存路径”的组合通过配置upload.path指定存放目录前端用/files/**静态资源映射访问。考虑到项目部署的是单台服务器本地存储足够用后续如果图片量变大可以平滑迁移到对象存储服务业务层改动很小——只需要把存储路径换成对象存储的访问链接即可。另一个重要设计是状态字段的取值统一管理。我们定义了五档状态待审核刚入档案还没开放领养、待领养已通过审核对外展示、审核中有人提交了领养申请正在进行背景核验、已预定审核通过等待签协议交接、已领养完成交接、已下架病亡、退回或其他原因。这些状态用一个int类型字段存储在代码里用枚举类做映射前端只接收状态的字符串描述这样即使后台逻辑调整也不会把状态值的变化扩散到前端。3.3 领养审批工作流状态机设计的核心思想领养申请的处理是整个系统里业务逻辑最重的一块也是我觉得最值得拿出来讲的部分。用户提交领养申请后系统生成一条adopt_application记录状态为待审核。管理员进入后台审核列表看到申请人填写的居住情况、养宠经历、家庭成员信息等可以做三个操作通过、驳回、转为面谈。这个流程的严谨性来源于设计阶段就画清楚了一套状态机规则。状态之间的流转不是无约束的每次流转还会写一条操作日志。核心逻辑如下待审核 -通过- 初审通过 待审核 -驳回- 已驳回 初审通过 -背景核实通过- 待签协议 待签协议 -完成签约- 已领养 待签协议 -未按期签约- 已失效 已驳回 -申请人重新提交- 待审核这套状态机规则落实到代码上我在Service层写了一个专门的状态校验方法。所有变更状态的数据库操作都必须先判断当前状态跟目标状态是否匹配不匹配就直接抛异常。为什么这么严因为在实际项目里我遇到过工作人员连点两次按钮导致一只已经“已领养”的猫咪又被误改成“待领养”数据一乱整个页面都跟着乱。状态机就是防止这类问题的最后一道防线。在事务方面handleAdoptionReview方法上加了Transactional注解里面做了三件事更新申请单状态、更新猫咪状态、写入审批日志。这三步要么全成功要么全回滚。因为一旦申请单更新成功了、猫咪状态却更新失败那用户看到的猫咪还是“待领养”其他人还能提交申请就会出现两只猫同时在领养流程里的脏数据。3.4 后端接口清单与控制器设计示例下面贴一段实际代码示例是领养申请提交接口的Controller层写法RestController RequestMapping(/api/adopt) public class AdoptController { Autowired private AdoptService adoptService; PostMapping(/apply) public Result apply(RequestBody Valid AdoptApplyDTO dto) { Long userId UserContext.get().getUserId(); Long applicationId adoptService.createApplication(userId, dto); return Result.success(applicationId); } GetMapping(/myApplications) public Result myApplications(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size) { Long userId UserContext.get().getUserId(); return Result.success(adoptService.pageMyApplications(userId, page, size)); } }代码里的UserContext.get()就是从JWT拦截器写入的ThreadLocal中取当前登录用户避免每个接口都去解析一遍Token。接口设计上有几个容易被忽视的细节值得单独说一下参数校验用JSR-303注解在DTO上直接声明NotBlank、Email、Pattern请求进来还没到Service层就先拦掉非法参数统一返回体所有接口返回ResultT结构包含code、message、data三个字段前端axios拦截器统一处理错误码不用每个页面自己写判断分页参数page和size统一走前端传入后端加了上限限制size 50防止有人一次拉走全量数据。4. 前端交互的三个重头戏列表筛选、表单校验、后台审核仪表板后端把数据和服务理清之后前端要解决的是“人怎么顺畅地使用这些能力”。我按照使用频率把前端拆成三条线来讲。4.1 猫咪列表页筛选条件多查询性能怎么做面向访客和注册用户的猫咪列表页是这个系统的门面页面。用户进来第一个动作就是想看“英短”“母猫”“已绝育”“待领养”的猫怎么筛筛选条件在页面上是四个下拉框加一个搜索框看起来很简单但背后的请求参数组合出几十种可能。这里有一个核心技术决定筛选参数不拼在URL里而是统一放在一个query对象里由Vue组件管理筛选条件变化时触发一次新的请求。data() { return { query: { breed: , gender: , status: 待领养, keyword: } } }, methods: { async loadList() { const res await this.$http.get(/api/cat/list, { params: this.query }) this.list res.data.records this.total res.data.total } }有些开发同学会习惯把所有筛选条件都塞进一个过期时间很短的缓存里其实没必要。MySQL在后端加了(status, breed)联合索引之后这种多条件组合查询的响应已经足够快没必要在前端做额外缓存徒增逻辑复杂度。列表页还有一个常见体验问题用户点进详情再返回列表时希望筛选条件还在、滚动位置还在。我们通过keep-alive组件缓存列表页实例并监听activated生命周期钩子恢复查询条件处理之后切换体验顺滑了很多。4.2 提交领养申请前端表单校验的边界在哪里申请表单包含姓名、手机号、所在城市、居住情况、养宠经历、家庭成员、住房类型等字段。前端表单校验最容易犯的错是“该交给后端的也拦在前端”。我定下的原则是前端只做格式合法性校验——手机号位数、必填项、长度限制后端做业务合法性校验——重复申请、状态是否允许申请。比如“这只猫是否已有人申请”这种事后端必须再验一次。为什么因为前端校验再严格攻击者也可以绕过页面直接调接口。后端在createApplication方法里先查猫咪当前状态如果已经不是“待领养”直接抛业务异常。只有后端也拦住了才算真正安全。多说一句表单交互细节手机号输入框是高频场景我们用el-input的formatter做自动加空格显示成“138 0000 0000”这种格式提交按钮在点击后进入loading状态防止用户重复点击造成重复提交。4.3 后台审核仪表板数据可视化和操作便捷性怎么平衡后台管理端用的是Vue Router嵌套路由左侧菜单、顶部面包屑、右侧内容区。审核仪表板首屏是一排统计卡片今日新增申请、待审核数量、总在库猫咪、本月成功领养数量。这些数据由一个统计接口一次返回避免前端发五个请求逐个加载。审核列表页的核心是表格操作。每一行显示申请人信息和猫咪信息摘要右侧三个按钮“查看详情”“通过”“驳回”。这里有一个我花了不少心思的交互细节“通过”按钮不能一键直接通过而是弹出确认弹窗要求管理员勾选一个确认框“已核实申请人信息”才能提交。这个小小的交互改动对业务起到的作用比想象中大——它强制操作者做了一次有意识的确认大大减少了误点导致的审核事故。前端请求封装方面axios实例统一配了baseURL、超时时间、请求拦截器自动加Token头和响应拦截器统一处理后端返回的code。Token过期时响应拦截器跳到登录页并清空本地用户信息。这套封装让页面代码非常干净业务组件里基本只有数据请求和渲染逻辑。5. 数据库表设计最花时间的部分字段、状态机与索引规划数据库设计我不主张一开始就追求“极致的范式化”。对于这套系统我的策略是核心业务表严格规范化统计查询类字段适当冗余用空间换查询效率。5.1 核心表结构与设计意图数据库一共规划了八张核心表表名用途关键字段user用户账号体系id, username, password_hash, role, phonecat_info猫咪档案id, cat_name, breed, gender, status, cover_imgcat_img猫咪图片列表id, cat_id, img_url, sort_orderadopt_application领养申请id, user_id, cat_id, status, apply_reasonapplication_log审核日志id, application_id, operator_id, action, remarkadopt_agreement领养协议id, application_id, agreement_no, sign_timeoperation_log管理员操作日志id, admin_id, target_type, target_id, contentsystem_config系统配置id, config_key, config_value以adopt_application为例表设计时特意加了submit_source字段记录用户提交申请时用的是哪个入口移动端页面还是PC页面这个字段在复盘推广渠道时非常有用。此外我还设计了一个uniq_user_cat唯一索引约束同一用户对同一只猫只能存在一条“进行中”的申请防止用户反复提交刷屏。5.2 索引规划怎么让多条件分页查询不拖后腿索引是这个系统性能的命门。我按真实查询场景逐条分析最终定了这些重点索引cat_info(status, breed, create_time)覆盖列表页最常用的三条件查询adopt_application(user_id, status)支撑“我的申请”页面adopt_application(cat_id, status)支撑后台按猫咪查申请记录application_log(application_id)支撑审核详情页拉日志。建索引的取舍在于索引不是越多越好每多一个索引写入时的维护成本就会增加一次。我经历过因为一个冗余索引导致批量导入数据变慢近一倍的情况所以建索引前都会先看一眼慢查询日志确认有实际需求才加。另外MySQL 8.0默认的utf8mb4字符集必须保留因为猫咪档案的描述文本可能要记录一些生僻字符emoji表情在utf8mb4下才能正常存储和显示。这算是一次踩坑换来的经验早期用utf8字符集时用户提交描述里带一个emoji整条数据就存不进去排查了半天才发现是字符集的问题。5.3 事务边界哪些操作必须放进同一个事务结合前面说的审核流程我总结三个必须开启事务的场景提交申请插入申请单更新猫咪状态为“审核中”审核通过更新申请单状态更新猫咪状态插入审核日志可选给用户发站内通知更新猫咪档案更新猫咪基本信息处理图片列表变更先删旧图再插新图。事务注解用Transactional(rollbackFor Exception.class)要特别注意rollbackFor的写法。默认情况下Spring只对RuntimeException回滚如果业务代码抛了受检异常不加rollbackFor的话事务不会回滚数据就会不一致。这个坑非常隐蔽我早期就在这里吃过亏。6. 部署与维护从源代码到生产环境的实战记录系统开发完成之后部署上线又是一个容易被忽视的大环节。我把自己踩过的一些坑和最终确定的方案整理一下。6.1 Maven打包与运行环境配置后端用Maven管理依赖和构建。执行mvn clean package -DskipTests生成一个可执行jar包放上服务器后用nohup java -jar启动。配置分离是必须的application-dev.yml放本地开发环境application-prod.yml放生产环境数据库连接、上传路径、日志级别都从这里区分。一个实际操作上的小技巧生产环境服务器内存不大时JVM参数要显式指定比如-Xms256m -Xmx512m。如果不指定JVM默认申请的堆内存可能占满整台服务器数据库和前端服务都会被拖垮。启动脚本我固定写成nohup java -Xms256m -Xmx512m -jar cat-adoption.jar --spring.profiles.activeprod /app/logs/cat-adoption.log 21 6.2 前端构建与Nginx反向代理Vue项目开发完执行npm run build生成dist静态目录上传到Nginx的html目录下。Nginx配置里最关键的是两个点第一前端路由的history模式必须做try_files配置否则用户直接刷新/admin/cat/list这个地址时Nginx找不到对应的物理文件会返回404。配置片段是location / { try_files $uri $uri/ /index.html; }第二API请求走反向代理。前端请求/api/开头的接口Nginx代理到后端服务的localhost:8080这样就不存在跨域问题也不用在后端单独配CORS了location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }6.3 上线后最值得留意的三个实际问题系统上线运行一段时间后真正考验的是维护能力。我把遇到的问题分成三类每一类都有对应的处理方案第一类慢SQL悄悄出现。上线初期数据量少查询都很快。运行两个月后后台导出报表的接口开始变慢。查了慢查询日志发现是报表统计SQL没走索引。处理办法给create_time字段加索引改写统计SQL使用覆盖索引。第二类服务器磁盘被图片占满。之前讲过图片存本地磁盘运行一段时间后发现磁盘告警。解决思路是给上传目录做定时清理策略清理无效的临时文件同时建议后续迁移对象存储。这类问题在开发环境根本感知不到只有真实跑起来才会暴露。第三类定时任务锁冲突。系统里跑了两个定时任务每日凌晨统计猫咪领养数据、每周定时给未完成申请的用户发提醒。如果应用在多节点部署定时任务会重复执行。所以上线时就要规划好要么单节点部署要么引入分布式锁。我们目前是单节点部署代码里在任务的入口加了一个tryLock逻辑做兜底避免将来扩展时踩坑。6.4 反向排查技巧日志与监控最后分享一个我自己一直坚持的运维习惯从上线第一天就要把日志规范化否则出问题时候所有的排查工作都会变成大海捞针。具体做法是日志框架用Logback按照info.log和error.log拆分文件每一天滚动一个日期文件。业务异常统一打印到error.log并带上接口路径和请求参数的关键信息。定位问题时候的姿势是先tail -f error.log看异常堆栈再根据接口路径和用户ID回查操作日志表基本能还原出事发过程。这里还要注意一个问题日志不要记录敏感字段。密码、身份证号、手机号这类信息要么脱敏要么不打别为了一时排查方便把用户隐私写进日志真出了问题在合规层面会很被动。7. 我最后想说的几句实在话这个项目从需求梳理到上线前后经历了大概六周。如果让我总结最想对后来者说的经验大概是三条。第一别把企业级系统想复杂了但也别把它想简单了。所谓“企业级”往往不是技术上用了多牛的东西而是对数据一致性、权限控制、操作留痕、稳定运行这些基础要求不妥协。SpringBoot的易用性、Vue的组件化、MyBatis的SQL可控性、MySQL的事务能力这套组合放在宠物领养这种中等复杂度的业务场景里真的是又稳又省心的搭配。第二状态机和事务是最容易出大事故的两个角落。领养申请一旦在状态流转上出了bug轻则数据显示错乱重则导致误送养。我建议做这类系统的人一定要把状态流转图画清楚再动手写代码让所有人都能看到同一个线上的“规矩”。第三预留升级空间但别过度设计。这套系统的图片存储目前是本地磁盘以后图片量大了可以平滑切换对象存储定时任务目前是单节点执行以后多节点部署也有分布式锁的兜底。这些“预留”都是低成本的设计决策不需要提前引入复杂的组件但能给未来省下不少重构的时间。如果你也正在做一个类似的管理系统希望这篇分享里的状态机设计、事务边界、索引规划、Nginx配置这些细节能帮你在动手前多有一点把握。项目的信息架构、后端接口设计、前端组件拆分每一条线单独拿出来都不算难但要把它们像齿轮一样严丝合缝地咬合起来需要的正是对业务逻辑的深度理解——这是比写代码本身更花功夫的部分也是最值得花功夫的部分。最后分享一个我在实际操作中一直保留的习惯每周导出一份猫咪状态和申请进度的统计数据手动核对一遍有没有“异常状态”——比如猫咪显示“待领养”但已经有超过三份进行中的申请。这个习惯帮我提前发现过两个隐蔽的逻辑漏洞比任何监控告警都好用。做事细心一点系统就会靠谱一点。
返回列表