Spring Boot社区养老系统实战:RBAC权限设计与核心业务实现 📅 发布时间:2026/9/18 7:19:21 👁 浏览次数: 简介这份毕业设计文档围绕基于Spring Boot的社区养老服务管理系统展开从选题背景、需求分析到ER图设计与权限管理完整呈现一套社区养老信息化方案。系统采用Spring Boot后端、Vue3前端与MySQL数据库设计了用户管理、健康管理、活动组织、紧急救援、家政服务等功能模块并通过RBAC实现管理者、工作者、医护人员、普通用户四种角色的权限区分。文档可作为计算机相关专业学生开展毕业设计的参考模板尤其适合准备开发前后端分离养老项目的读者能够帮助快速梳理系统功能结构和数据库建模思路同时涵盖中英文摘要、论文目录与各章节安排便于直接借鉴写作逻辑。整套资源为1个doc文件压缩包大小3.69MB内容紧凑目前已有358人学习使用值得正在选题或撰写论文的读者参考。1. 项目拆解Spring Boot 如何撑起一个社区养老服务平台一份社区养老服务管理系统的毕业设计文档看起来是典型的课程作业但里面对需求分析、RBAC 权限模型、ER 图设计的处理其实踩中了当前中小型管理系统开发的大部分关键点。这个系统的核心价值不在于功能多新颖而在于它的角色划分和业务流程——管理者、工作者、医护人员、普通用户四个角色在同一个平台上协作再加上健康档案、服务预约、活动报名这些模块几乎覆盖了社区养老场景下所有高频业务动作。我拿到这份资料后第一反应是它适合两类人看。一类是正在做 Spring Boot 相关毕业设计的学生可以直接借鉴它的模块划分和数据库设计思路另一类是准备接社区、街道、养老驿站类信息化项目的开发者这套系统的角色权限设计和业务表结构稍加改造就能复用到真实项目中。下文我按照「技术选型为什么这么定 → RBAC 权限怎么落地 → 核心表怎么设计 → 接口怎么实现 → 安全优化怎么做」的顺序把整份文档里的技术点掰开来讲。2. Spring Boot Vue3 MySQL为什么是这套组合2.1 Spring Boot 版本与项目骨架的选择逻辑这份文档选用 Spring Boot 做后端框架原因很直接它把 Spring 家族繁琐的 XML 配置全部干掉基于约定优于配置的思想开发者只需要关注业务代码本身。我从文档里看到它明确提到了 Spring Boot Dev Tools 的热部署能力这说明作者在开发效率上是有考量的。具体到上手我一般会用 Spring Initializr 生成基础工程依赖勾选 Spring Web、Spring Security、MyBatis-Plus、MySQL Driver、Lombok、Validation。这里有一个细节值得注意Spring Boot 2.7.x 和 3.x 在 javax 与 jakarta 命名空间上不兼容文档里没有写具体版本但从它使用 MyBatis-Plus 来看大概率是 2.x 系列。如果你是自己新建项目我建议直接用 2.7.18这是 2.x 的最后一个版本稳定且兼容性最好。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent这段配置的作用是锁定 Spring Boot 全家桶的版本号子模块不需要再单独声明版本。选择 2.7.18 的关键原因有两个一是 MyBatis-Plus 对它的支持最成熟二是 Spring Security 5.7 之后支持了 lambda 风格的授权配置写起来比之前的antMatchers方式简洁很多。2.2 ORM为什么选 MyBatis-Plus 而不是 JPA文档里点了 MyBatis-Plus 的名这个选择我完全认同。社区养老系统的业务大量涉及多表关联查询和动态条件筛选——比如按老人姓名、年龄段、服务状态组合查询健康档案——MyBatis-Plus 的 Wrapper 机制能把这种查询写得很直白。相比之下Spring Data JPA 虽然开发效率高但在复杂查询、SQL 优化和迁移学习曲线上都不如 MyBatis-Plus 可控。LambdaQueryWrapperHealthProfile wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(name), HealthProfile::getElderName, name) .ge(HealthProfile::getAge, minAge) .eq(HealthProfile::getStatus, NORMAL) .orderByDesc(HealthProfile::getCreateTime); ListHealthProfile list healthProfileMapper.selectList(wrapper);逻辑说明LambdaQueryWrapper通过::方法引用来指定字段避免了硬编码字符串列名编译期就能发现拼写错误。like方法做了空值判断name参数为空时不会拼进 SQLge是大于等于查询eq是等值查询。这段代码最后会被 MyBatis-Plus 翻译成带WHERE条件的预编译 SQL参数自动绑定不需要手动拼接。值得提一句MyBatis-Plus 的分页查询需要用分页插件才能生效。文档里提到的用户列表、订单列表都需要分页所以MybatisPlusInterceptor的配置是必须的不配置的话Page对象只会查出全量数据再内存截断数据量一大就会卡顿。我会在第四章的接口实现里演示具体配置。2.3 前端技术栈Vue3 Element-Plus 的工程化优势文档选择 Vue3 做前端这符合当前管理系统的开发常态。Vue3 的 Composition API 配合script setup语法写表单页、列表页这些重复性高的界面时比 Options API 代码量少三分之一左右。配合 Element-Plus 组件库后台管理界面的表格、弹窗、表单校验这些都能直接拖组件用。// 前端角色路由示例管理端只有 ADMIN 角色能访问 const routes [ { path: /admin, component: Layout, meta: { roles: [ADMIN] }, children: [ { path: users, component: UserManage, meta: { roles: [ADMIN] } }, { path: services, component: ServiceManage, meta: { roles: [ADMIN, WORKER] } } ] } ]需要说明的是前端路由守卫只是控制页面显隐真正的权限校验必须由后端完成。文档里的 RBAC 设计恰好能说明这一点——前端根据登录用户的角色动态生成菜单后端的每个接口再校验一次操作权限。双层校验的目的很明确防止有人绕过前端直接调用接口地址操作数据。2.4 MySQL 与数据库工具链MySQL 作为 RDBMS 的主流选择在中小型系统里几乎没有替代压力。社区养老系统的数据量级通常不会太大——一个社区几千名老人健康档案和工单记录在百万级以内——MySQL 的 InnoDB 引擎加合理的索引设计完全能扛住。开发流程中我习惯用 Navicat 做可视化建模先画 ER 图确定实体关系再用它的模型转 SQL 功能直接生成建表语句最后用 DBeaver 或 DataGrip 做日常查询调试。文档里的 ER 图设计部分就是这个思路从用户、角色、服务预约、健康档案等实体出发先把一对多关系理清再落成外键字段。3. RBAC 权限模型落地四种角色如何在一套系统里隔离权限3.1 角色矩阵管理者、工作者、医护人员、普通用户都能做什么这个系统的权限模型是标准的 RBAC只是把具体的角色固化成了四类。我从文档的需求分析里梳理出了权限边界可以用下面的矩阵直观对比。功能模块管理者工作者医护人员普通用户用户/角色管理全部权限查看查看无健康档案管理增删改查录入/更新增删改查查看本人服务工单处理分配/督办接单/完成参与处理发起/评价活动发布与报名发布/审核发布查看报名/取消紧急呼救处置查看全部接警处理查看一键呼救这个设计最值得借鉴的是把「医护人员」从「工作者」中单独拆出来。老年人健康数据的修改权限只开放给医护角色工作者可以录入测量数据但不能修改诊断类信息在权限粒度上做到了读写分离。3.2 数据库层面的 RBAC 表结构设计RBAC 的标准实现需要五张核心表用户表、角色表、权限表、用户-角色关联表、角色-权限关联表。社区养老系统的业务数据健康档案、服务工单、活动报名都要通过用户ID关联到具体的业务表。CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), phone VARCHAR(20), role_id BIGINT, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (role_id) REFERENCES sys_role(id) ); CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(50) NOT NULL UNIQUE, role_name VARCHAR(50) NOT NULL, description VARCHAR(200) ); CREATE TABLE sys_permission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, perm_code VARCHAR(100) NOT NULL UNIQUE, perm_name VARCHAR(100), parent_id BIGINT, type TINYINT ); CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) ); CREATE TABLE sys_role_permission ( role_id BIGINT NOT NULL, permission_id BIGINT NOT NULL, PRIMARY KEY (role_id, permission_id) );说明这个设计里我给sys_user加了一个冗余的role_id外键这是因为在多数业务查询中一个用户只对应一个角色直接冗余字段可以减少一次关联查询。如果你的系统将来要支持一个用户多个角色那就必须用sys_user_role关联表做多对多冗余字段的方案就要废弃。3.3 Spring Security JWT 的认证授权实现前后端分离的项目里Session 方案要让前端处理 Cookie跨域时还要考虑withCredentials的坑所以现在的主流做法是 JWT 无状态认证。Spring Security 负责拦截请求、解析 Token、校验角色权限业务服务只关注数据逻辑。Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/login).permitAll() .antMatchers(/api/admin/**).hasRole(ADMIN) .antMatchers(/api/doctor/**).hasRole(DOCTOR) .antMatchers(/api/worker/**).hasAnyRole(WORKER, ADMIN) .antMatchers(/api/elder/**).hasAnyRole(ELDER, ADMIN) .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }这个配置里最核心的两行是antMatchers(/api/doctor/**).hasRole(DOCTOR)和antMatchers(/api/elder/**).hasAnyRole(ELDER, ADMIN)——前者把医护人员接口锁死只有 DOCTOR 角色能调后者允许普通用户和管理者同时访问老年端接口因为管理者需要代老人操作一些功能。这里有一个 Spring Security 6 和 5.7 的差异需要注意5.7 里antMatchers还能正常用Spring Security 6 换成了requestMatchers而且废弃了WebSecurityConfigurerAdapter必须用SecurityFilterChain的写法。如果项目升级到 Spring Boot 3.x这段配置需要同步改掉否则启动直接报错。3.4 后端如何动态生成前端菜单接口返回菜单的逻辑是根据用户拥有的角色去查询sys_permission表再按parent_id组装成树形结构。前端拿到 JSON 后动态注册路由展示对应的菜单项。public ListMenuVO getMenusByUserId(Long userId) { // 1. 根据用户ID查询角色 ListLong roleIds userRoleMapper.selectRoleIdsByUserId(userId); // 2. 根据角色查询权限标识 ListString perms permissionMapper.selectPermsByRoleIds(roleIds); // 3. 查询所有菜单并过滤 ListMenu allMenus menuMapper.selectList(null); return allMenus.stream() .filter(menu - hasPermission(perms, menu.getPermCode())) .map(MenuVO::new) .collect(Collectors.toList()); }逻辑拆解第一步拿到用户所有角色ID第二步查出这些角色被赋予的权限编码第三步把菜单列表里不在权限范围内的项过滤掉。这个方案比前端写死路由要灵活因为后端改权限配置后前端菜单会自动跟着变不需要重新部署页面代码。4. 业务表设计与接口实现健康档案、服务预约、活动报名的核心链路4.1 ER 图核心关系与建表思路文档在前面给出了 ER 图的设计过程我在这个基础上把关键实体关系梳理成四组一对多关系是「一个老人可以有多条健康档案记录」「一个用户可下多个服务预约单」多对多关系是「活动与参与老人」之间通过报名记录表关联自关联是「菜单表」的父级菜单指向自身继承关系则通过角色字段区分医护和老人而不是单独建两张用户表。CREATE TABLE health_profile ( id BIGINT PRIMARY KEY AUTO_INCREMENT, elder_id BIGINT NOT NULL, blood_pressure VARCHAR(20), blood_sugar VARCHAR(20), heart_rate INT, height DECIMAL(5,2), weight DECIMAL(5,2), allergy_history VARCHAR(500), chronic_disease VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (elder_id) REFERENCES sys_user(id) ); CREATE TABLE service_appointment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, elder_id BIGINT NOT NULL, service_type VARCHAR(50), appoint_time DATETIME NOT NULL, address VARCHAR(200), status TINYINT DEFAULT 0, assignee_id BIGINT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (elder_id) REFERENCES sys_user(id), FOREIGN KEY (assignee_id) REFERENCES sys_user(id) ); CREATE TABLE activity_signup ( id BIGINT PRIMARY KEY AUTO_INCREMENT, activity_id BIGINT NOT NULL, elder_id BIGINT NOT NULL, signup_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0, UNIQUE KEY uk_activity_elder (activity_id, elder_id) );重点分析两个细节service_appointment表的order_no字段是唯一索引的业务单号不用数据库自增ID直接对外展示避免泄露业务量activity_signup表加了UNIQUE KEY uk_activity_elder(activity_id, elder_id)联合唯一索引从数据库层面就禁止了同一个老人重复报名同一场活动。4.2 服务预约接口完整的状态流转实现服务预约是社区养老系统里状态最多的业务流程从用户下单到工作者接单再到完成评价一共五个状态。我在实现时用status字段配合修改时间的字段来追踪每一步操作。PostMapping(/api/appointment) public Result createAppointment(RequestBody Valid AppointmentDTO dto) { // 1. 校验老人信息是否有效 User elder userService.getById(dto.getElderId()); if (elder null || !ELDER.equals(elder.getRoleCode())) { return Result.error(老人信息不存在); } // 2. 生成唯一业务单号 String orderNo SA LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyyMMddHHmmss)) RandomUtil.randomNumbers(4); // 3. 保存预约记录 ServiceAppointment appointment new ServiceAppointment(); BeanUtils.copyProperties(dto, appointment); appointment.setOrderNo(orderNo); appointment.setStatus(0); // 0-待接单 appointmentMapper.insert(appointment); return Result.success(预约成功, appointment); }参数说明Valid触发 DTO 里的字段校验注解比如NotNull(message 服务类型不能为空)校验不通过时 Spring 会自动返回 400 错误RandomUtil.randomNumbers(4)生成四位随机数拼在时间戳后面保证并发下订单号不冲突。接单操作则是通过UPDATE service_appointment SET status 1, assignee_id ? WHERE id ? AND status 0这样的乐观更新实现的WHERE条件带上status 0是为了防止两个人同时接单导致的状态覆盖。4.3 分页查询中的常见坑MyBatis-Plus 分页插件必须显式配置我见过很多刚用 MyBatis-Plus 的开发者写完selectPage一调用发现返回的总条数不对根源就是没配置分页插件。这个拦截器会把物理分页的LIMIT语句自动拼接进 SQL不配置的话Page对象查出来的列表其实是全量数据。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(100L); interceptor.addInnerInterceptor(pagination); return interceptor; } }setMaxLimit(100L)的作用是把单页最大查询量限制在 100 条防止有人把size参数改成999999直接把数据库拖垮。这个参数建议所有管理系统的分页插件都配上属于安全兜底手段。4.4 健康档案的数据隔离医护只能看自己的老人吗健康档案的权限设计比角色区分更细一层。文档里的角色矩阵写得很清楚医护人员可以增删改查健康档案但实际业务里必须加一层「归属人」的限制——医生A不应当看到医生B负责的老人的详细病历。这是行级数据权限问题靠 Spring Security 的角色注解解决不了需要在 SQL 层处理。常见做法是给档案表加doctor_id字段查询时强制拼接AND doctor_id 当前登录人ID。更正规的方案是引入 MyBatis-Plus 的DataPermissionInterceptor按注解自动拼接数据权限 SQL但一般毕业设计或小规模社区项目用不到那么重直接在 Service 层加过滤条件就够了。5. 接口安全与工程化统一响应体、参数校验、多环境打包5.1 前端拿到的数据格式必须统一前后端分离项目最常见的前端报错就是「Cannot read properties of undefined」根源往往是后端接口返回结构不一致——登录接口返回一个字段列表接口返回另一个字段。这个系统的接口数量有几十个不做统一封装前端联调时会被逼疯。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(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }这套封装的核心思想是所有接口统一返回code / message / data三层结构前端只需要判断code 200就取data否则弹出message。配合 Spring 的全局异常处理器业务代码里可以放心抛出自定义异常最终都会转成这个JSON格式返回。5.2 Spring Boot 多环境配置开发、测试、生产怎么切社区养老系统面对的是街道办、养老驿站这类运营方项目从开发到上线一般要经历开发环境、测试环境、生产环境三个阶段的部署。Spring Boot 的多环境配置通过application-{profile}.yml来实现切换部署只需要一个启动参数。# application-dev.yml spring: datasource: url: jdbc:mysql://localhost:3306/eldercare_dev?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root123 # application-prod.yml spring: datasource: url: jdbc:mysql://192.168.1.100:3306/eldercare_prod?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: eldercare_app password: ${DB_PASSWORD}生产环境的密码不要写在配置文件里用${DB_PASSWORD}占位符从环境变量或者服务配置中心读取。启动时用java -jar app.jar --spring.profiles.activeprod指定环境即可。Maven 打包时还可以配合maven-resources-plugin做资源过滤但更推荐启动参数动态指定这样打出来的包是同一个部署时决定跑在哪套环境上。5.3 密码加密不能再用 MD5这是我在评审项目时反复强调的一点。社区养老系统涉及老年人的健康档案、联系方式这些敏感信息用户密码如果明文存数据库或者用 MD5 加密密码一致就能直接比对撞库MySQL 一旦泄露所有账号就全废了。正确做法是 BF 加密BCryptSpring Security 原生支持直接用BCryptPasswordEncoder加密和比对。更重要的是BCrypt 的每次加密结果都不同同一密码两次加密出来的密文不一样但matches方法能够验证通过这是 MD5 和 SHA 系列都做不到的。5.4 初始数据预置让系统拿到就能跑起来文档里没有提但实际部署这类系统时数据库不会从空库开始——需要预置管理员账号、默认角色、基础菜单权限。我一般在data.sql或 Flyway 迁移脚本里做初始化确保系统第一次启动就能用admin账号登录。INSERT INTO sys_role (id, role_code, role_name) VALUES (1, ADMIN, 管理者), (2, WORKER, 工作者), (3, DOCTOR, 医护人员), (4, ELDER, 普通用户); INSERT INTO sys_user (id, username, password, real_name, role_id) VALUES (1, admin, $2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0c1SQJzON2TQ1b7Sgui, 系统管理员, 1);这里$2a$10$...是一段 BCrypt 密文对应明文admin123。注意不要直接在 SQL 里写明文密码否则数据库文件和数据备份一旦泄露账号就直接暴露了。Flyway 比data.sql更可控——它记录每次脚本的执行版本增量升级时不会重复执行但小型项目用data.sql加ON DUPLICATE KEY UPDATE也能达到目的。5.5 验证接口安全性的快速方法JWT 失效与角色越权系统上线前有一个简单有效的验证套路第一拿管理员 Token 访问/api/doctor/**的接口如果返回 403 说明角色拦截生效第二把 Token 的过期时间改成过去的时间戳再请求任意接口应该返回 401第三普通用户登录后手动修改请求路径去查别人的健康档案看后端是否校验了数据归属关系。这三步验证通过这套系统的权限和数据隔离基本就达标了。本文还有配套的精品资源点击获取