news 2026/10/22 16:52:19

基于Spring Boot和Vue的流浪宠物救助管理系统设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Spring Boot和Vue的流浪宠物救助管理系统设计与实现

1. 项目背景与系统定位

做流浪宠物救助系统这个方向,我是比较有发言权的。前前后后帮好几个公益组织做过类似的线上平台,踩过不少坑,也总结了不少经验。这次分享的是一个典型的Spring Boot + Vue全栈项目,核心是围绕“三个角色”来构建完整业务闭环,正好覆盖了流浪宠物救助里最常见的三类人:平台管理员、救助站工作人员、普通领养人。

先说说这个项目到底解决什么问题。流浪宠物救助这个事,线下链条其实很清晰:发现流浪动物 → 救助站收容 → 医疗处理 → 发布领养信息 → 申请领养 → 审核 → 回访。但实际运营中,这个链条几乎全靠手工表格、微信群、口头沟通来串联。救助站少说几百只宠物,靠Excel记录每只宠物的疫苗接种时间、绝育状态、领养进度,累不说,信息还容易错。领养人想找一只合适的宠物,得挨个救助站问,效率低到离谱。管理员想统计各救助站的数据,得等月底人工汇总,数据滞后不说,准确性也堪忧。

这套系统就是把整条链路搬上线。管理员在后台管理所有救助站账号、审核宠物信息、发布平台公告;救助站工作人员负责录入宠物档案、更新救助进度、处理领养申请;普通用户通过前端页面浏览宠物、提交领养申请、查看审核结果、收藏心仪的宠物。三个角色各管一段,权限边界清晰,数据流转闭环,能把这套系统的前后端代码完整跑起来的开发同学,对权限设计和业务状态流转的理解基本就过关了。

项目比较适合两类人参考:一类是准备做毕业设计或者个人项目的开发者,这个选题的完整度、技术覆盖面和业务复杂度都挺合适;另一类是正在给公益组织做信息化方案的从业者,可以拿这套结构去改造成自己想要的版本。本文会从架构设计、角色权限、核心功能、数据库设计、实操踩坑几个维度展开,全程按我真实的开发经验来讲。

2. 技术架构与核心设计思路

2.1 为什么选Spring Boot + Vue这套组合

这个组合在中小型管理系统里属于标配,不稀奇,但胜在稳。后端Spring Boot的好处不用多说——生态成熟、上手快、社区资料多,遇到问题搜一下基本都有答案。前端Vue对团队要求低,组件化开发让页面复用性高,配合Element UI这类组件库,后台管理系统的页面搭建速度非常快。

我见过不少项目用JSP或者Thymeleaf做前端,不能说错,但前后端耦合太紧,后面想单独换前端或者加个移动端适配,改动成本很高。Vue这种前后端分离的方式,接口定义清楚了,前后端可以并行开发,联调阶段用Mock数据也能撑住。而且Vue的响应式数据绑定在做表单交互、列表筛选这类场景时,开发体验比传统模板引擎好太多。

这套项目的技术栈大致是:

  • 后端:Spring Boot 2.x + MyBatis Plus + MySQL + Redis(可选)
  • 前端:Vue 2.x + Vue Router + Vuex/Pinia + Element UI + Axios
  • 权限:Spring Security + JWT,前端路由守卫配合拦截
  • 构建:Maven(后端)+ npm(前端)

如果是我在本地跑这套项目,后端用JDK 1.8或者11都行,Spring Boot版本2.3到2.7之间都没有大坑。前端Vue用2.x版本配上Element UI最稳,Vue 3的组件库生态虽然也成熟了,但这个项目如果基于Vue 2来设计,没必要强行升级。

2.2 前后端分离下的工程组织方式

前后端分离不是简单地把代码分成两个文件夹就完了,工程组织方式直接影响开发和维护效率。这套项目我的习惯是建两个独立工程:

pet-adoption/ ├── backend/ // Spring Boot 后端工程 │ ├── src/main/java │ ├── src/main/resources │ └── pom.xml └── frontend/ // Vue 前端工程 ├── src/ ├── package.json └── vue.config.js

开发和测试阶段用代理转发解决跨域,配置在vue.config.js里:

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

这样前端请求 /api/xxx 就自动转发到后端8080端口,解决了本地开发最常见的跨域问题,不用后端额外配CORS。生产环境则是通过Nginx反向代理,把 /api 路径转发到后端服务,前端静态文件由Nginx直接托管。这套方案在真实项目中长期运行下来比较省心,基本不需要为跨域问题头疼。

