Spring Boot智慧养老监护平台:多角色权限与数据库设计实战解析

📅 发布时间:2026/9/16 4:25:47
Spring Boot智慧养老监护平台:多角色权限与数据库设计实战解析
简介面向Java后端开发与毕业设计人群这份材料是一套基于Spring Boot的社区智慧养老监护管理平台设计与实现源码及论文配套资源。平台围绕管理员、后勤人员、护工、体检员、用户五类角色构建闭环业务覆盖房间信息与入住管理、老人健康状态档案、物资申请审批、留言反馈、公告发布等主要模块适合课程设计、毕业设计或Spring Boot综合项目学习。包体共473个文件约23.78MB以java后端、vue前端、sql数据库脚本为主配合xml配置、js交互、svg图标和bat一键部署脚本可帮助使用者快速跑通环境并理解前后端交互。目前已有241人学习。借助角色权限划分、数据库表结构以及部署脚本使用者不仅能复现完整的养老监护管理流程还可参考论文说明进行功能扩展是实战性较强的Spring Boot入门与进阶参考资料。1. 社区智慧养老监护平台一个Spring Boot工程如何撑起五类角色社区养老服务站的护工每天下班前要核对十几个房间的入住老人体检员要随时查老人的慢性病史后勤人员要盯着物资申请有没有人处理而家属最关心的是留言有没有人回。这套基于springBoot的智慧养老监护管理平台把五类角色塞进一个Spring Boot服务里管理员管房间和老人档案后勤人员查反馈和物资申请护工看入住和留言体检员看健康档案和公告用户提交留言和物资申请。对做java毕设或课程设计的人来说它最大的参考价值不是前端页面而是数据模型怎么落、角色权限怎么切、业务流程怎么保证不越权不重复。下面重点拆三个部分Mysql表结构设计、基于拦截器的权限控制、物资申请与留言的状态流转最后给出一套答辩现场用得上的构建和数据验证方案。2. 老人、床位、入住关系的Mysql表结构设计与状态字段取舍2.1 一张sys_user承载五类角色为什么先区分角色再建业务表平台里有管理员、后勤人员、护工、体检员、用户五种身份。很多人在课程设计里习惯建五张用户表后面做登录和权限判断时全是if-else每张表字段还高度重复。这个项目的做法是只建一张sys_user表用role字段区分身份角色维度上的业务权限交给接口层控制。CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(30), phone VARCHAR(20), role TINYINT NOT NULL COMMENT 0-管理员 1-后勤 2-护工 3-体检员 4-用户, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;角色字段用TINYINT而不是VARCHAR是因为代码里做权限判断时拿整数直接比对比如role 0表示管理员比字符串比较更干净也避免把“Admin”“admin”“管理员”各种写法混在一起。密码字段建议存MD5值MD5(123456)这样的密文用在演示系统里够用但如果你打算作为正式毕业设计提交最好换成BCrypt因为Mysql数据库一旦泄露明文密码就是安全事故。初始化数据时直接把五类账号造好答辩现场不用现注册INSERT INTO sys_user (username, password, real_name, role) VALUES (admin, MD5(123456), 系统管理员, 0), (logistics, MD5(123456), 后勤张姐, 1), (nurse01, MD5(123456), 护工小李, 2), (doctor01, MD5(123456), 体检员王医生, 3), (elder01, MD5(123456), 用户陈奶奶, 4);这里把“用户”也放进sys_user是很多智慧养老项目容易漏掉的一层。老人自己或者家属登录后提交物资申请、发布留言都需要一个身份标识user_id要能被后续业务表引用。如果单独建一张老人表再和用户表关联会多一次关联查询而这个项目里用户和老人的对应关系其实是一对一直接在业务表里冗余user_id字段即可。2.2 房间表与老人表的主数据字段设计房间信息管理是整个平台的基础数据来源护工查看入住老人、体检员查看老人健康状态最终都会落到房间和老人两张主表上。房间表的核心字段不是面积和装修而是床位数量和房间状态。CREATE TABLE room_info ( id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(20) NOT NULL UNIQUE, floor_no INT, room_type VARCHAR(20) COMMENT 单人间/双人间/护理间, bed_count INT DEFAULT 2, room_status TINYINT DEFAULT 0 COMMENT 0-空闲 1-部分入住 2-已满 3-维修, remark VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE elder_info ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT COMMENT 关联sys_user中的用户角色, name VARCHAR(30) NOT NULL, age INT, gender TINYINT, health_status VARCHAR(200) COMMENT 当前身体状态描述, chronic_disease VARCHAR(200) COMMENT 是否有慢性疾病, emergency_contact VARCHAR(50), emergency_phone VARCHAR(20), id_card VARCHAR(18), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );注意room_status和bed_count是两条信息bed_count是物理床位数量room_status是当前实际状态。管理员新增房间时room_status默认0当有老人入住时由程序判断已入住人数和bed_count的关系自动更新room_status为1或2。不能靠管理员手工改状态否则就会出现床位已满但room_status还显示空闲的错误。老人表里的chronic_disease字段建议存文本描述而不是布尔值比如“高血压II级”“糖尿病需胰岛素”体检员查看老人信息时直接读文本比看0和1更直观。health_status则是动态信息护工和体检员都可以在各自权限内查看管理员编辑老人信息时修改它。2.3 入住记录表用is_active保留历史床位数据房间入住管理是整个平台里最容易做错的一张表。最常见的错误写法是直接在room_info表里加一个elder_id表示当前谁住在里面。这样做的后果是老人退住后历史入住记录彻底丢失管理员无法统计每个房间住过多少人也无法回溯某段时间的入住情况。正确做法是单独建一张room_occupancy关联表每次入住生成一条记录退住时不物理删除只把is_active置为0CREATE TABLE room_occupancy ( id INT PRIMARY KEY AUTO_INCREMENT, room_id INT NOT NULL, elder_id INT NOT NULL, check_in_time DATETIME, check_out_time DATETIME, is_active TINYINT DEFAULT 1 COMMENT 1-在住 0-已退住, remark VARCHAR(200) );查询当前在住老人时过滤条件固定写is_active 1SELECT e.name, e.age, e.health_status, r.room_no, r.room_type FROM room_occupancy o JOIN elder_info e ON o.elder_id e.id JOIN room_info r ON o.room_id r.id WHERE o.is_active 1 AND r.room_status ! 3 ORDER BY r.room_no;这条SQL是护工端“房间入住查看”和后端数据处理的核心。is_active字段让退住操作变成一次UPDATE而不是DELETE查询历史记录时只需把条件改成is_active 0。另外外键在这个项目里不需要建物理约束因为管理员删除房间时可能提示外键冲突导致删除失败逻辑关联配合代码校验已经足够所谓外键留给数据库不如留给Service层。3. 基于拦截器与RequireRole注解的多角色接口权限控制3.1 为什么毕业设计不直接引入Spring SecuritySpring Boot入门阶段接触到的Spring Security配置复杂过滤链、UserDetailsService、密码编码器一套下来对课程设计而言太重。如果你在springboot面试题里被问到过Spring Security的过滤器链就知道它内部处理顺序稍微配置错接口就全部403。这个平台的五类角色权限边界非常清晰用拦截器加自定义注解就能解决代码量不到三十行可读性也好答辩老师问起来你能把每条路径的权限规则讲清楚。3.2 自定义RequireRole注解与拦截器实现先定义一个注解标注在Controller的方法上声明该方法允许哪些角色访问Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { int[] value(); }然后在拦截器里读取注解比对当前登录人的角色Component public class RoleInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod method (HandlerMethod) handler; RequireRole anno method.getMethodAnnotation(RequireRole.class); if (anno null) { anno method.getBeanType().getAnnotation(RequireRole.class); } if (anno null) { return true; } HttpSession session request.getSession(); Integer role (Integer) session.getAttribute(role); if (role null) { response.sendRedirect(/login.html); return false; } for (int r : anno.value()) { if (r role) { return true; } } response.setStatus(403); return false; } }这段代码的核心逻辑是先判断请求是否来自Controller方法如果不是直接放行避免静态资源被拦截然后依次找方法上的注解和类上的注解都没标就默认登录即可访问。拿到Session里的role后遍历注解的value数组命中任何一个角色就放行都不匹配返回403。注册这个拦截器时注意路径规划Configuration public class WebConfig implements WebMvcConfigurer { Resource private RoleInterceptor roleInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(roleInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login); } }这里/api/**匹配所有后端接口登录接口单独放行。登录成功后把userId和role放进Session后续所有接口都能通过Session拿到身份信息。拦截器和过滤器要区分一下过滤器是Servlet层面的拿不到HandlerMethod也就没法判断方法上的注解拦截器在SpringMVC内部能拿到方法对象所以注解权限判断用拦截器更自然。3.3 各角色核心接口路径与返回格式规划接口路径直接按角色前缀区分一眼能看出归属也方便拦截器按前缀理解权限范围角色典型接口方法说明管理员/api/admin/room/listGET分页查询房间信息管理员/api/admin/roomPOST新增房间管理员/api/admin/elderPOST新增老人信息后勤人员/api/logistics/material/listGET物资申请列表后勤人员/api/logistics/feedback/listGET反馈信息列表护工/api/nurse/occupancy/listGET查询入住老人护工/api/nurse/message/listGET留言查看体检员/api/doctor/elder/listGET老人健康档案查询体检员/api/doctor/notice/listGET公告查看用户/api/user/materialPOST提交物资申请用户/api/user/messagePOST发布留言接口Controller示例管理员新增房间RestController RequestMapping(/api/admin/room) public class AdminRoomController { PostMapping RequireRole({0}) public Result addRoom(RequestBody RoomInfo room) { roomService.insert(room); return Result.success(); } }RequireRole({0})表示只有管理员能调用护工和体检员即便知道接口地址也无法新增房间。角色枚举和注解配合权限的规则散落在各个Controller方法上比集中式配置直观。前后端分离部署时前端Vue项目打包后的静态文件放在src/main/resources/static目录下后端接口统一走/api前缀不存在跨域问题也就不需要额外配置CorsFilter。4. 物资申请与留言管理两条业务流程的状态设计与事务处理4.1 物资申请从提交到处理的乐观状态流转用户在“物资申请管理”界面新增一条申请后勤人员在“物资申请查看”界面看到后来处理。这个流程看似简单但它涉及状态变更容易出两类问题一是用户重复提交二是后勤人员并发审批同一条申请导致状态错乱。先看用户提交申请的ControllerPostMapping(/api/user/material) RequireRole({4}) public Result submitApply(RequestBody MaterialApply apply, HttpSession session) { apply.setUserId((Integer) session.getAttribute(userId)); apply.setStatus(0); // 0-待处理 1-已批准 2-已驳回 materialService.insert(apply); return Result.success(); }状态字段塞在申请单里0表示待处理。后勤人员处理时不能直接无条件更新状态而是要用CAS思想把“当前状态是0”作为更新条件PostMapping(/api/logistics/material/handle) RequireRole({1}) public Result handle(RequestParam Integer id, RequestParam Integer result) { int rows materialService.handleWithStatus(id, 0, result 1 ? 1 : 2, loginUserId); if (rows 0) { return Result.error(该申请已被处理请勿重复操作); } return Result.success(); }对应的SQL是关键Mysql的UPDATE语句自带行锁把期望状态放进WHERE条件里UPDATE material_apply SET status #{targetStatus}, handle_time NOW(), handler_id #{handlerId} WHERE id #{id} AND status #{expectStatus}如果两个后勤人员同时点击处理同一条申请Mysql的行锁会让第二个UPDATE等待等第一个提交后第二个的WHERE条件status 0已经不成立影响行数为0代码里rows 0的分支就会提示“已被处理”。这是典型的乐观锁写法比select后再update安全得多毕业设计里如果你在springboot配置了多数据源或者用了MyBatis-Plus同样能套用这个模式。4.2 留言与回复单表单回复字段还是父子表用户留言、护工查看留言、管理员回复留言这个模块的数据结构有两种设计思路。一种是建parent_id自关联父子表支持多级回复另一种是单表加reply_content字段一条记录存留言和回复。这个平台的需求是“用户新增留言并查看管理员回复”单表单回复字段就够了多级回复用不上反而增加查询复杂度。CREATE TABLE message_feedback ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 留言人, content VARCHAR(500) NOT NULL, reply_content VARCHAR(500) COMMENT 管理员回复内容, reply_time DATETIME, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;查询留言列表时需要把回复状态计算出来用IF函数在前端直接展示SELECT id, user_id, content, reply_content, create_time, IF(reply_content IS NOT NULL AND reply_content ! , 已回复, 待回复) AS reply_status FROM message_feedback ORDER BY create_time DESC LIMIT 0, 10;这里IF的判断条件要同时检查NULL和空字符串因为管理员可能只点了保存没填内容。LIMIT 0, 10做分页前端每次滚动加载传page参数对应SQL里的LIMIT #{offset}, #{size}。事务处理上用户提交留言和查询列表是读多写少的场景不需要加锁。但管理员回复留言时回复内容和回复时间要同步更新用Transactional保证一起成功或一起回滚Transactional(rollbackFor Exception.class) public void replyMessage(Integer id, String replyContent) { messageFeedbackMapper.updateReply(id, replyContent); }注意Transactional只能通过代理对象调用时生效如果在同一个类里调用带事务的方法事务会失效这是springboot面试题里常挖的坑实际开发时把事务方法放到独立Service类里。4.3 只读角色的查询优化与索引使用护工查看入住老人列表、体检员查看老人信息这两个功能本质上是多表关联查询。数据量不大时看不出差别但入住记录积累一年后room_occupancy表可能有上千条数据关联查询开始变慢。常用的优化策略是给外键字段和状态字段建联合索引ALTER TABLE room_occupancy ADD INDEX idx_room_active (room_id, is_active); ALTER TABLE message_feedback ADD INDEX idx_user_time (user_id, create_time);idx_room_active覆盖了“查某个房间当前谁在住”的场景idx_user_time覆盖了“用户查自己的留言列表”的场景查询时避免回表。体检员查看老人信息时只查elder_info主表按age、chronic_disease等字段做条件过滤如果有“按疾病类型筛选老人”的需求再对chronic_disease加普通索引但文本字段的索引长度要控制字符串前缀索引(10)就够。5. 答辩演示前的一键构建、数据验证与常见启动排错5.1 三个bat脚本的分工与参数项目里带了1-install.bat、3-build.bat、2-run.bat三个脚本很多第一次拿到源码的人容易按文件名顺序理解成执行顺序实际上是install、build、run三个阶段我的建议是答辩前一晚按这个顺序跑一遍echo off rem 1-install.bat清理并安装依赖到本地Maven仓库 mvn clean install -DskipTests -q pause echo off rem 3-build.bat打包成可执行jar mvn package -DskipTests -q pause echo off rem 2-run.bat以8080端口启动服务 java -jar target/smart-elder-care-1.0.0.jar --server.port8080 pause-DskipTests跳过测试减少打包时间-q安静模式只输出错误不刷进度条。java -jar后面的--server.port8080是Spring Boot的命令行参数优先级高于application.yml里的server.port配置现场如果8080被占用改成--server.port8081即可不用改文件重新打包。前端文件已经编译好放在static目录下包含index.html和chunk-vendors等静态资源Spring Boot内嵌Tomcat会直接把static目录映射为根路径启动后访问http://localhost:8080就能看到登录页。如果你的机器上没装Maven直接执行2-run.bat需要jar包已经存在否则会报找不到target目录。5.2 演示现场的数据验证技巧答辩现场演示时要避免“新增一条就刷新一下页面”的尴尬准备好两条SQL提前核对数据SELECT r.room_no, r.bed_count, COUNT(o.id) AS live_count FROM room_info r LEFT JOIN room_occupancy o ON o.room_id r.id AND o.is_active 1 GROUP BY r.id HAVING live_count r.bed_count;这条SQL查出所有还有空位的房间演示前先跑一遍确保登录管理员账号后“房间信息管理”列表里有空闲房间。用户端和后勤端的联动演示可以提前用用户账号提交一条物资申请状态设为0演示时后勤账号登录后直接就能看到待处理记录不用现场现填。Mysql连接配置检查重点看字符集和时区很多启动报错都出在这里spring.datasource.urljdbc:mysql://localhost:3306/smart_elder_care?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.password123456characterEncodingutf8解决中文乱码serverTimezoneAsia/Shanghai解决Mysql 8.x的时区报错。如果你用的数据库没有smart_elder_care这个库先执行CREATE DATABASE smart_elder_care DEFAULT CHARSET utf8mb4再导入sql文件。5.3 最常见的三个启动失败原因端口被占用是最常见的启动日志里看到Port 8080 was already in use在Windows下用netstat -ano | findstr 8080找到占用进程PIDtaskkill /pid 进程号 /f强制结束或者直接改启动端口。第二类是Mysql驱动版本不匹配springboot 2.x默认配mysql-connector-java 8.x如果本地是Mysql 5.7驱动也能兼容但URL里的driver-class-name要确认是com.mysql.cj.jdbc.Driver而不是旧的com.mysql.jdbc.Driver。第三类是页面白屏但接口正常打开浏览器F12看Console多半是static目录下前端资源引用了绝对路径/app.xxx.css这种开头少了一层context-path检查application.yml里有没有设置server.servlet.context-path如果设置了前端静态资源路径也要同步调整。本文还有配套的精品资源点击获取