news 2026/9/16 4:25:43

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

角色字段用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置为0:

CREATE 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 = 1

SELECT 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 Security

Spring 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 物资申请:从提交到处理的乐观状态流转

用户在“物资申请管理”界面新增一条申请,后勤人员在“物资申请查看”界面看到后来处理。这个流程看似简单,但它涉及状态变更,容易出两类问题:一是用户重复提交,二是后勤人员并发审批同一条申请导致状态错乱。

先看用户提交申请的Controller:

@PostMapping("/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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

查询留言列表时,需要把回复状态计算出来,用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.port=8080 pause

-DskipTests跳过测试减少打包时间,-q安静模式只输出错误不刷进度条。java -jar后面的--server.port=8080是Spring Boot的命令行参数,优先级高于application.yml里的server.port配置,现场如果8080被占用,改成--server.port=8081即可,不用改文件重新打包。

前端文件已经编译好放在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.url=jdbc:mysql://localhost:3306/smart_elder_care?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456

characterEncoding=utf8解决中文乱码,serverTimezone=Asia/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找到占用进程PID,taskkill /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,如果设置了,前端静态资源路径也要同步调整。

本文还有配套的精品资源,点击获取

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

宿舍用电安全升级:离人断电系统原理、选型与部署实战

开学季刚过&#xff0c;后勤群里又有老师吐槽&#xff1a;学生宿舍忘拔充电器引发的小火情、电吹风过热跳闸、人走不关空调导致电费飙升。这些问题背后其实都指向同一个需求——离人断电。这些年我参与过不少高校学生公寓的用电安全改造&#xff0c;从最早的机械式定时器&#…

作者头像 李华
网站建设 2026/9/16 4:25:38

基于ADPD188BI和RA8D2K的高性能烟雾探测系统设计

这几年做消防报警相关的产品选型&#xff0c;我把主流的光学烟雾传感器方案都摸了一遍&#xff0c;最终定下来的组合是ADI的ADPD188BI配合瑞萨的R7KA8D2KFLCAC&#xff08;RA8D2K系列&#xff09;。这套方案要解决的核心问题很直接&#xff1a;比传统光电烟感更早发现阴燃火&am…

作者头像 李华
网站建设 2026/9/16 4:25:36

SCDUNet++与迁移学习在滑坡测绘中的技术解析

成都理工的滑坡测绘工作最近讨论度挺高&#xff0c;很多人看到 SCDUNet 这个模型名一头雾水——这到底是 UNet 的哪一代变种&#xff0c;凭什么跟迁移学习搭在一起就能提升滑坡识别精度&#xff1f;我本身做过几年遥感影像语义分割&#xff0c;也踩过滑坡样本不足的坑&#xff…

作者头像 李华
网站建设 2026/9/16 4:25:06

鸿蒙ArkTS @Styles装饰器:样式复用最佳实践与避坑指南

看到这个标题&#xff0c;估计不少刚开始接触鸿蒙 ArkTS 声明式开发的朋友都会有点懵——样式复用直接用公共类不就行了&#xff1f;为什么还要专门搞一个 Styles 装饰器&#xff1f;说实话&#xff0c;我刚开始也这么想。但真正在 HarmonyOS 应用开发里写多页面、多组件的时候…

作者头像 李华
网站建设 2026/9/16 4:24:02

OpenClaw:让大模型驱动具身机器人,从原理到实践

最近一两年&#xff0c;AI智能体&#xff08;Agent&#xff09;这个概念几乎被聊烂了&#xff0c;但绝大多数讨论都停留在“对话机器人”或者“自动写文案”的层面。直到我接触了 OpenClaw 这个开源项目&#xff0c;才真正感觉到智能体从“数字世界”走向“物理世界”的那条路&…

作者头像 李华
网站建设 2026/9/16 4:23:50

内网环境下TiDB周边Agent离线部署与配置完整指南

最近帮一个团队交付一套完全隔离的数据库集群&#xff0c;网络环境很严格&#xff0c;除了 SSH 端口&#xff0c;其余端口都要走流程申请放行&#xff0c;服务器不能访问外部软件源&#xff0c;也不能随便装包。TiDB 集群本身倒是装得顺利&#xff0c;官方离线包一次搞定&#…

作者头像 李华