2.3 后端模块化分包策略

后端分包这块,我见过很多新手把所有的类都扔在com.example.demo下面,Controller、Service、Mapper混在一起,看起来热闹,真正要改功能的时候想哭。这个项目我推荐按模块分包,而不是按技术层次分包:

com.pet.adopt ├── controller/ │ ├── AdminController.java │ ├── PetController.java │ ├── ApplyController.java │ └── UserController.java ├── service/ ├── mapper/ ├── entity/ ├── config/ └── common/

这看起来跟常规分包没什么区别,但关键在controller层面是按业务模块拆的,而不是按角色拆。比如PetController里既有管理员审核宠物信息的接口,也有普通用户查询宠物列表的接口,这是对的。因为同一个业务对象的不同操作,服务的是不同角色,但数据模型是同一套。如果按角色拆包,很容易导致一个pet相关的接口分散到三个controller里,改起来要翻好几个文件。

实体类用MyBatis Plus的话,可以少写很多XML,简单的增删改查直接继承BaseMapper就行,只有复杂查询才需要手写SQL。这套项目里宠物列表筛选(按品种、年龄、状态多条件组合查询)和领养申请记录查询(根据角色不同查询条件不同),这些是需要单独写Mapper方法的,后面讲实现的时候再细说。

3. 三大角色的权限体系与业务边界

3.1 管理员端的管理职能

管理员是整个系统的超级用户,承担的角色是“平台运营者”,而不是具体业务执行者。管理员端的功能设计上要避免大包大揽,把救助站该做的事抢过来。合理的划分是管理员管“人和规则”,救助站管“事和流程”。

具体来说,管理员的权限包括:

  • 救助站账号管理:审核救助站注册申请、启用或禁用账号、重置密码
  • 平台公告管理:发布、编辑、下线平台公告,公告会推送到所有登录用户的消息列表
  • 宠物信息终审:救助站录入的宠物信息需要管理员抽查审核,确保信息真实完整
  • 领养数据总览:查看各救助站宠物数量、领养率、待处理申请数等统计指标
  • 领养回访记录管理:监督救助站的回访任务完成情况

这里有个值得注意的设计点:宠物信息为什么不是救助站录入后直接发布,而是需要管理员终审?实际运营中,救助站工作人员水平参差不齐,有的照片拍得模糊,有的信息填一半,直接发布到前台,用户看到的就是一堆不完整的数据,体验很差。管理员终审相当于一道质检关卡。当然如果救助站数量太多、平台人力有限,也可以设置白名单机制,信誉好的救助站免审核,这个属于业务上的取舍。

管理员端的页面包括:工作台(数据统计)、救助站管理、会员管理、宠物内容审核、领养订单管理、公告管理。菜单比较多,用侧边栏分组展示比较合理。管理员账号由系统初始化生成,注册入口不开放,防止随意创建管理员账号。

3.2 救助站端的日常运营

救助站工作人员是系统里工作量和操作频率最高的角色。他们每天都要做的就是三件事:录入新救助的宠物、更新宠物状态、处理领养申请。

救助站端的核心功能:

  • 宠物信息录入:包括宠物图片(支持多图上传)、品种、性别、年龄、毛色、健康状况、疫苗情况、绝育状态、救助经过描述
  • 宠物状态管理:待安置 → 待领养 → 已领养 → 已回访,状态变更后前台同步更新
  • 领养申请处理:查看领养人提交的申请详情、审核通过或驳回、预约线下看宠
  • 领养回访:已领养宠物定期回访,记录回访结果(宠物健康状况、是否适应新家庭等)
  • 宠物日志:记录宠物的日常(喂食情况、治疗记录、性格变化等),供领养人参考

救助站角色的用户来源有两种方案:一种是管理员预先分配好账号,救助站拿到账号直接登录;另一种是救助站自己在系统注册,管理员审核通过后启用。第一种方案管理成本低,适用于救助站数量不多的场景;第二种更灵活,但管理员需要额外的审核工作量。我做的这个项目默认用第二种方案,因为流浪宠物救助站的流动性大,新救助站冒出来是常态,完全靠管理员线下开通账号有滞后。

救助站端的界面设计和管理员端差别不大,主要差异在菜单权限。救助站登录后只能看到自己的宠物列表、自己的申请记录、自己的回访任务,看不到其他救助站的数据。这个数据隔离在查询逻辑里就要做好,不然两个救助站看到互相的数据,很容易出问题。

3.3 领养者端的自助服务

领养者其实就是普通注册用户,登录后进入的是前台展示页面,跟管理端完全分开。领养者端的设计原则是“低门槛、引导式”,要照顾到不一定熟悉互联网操作的用户群体。

领养者端的主要功能:

  • 浏览宠物:按品种、性别、年龄段、救助站等条件筛选,列表卡片展示宠物照片和基本信息
  • 宠物详情:查看完整档案、救助经过、性格描述,查看救助站联系方式
  • 领养申请:填写申请表单(家庭情况、养宠经验、居住环境、收入情况等),提交后等待审核
  • 申请记录:查看我的申请列表,跟踪审核进度(待审核 → 已通过 → 已驳回)
  • 宠物收藏:收藏感兴趣的宠物,方便后期对比
  • 消息通知:接收申请结果通知、公告提醒

领养申请表单的设计要充分考虑后端审核的需求。表单字段太少,救助站无法判断申请人是否合适;字段太多,领养人嫌麻烦直接放弃。我的经验是控制在8到10个必填字段,核心围绕“是否有稳定住所”“是否有养宠经验”“家庭是否有过敏成员”“是否同意回访”这几个关键决策点设计。

领养者端页面整体风格要跟管理端有差异,不能直接套用后台上表格密密麻麻的那种布局。前台页面走温暖、亲和的风格,图片占比大、文字简洁,多采用卡片式布局。有不少同学做这个项目拿后台模板一套就完事,前台后台看起来一模一样,实际是不合理的。

4. 核心功能模块的落地实现

4.1 宠物档案与状态流转

宠物档案是整个系统的数据主线,从救助站录入一只宠物开始,到它被领养并完成回访,这张表会经历多个状态。状态机设计得好不好,直接影响业务逻辑的复杂度。

宠物状态我按这种方式设计:

状态值含义可进行的操作
0待安置正在进行治疗和观察,暂不开放领养
1待领养健康状态良好,前台展示并开放申请
2审核中有领养人提交申请,宠物暂时锁定
3已领养审核通过并完成线下交接
4已回访完成一次或多次回访记录

这个状态流转链路上有几个容易忽略的细节。第一,“审核中”状态的意义是什么?当有领养人提交申请后,宠物如果还显示“待领养”,其他人也能继续申请,理论上没问题,但实际运营中会给救助站带来沟通麻烦。锁定的意思是暂时不再接受新的申请,集中处理当前这一批。等申请被驳回,状态回到“待领养”;申请通过,进入“已领养”。这样处理会让救助站工作人员的工作路径更清晰。

第二,宠物状态变更时机需要在代码层面控制,不能靠前端按钮随便改。后端Service层对状态变更做校验,比如已领养的宠物不能直接改回待领养,必须先走人工审核。这个校验在Controller层做也行,但更稳妥的是放在Service层,防止绕过接口逻辑。

后端状态更新的核心代码大致是这样:

@Service public class PetServiceImpl implements PetService { @Override @Transactional public boolean updatePetStatus(Long petId, Integer newStatus, Long operatorId) { Pet pet = petMapper.selectById(petId); if (pet == null) { throw new BusinessException("宠物信息不存在"); } // 状态流转合法性校验 if (!canTransition(pet.getStatus(), newStatus)) { throw new BusinessException("非法的状态流转"); } // 记录操作日志,方便追溯 updatePetStatusLog(petId, pet.getStatus(), newStatus, operatorId); pet.setStatus(newStatus); return petMapper.updateById(pet) > 0; } private boolean canTransition(Integer current, Integer target) { // 规则判断:待安置->待领养 待领养->审核中 审核中->待领养/已领养 等 } }

前端状态展示用Element UI的el-tag根据状态值渲染不同颜色和文案。这里我建议前后端定义好状态枚举字段,不要前端写死中文,否则后端改状态值的时候前端要跟着改,容易出现线上事故。

4.2 领养申请与审核流程

领养申请是连接领养者和救助站的业务桥梁,这个流程设计直接影响用户体验和救助站的工作负担。

申请流程可以拆成四步:

  1. 领养者在前台提交申请,填写领养意向表单
  2. 系统生成申请记录,状态为“待审核”,同时把宠物状态改为“审核中”
  3. 救助站在后台查看申请详情,可以选择通过或驳回,驳回必须填写原因
  4. 领养者在个人中心查看结果,通过则按救助站要求预约线下看宠,驳回则显示原因

这块我在实现时的做法是:领养申请单独建一张表,而不是在宠物表上加申请字段。因为一只宠物可能会被多个用户申请,是一对多的关系,不建独立表的话数据都无法存放。表结构后面会详细说,核心字段包括申请宠物ID、申请人ID、申请表单内容(JSON格式存)、状态、审核人、审核时间、驳回原因。

审核功能实现时的关键点:救助站点击“通过”时,前端要弹二次确认框,提示“通过后宠物将被标记为已领养,且不再接受其他申请”。这是防止误操作的最好方式。我做过一个救助站用户,审核时点了通过后又发现搞错了,结果宠物状态已经变更,只能靠管理员后台修复。加一个二次确认能减少不少这种事故。

驳回时强制填写原因也是必要的。用户被驳回之后,如果看不到原因,大概率会重复提交同样的申请,救助站觉得烦,用户也觉得很迷茫。有了驳回原因,用户可以针对性地修正问题再次申请,整体转化率反而更高。

4.3 公告与反馈模块

公告和反馈模块看似简单,但在救助平台里承担了不小的作用。公告用于平台发布需要所有用户知晓的信息,比如领养条件调整通知、某个救助站临时关闭等。反馈则给用户一个表达诉求的渠道,领养人遇到问题、志愿者发现平台bug都可以提交反馈。

公告这块我在项目里用的是比较轻量的实现:公告表存标题、内容、发布人、置顶标识、发布时间。前台在首页展示最近5条公告,全部公告单独一个列表页。管理员发布公告时,可以选择通知范围(全体用户/指定救助站),通知范围影响消息中心的展示逻辑。

反馈模块要注意防刷,不做限制的话很容易被恶意提交大量垃圾反馈。我加了两个限制:同一个用户两次提交反馈的最小时间间隔限制(5分钟内不能重复提交),以及反馈内容敏感词过滤。这块用Interceptor在Controller层做拦截就行,不需要上MQ这种重型方案。

其实还有个小细节:用户提交反馈后,理论上应该有系统通知告知“反馈已收到,预计X个工作日内处理”。这个通知逻辑可以做成异步的。人少的时候同步发也无所谓,但到后面用户量上来,还是建议用消息队列或者直接异步任务把通知发送跟主流程解耦。

5. 数据库设计与关键表结构

5.1 用户与角色设计

用户表设计时,我强烈建议不要用“一张表存所有用户然后用role字段区分”的做法。虽然这个项目只有三个角色,看起来一表搞定很简单,但实际开发中会遇到几个麻烦:不同角色需要存储的扩展字段不一样,管理员不需要填家庭住址,领养者需要;救助站工作人员需要关联所属站点的ID,其他角色不需要。如果全放一张表,字段大量冗余,空值率极高,后期维护看着都头大。

我推荐的方案是拆两张表再加一张中间关联表:

  • sys_user:用户公共信息表(用户名、密码、手机号、邮箱、状态、头像、创建时间)
  • role表:角色定义表(角色代码、角色名称)
  • user_role:用户角色关联表

登录时根据用户ID查出角色列表,Spring Security的GrantedAuthority就按角色集合来设置。接口权限校验用注解 @PreAuthorize("hasRole('ADMIN')") 或者 @RequiresPermissions(如果用Shiro)都行。

sys_user的核心字段示例:

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, phone VARCHAR(20), email VARCHAR(100), avatar VARCHAR(255), type TINYINT COMMENT '用户类型:0管理员 1救助站 2领养者', status TINYINT DEFAULT 1 COMMENT '账号状态:0禁用 1启用', create_time DATETIME );

这里type字段跟角色表作用重复了,但实际开发中我保留了这个字段做快速查询用。比如前台统计“该救助站名下宠物数”时直接用type + 关联救助站ID查询,不需要走角色关联表。角色关联表的维护走业务逻辑,查询时以type字段为准,取一个平衡。

5.2 宠物信息与领养申请主表结构

宠物表和申请表是这个系统的核心业务表,字段设计直接决定后续功能扩展的基础。宠物表我会这样建:

CREATE TABLE pet_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), type TINYINT COMMENT '宠物类型:1猫 2狗 3其他', breed VARCHAR(50), gender TINYINT COMMENT '0未知 1公 2母', age_month INT COMMENT '月龄', color VARCHAR(20), health_status VARCHAR(500) COMMENT '健康状况描述', vaccine_status TINYINT COMMENT '0未接种 1已接种', neuter_status TINYINT COMMENT '0未绝育 1已绝育', rescue_story TEXT COMMENT '救助经过描述', status TINYINT COMMENT '状态:0待安置 1待领养 2审核中 3已领养 4已回访', shelter_id BIGINT COMMENT '所属救助站ID', cover_image VARCHAR(255), images TEXT COMMENT '多图JSON数组', is_deleted TINYINT DEFAULT 0, create_time DATETIME, update_time DATETIME );

images字段用TEXT类型存JSON数组,前端上传后组装。不建议单独建一张图片表,因为宠物图片和宠物是一对一绑定的(虽然一个宠物有多张图片,但图片不会被其他宠物复用),TEXT类型足够,查询也是一次取全,不需要联表。

申请表的字段设计:

CREATE TABLE adopt_apply ( id BIGINT PRIMARY KEY AUTO_INCREMENT, pet_id BIGINT NOT NULL, user_id BIGINT NOT NULL, form_data TEXT COMMENT '申请表单JSON', status TINYINT COMMENT '0待审核 1已通过 2已驳回', auditor_id BIGINT COMMENT '审核人ID', audit_time DATETIME, reject_reason VARCHAR(500), create_time DATETIME );

form_data以JSON方式存储申请表单的完整内容,优点是不管表单字段怎么变化,表结构都不用改,查询时取出JSON解析就行。缺点是没法直接用SQL对表单内容做统计。在这个项目里,申请表单的内容变化频率高(不同救助站要求的表单可能不同),用JSON存储是划算的。

5.3 关联关系与冗余字段的取舍

救助站跟宠物是1对N关系,用户在宠物表里存shelter_id就可以了,不需要单独建关联表。救助站跟管理员之间是N对N吗?不是,管理员管所有救助站,救助站不归属某一个管理员,所以也不需要关联表。真正需要中间表的是用户和角色、领养者收藏宠物(用户和宠物多对多)。

收藏关系表:

CREATE TABLE user_favorite ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, pet_id BIGINT NOT NULL, create_time DATETIME, UNIQUE KEY uk_user_pet (user_id, pet_id) );

这里有个设计细节:给user_id和pet_id加联合唯一索引,防止同一个用户重复收藏同一只宠物。如果不加这个索引,数据层不做限制的话,前端按钮即使加了loading状态,连点两下也会插入两条记录,到时候又要写清理SQL,非常麻烦。

关于冗余字段,我的原则是:查询频率高、数据量小、变化频率低的字段,值得冗余。比如救助站名称,在宠物列表页面每条记录都要显示“来源救助站”,如果每次都要join查询shelter表,列表一页20条数据就是20次联表查询,虽然数据库扛得住,但没必要。在pet_info表里冗余一个shelter_name字段,每次更新救助站名称时同步更新该救助站名下所有宠物记录,用一条UPDATE就能搞定。这种操作在管理系统里叫“反范式设计”,用空间换时间,小项目里非常实用。

6. 开发环境与联调实操

6.1 本地环境搭建的关键步骤

新下载这个项目,要先有一个全局的目录规划。我一般建议这样操作:

第一步,安装基础环境。JDK 1.8以上、Maven 3.6+、Node.js 14+、MySQL 5.7或8.0。说实话MySQL版本用8.0是主流,驱动的url要配置时区参数,不然有报错。看你项目里pom里用的mysql-connector的版本,8.x的驱动连接url必须加上serverTimezone=Asia/Shanghai。

第二步,创建数据库并导入初始数据。项目里一般会带一个pet_adoption.sql文件,包含建表语句和初始数据。初始数据通常包含一个admin账号和几个救助站的测试账号。导入后要检查user表里的密码字段是什么算法存的基础数据的。如果用的是BCrypt加密,测试账号的密码字段会是加密串,你得从代码里找到默认密码,或者自己注册一个新账号,不然登录不了。

第三步,启动后端的顺序有讲究。先确保MySQL能在本机连接,再启动Spring Boot应用。Spring Boot启动过程中会检查数据源,连不上数据库就直接启动失败。启动成功后看日志里有没有“Started Application in X seconds”字样。

第四步,前端工程启动。npm install装依赖,npm run serve启动开发服务器。首次npm install会比较慢,视网络情况可能需要几分钟。装完后如果8081端口被占用,改vue.config.js里的port。

我把后端配置文件application.yml里最容易被忽略的几个配置给出来了:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_adoption?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: your-secret-key expire: 86400

很多同学启动起来之后,接口报错但从控制台看不到SQL,就是没配MyBatis Plus的log-impl。配了StdOutImpl之后,每次请求都能看到实际执行的SQL语句,排查数据问题效率高出一大截。排线上问题的时候再把日志级别调成INFO或者WARN。

6.2 启动顺序与前后端联调

联调阶段最常出问题的就是登录认证流程了。整个流程是这样的:前端调后端 /api/user/login,后端用JWT生成token返回给前端。前端把token存在localStorage里,每次请求在axios拦截器里加上Authorization头。

// axios 请求拦截器 axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config })

后端一旦在Spring Security里配好了JWT过滤器,那么拦截规则就得想清楚。常见的坑是:过滤器的拦截顺序不对,把登录接口和注册接口也拦了,导致前端一直登录失败。Spring Security的WebSecurityConfigurerAdapter配置里要把 /api/login、/api/register、/api/pet/list(宠物列表需要游客看)这些接口加入白名单,其他接口统一走认证。

前端路由守卫也要配合处理。如果用户没登录直接访问后台页面,Vue Router的beforeEach钩子里判断有没有token,没有就重定向到登录页。但注意一个问题:token过期后,后端返回401,前端不能只靠路由守卫判断,axios的响应拦截器也要监听401,清理本地token并跳转到登录页。我们实际联调时遇到的诡异问题就是token过期后只在路由守卫里判断,用户停留在页面不触发路由变化,结果页面一直是空白没有跳转,非常影响体验。

6.3 角色切换与权限验证

联调阶段验证权限功能,我一般用三套账号反复跳转:admin账号、救助站账号、普通用户账号。建一个Chrome无痕窗口和一个普通窗口,同时登录不同角色,方便测试权限隔离。

这里提醒一个容易漏的点:管理员接口要验证“普通用户不能访问”,光靠前端隐藏按钮还不够,还得测试接口层面。用Postman直接调管理员接口,如果返回403,说明后端权限校验生效;如果返回200,说明接口漏配了@PreAuthorize或者拦截器漏配了。

排查权限问题的时候,我习惯先在控制台打印当前用户的权限集合:

@GetMapping("/api/auth/permissions") public Result getCurrentPermissions(Authentication authentication) { Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); return Result.success(authorities); }

用前置登录后的token调这个接口,看返回的权限集合是否符合预期。如果只有一个ROLE_USER而没有ROLE_ADMIN,说明登录时角色加载逻辑有问题,大概率是查询角色时没有关联到用户。

7. 常见问题与排查技巧实录

7.1 登录401排查

前端登录接口返回401,通常的原因有几种:

第一,Spring Security默认的是表单登录,不是接口登录。你自定义的 /api/login 如果是普通的Controller接口,没有走Security的认证过滤器,配置不对的话接口根本执行不到就返回401。解决方案是在Security配置里放行这个接口,并禁用HTTP Basic和表单登录。

第二,密码加密方式不一致。数据库里的密码是明文还是BCrypt,注册时用的什么加密方式,登录校验时是什么方式,三处要一致。如果注册和登录的加密标准不一致,登录时就永远匹配不上。我处理过一个案例,数据库初始化脚本里的密码是MD5加密,而代码里用BCrypt校验,怎么都登录不进去,最后重新用BCrypt生成了种子数据才解决。

第三,前端表单提交的字段名对不上。后端接收的是username/password,前端传的是mobile/password,字段名对不上必然认证失败。联调时看到401先看后端日志有没有打印“Unsupported username or password”之类的输出,能很快定位。

7.2 宠物图片上传失败

图片上传是这类系统必然用到的功能,也最容易出问题。Element UI的上传组件配置是这样的:

<el-upload action="/api/upload" name="file" :headers="uploadHeaders" :on-success="handleUploadSuccess" list-type="picture-card"> <i class="el-icon-plus"></i> </el-upload>

最容易出的坑就是action路径跟前端代理路径对不上。前端把请求发给 /api/upload,如果后端上传接口是 /upload 且没有 /api 前缀,Nginx或者开发代理不会自动转换路径。建议所有接口统一加 /api 前缀,后端用context-path统一处理。

还有文件大小限制问题。Spring Boot内置的Tomcat默认单文件上传大小是1MB,超过就报错。宠物照片动辄2-3MB,会把用户整崩溃。要手动调大:

spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB

图片存到哪也是个问题。这个项目简单做法是存到本地磁盘,数据库存相对路径。上线部署时如果有多台服务器,本地存储就会失效,得上OSS或者云存储。考虑到这是学习项目,本地存储没问题,但要设计好一个BasePath配置,方便以后迁移到对象存储。

7.3 列表分页与筛选

列表分页筛选这个看似简单的功能,坑也不少。前端用Element UI的Pagination组件,传currentPage和pageSize给后端,后端用MyBatis Plus的Page对象接收,返回的数据结构包含records、total、current、size。

重点处理多条件筛选时的SQL组合问题。比如宠物列表要支持按类型、性别、状态、救助站、品种关键字组合筛选,MyBatis Plus的QueryWrapper写法:

public Page<PetInfo> queryPetList(PetQuery object) { Page<PetInfo> page = new Page<>(object.getCurrent(), object.getSize()); LambdaQueryWrapper<PetInfo> wrapper = new LambdaQueryWrapper<>(); if (StrUtil.isNotBlank(object.getType())) { wrapper.eq(PetInfo::getType, object.getType()); } if (StrUtil.isNotBlank(object.getGender())) { wrapper.eq(PetInfo::getGender, object.getGender()); } if (object.getStatus() != null) { wrapper.eq(PetInfo::getStatus, object.getStatus()); } if (object.getShelterId() != null) { wrapper.eq(PetInfo::getShelterId, object.getShelterId()); } if (StrUtil.isNotBlank(object.getKeyword())) { wrapper.and(w -> w.like(PetInfo::getName, object.getKeyword()) .or().like(PetInfo::getBreed, object.getKeyword())); } wrapper.eq(PetInfo::getIsDeleted, 0); wrapper.orderByDesc(PetInfo::getCreateTime); return petMapper.selectPage(page, wrapper); }

这里有个经验:所有列表查询默认都要加上 is_deleted = 0 的条件。逻辑删除是个好东西,一旦用过就回不去了,但千万记得所有Mapper查询都要加上删除标记过滤,不然删过的数据还会出现在列表里。用MyBatis Plus的@TableLogic注解可以自动处理逻辑删除和查询过滤,比手动加条件要省心。

前台列表页还有个常见需求:只显示“待领养”状态的宠物。这个在前端筛选时直接固定status=1传给后端就行,后端设计时把status作为可选查询参数,既方便前台固定条件,又方便后台按各种状态查询。

7.4 后台管理界面适配问题

三个角色的后台界面如果共用一套布局,要注意不同屏幕下的适配。Element UI的布局组件虽然响应式做得可以,但管理后台的大表格在窄屏上会横向溢出。我建议表格外面包一层div,设置横向滚动:

<div style="overflow-x: auto;"> <el-table :data="list" v-loading="loading"> </el-table> </div>

还有Form表单在弹窗里的布局,栅格数要根据屏幕宽度动态调整。小屏下把el-col的span改大一点,比如 xs=24, sm=12, lg=8。这些细节看着琐碎,但实际用户使用时感知很明显,尤其救助站工作人员很多是在手机或平板上操作的,移动端适配不做好的话,系统直接被嫌弃。

另外一个容易漏的地方是编辑界面回显。点“编辑”按钮弹出弹窗,弹窗里的表单需要预填当前信息。用深拷贝再赋值的方式比较安全,避免直接给响应式对象的表单字段赋值导致双向绑定混乱:

editForm(pet) { this.form = JSON.parse(JSON.stringify(pet)) this.dialogVisible = true }

直接this.form = pet,然后把form传给后端时如果用同一个对象,前端修改表单的同时也会修改列表数据,出现列表没刷新但显示已经变了的奇怪问题。深拷贝能稳定避免这个坑。

8. 个人实操心得与后续扩展建议

这套项目从头到尾撸一遍,说实话工作量不算小,但技术覆盖面很均衡。权限设计部分能练到Spring Security的整合,业务部分能练到状态机和一对多、多对多的关联关系设计,前端部分能练到Vue组件化和路由权限控制,再加上JWT认证、文件上传、数据统计这些高频需求,整个系统跑通之后对全栈开发的整体把握会提升很明显。

我真实验证过的一个心得是:在开发前先花几个小时把三张角色的“用户故事”画完,把每个角色要做的核心操作列成清单,再去设计数据库和接口,比直接上手敲代码效率高得多。这个项目如果一上来就建表写代码,大概率做到一半发现角色权限边界不对,要回头改表结构,返工成本非常高。

系统整个跑起来之后,后续可以扩展的方向还挺多的。一是数据可视化:管理员工作台加一个领养数据趋势图,统计每月的救助数、领养数、退养数,用ECharts做折线图或柱状图,非常直观。二是消息通知:现在申请结果是通过站内消息告知用户,可以扩展邮件或短信通知,救助站审核通过后自动发短信给领养人。三是回访提醒:定时任务扫描已领养宠物,超过约定时间没有回访记录就自动给救助站下发待办任务。四是移动端适配:把这套Vue管理端套上uni-app转成小程序版本,让救助站人员能用手机快速录入和更新宠物信息,在实际运营中这个需求非常强烈。

最后一个建议给正在做类似项目的人:不要被“三个角色”的模式绑住。真实业务场景里,救助站下面可能还分站管理员、物资管理员、医疗记录员等更多的细分角色,把这三个角色的权限边界想通透,后面加角色只是复制配置和调整菜单的事,架构上不需要推倒重来。这就是把权限设计做扎实的长期价值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 16:51:25

从0到2万Star:一份AI编程教程的开源实践与内容创作复盘

项目 Star 数跳到 2 万的那天&#xff0c;我盯着数字看了很久。从最初的个位数走到这里&#xff0c;中间设想过很多次“到了 2 万该怎么庆祝”&#xff0c;真到了反而很平静。真正让我意外的&#xff0c;是之后发生的一件事&#xff1a;有朋友截图给我&#xff0c;说在某头部 A…

作者头像 李华
网站建设 2026/10/22 16:50:35

动态规划从原理到实战:状态设计、转移方程与常见模型解析

1. 动态规划的核心原理&#xff1a;从两个本质属性说起动态规划这两个字&#xff0c;几乎出现在每个算法学习者的必读书单里。很多人在初学阶段记住了“状态”、“转移方程”、“边界条件”这些关键词&#xff0c;但真到动手解决一个没见过的题目时&#xff0c;还是会卡住——状…

作者头像 李华
网站建设 2026/10/22 16:46:31

一句话生成可交互原型?从Text-to-Prototype到D2C的实战全攻略

最近有个做产品的A同学兴冲冲地跑来跟我说&#xff0c;他只用一句话就让AI画出了一个App原型图&#xff0c;而且这个原型还能点击跳转&#xff0c;从首页到商品详情再到购物车&#xff0c;一条流程走下来有模有样。我听完第一反应是挺新鲜&#xff0c;第二反应是打开他发给我的…

作者头像 李华
网站建设 2026/10/22 16:46:30

2026年AI读书App五大核心能力测评维度

1. 为什么2026年的AI读书App&#xff0c;核心考的是这五个维度从2025年下半年到现在&#xff0c;我陆续把市面上主打AI能力的读书类App翻了个遍&#xff0c;有几个晚上甚至在同一本书、同一页上&#xff0c;用四个不同App做对比测试。回过头看这条路&#xff0c;我自己最大的感…

作者头像 李华
网站建设 2026/10/22 16:45:18

WinCC报表实战:从归档到自动化,打通工业报表核心链路

做上位机的人&#xff0c;十个里有八个被问过类似的问题&#xff1a;“这个数据能不能帮我导出来&#xff1f;”“能不能每天自动生成一份产量报表&#xff1f;”我第一次接触WinCC报表时&#xff0c;说实话没太当回事&#xff0c;以为就是个打印预览加一个表格控件&#xff0c…

作者头像 李华
网站建设 2026/10/22 16:44:30

llama.cpp与GGUF:本地大模型轻量化推理的核心技术解析

1. 项目概述&#xff1a;为什么说 llama.cpp 是本地大模型的“裸引擎”你有没有试过在自己笔记本上跑一个真正能对话、能推理、能写代码的大语言模型&#xff1f;不是调用某个网页接口&#xff0c;也不是点开某个APP——而是打开终端&#xff0c;敲几行命令&#xff0c;看着模型…

作者头像 李华