SpringBoot社区独居老人健康管理系统设计与实现
发布时间:2026/10/1 12:33:41来源:尧图网络
做毕设选这个题的同学十有八九是看中了健康管理这个自带现实意义的方向。社区独居老人的需求确实存在突发疾病没人知道、家政服务不好约、去趟医院要折腾半天。把这些问题放到一个系统里解决从课题答辩角度看既有社会价值可言又有完整业务闭环可讲技术点还能覆盖从前端到后端的全套流程属于典型的花一份功夫、拿全栈分的选题。这篇我按自己做这类项目的思路把SpringBoot社区独居老人健康管理系统的核心设计、实操细节、避坑经验完整走一遍。1. 项目定位为什么独居老人健康管理是毕设里的优选选题1.1 需求背景社区场景下的真实痛点独居老人这个群体的核心痛点是信息孤岛。老人的健康数据散落在医院病历本里子女没在身边不知道老人今天血压高不高社区网格员上门走访靠的是纸质记录家政服务靠老人自己打电话找医疗保健服务更是难以及时触达。针对这个需求设计一个系统核心要解决三件事一是把老人的基础健康档案数字化二是把健康监测数据集中管理并做异常预警三是把家政、医疗等服务预约流程线上化。这个逻辑链条非常清晰放到SpringBoot体系里实现既有数据处理的深度又有业务流转的复杂度答辩时能讲的东西足够多。从开发工作量角度说这题目不像纯粹的电商秒杀、秒杀系统那样需要硬扛高并发也不像纯信息管理那样单薄。老人管理、健康记录、服务预约、订单状态流转、权限控制、预警通知这几个模块串起来恰好能让整条技术栈全跑一遍工作量分布也很均匀不会出现某个阶段特别赶的情况。1.2 技术选型SpringBoot方案的优势选SpringBoot做这个题目最大优势是生态成熟、资料多、踩坑成本低。单体应用下SpringBoot通过一个内嵌Tomcat就能跑起来打包成jar直接部署开发调试都不用单独配置Web容器对毕设来说少了一堆环境上的麻烦。整个技术栈是这样的后端框架SpringBoot 2.x MyBatis-Plus操作数据库几乎不用写SQL数据库MySQL 5.7或8.0表结构控制在12到15张以内工作量适中前端如果走前后端分离用Vue3 Element UI如果不分离直接用Thymeleaf Bootstrap安全认证JWT做无状态登录省去Session会话管理一堆事定时任务Spring Task做健康数据检查和预警推送这套组合的合理性在于每个环节都有大量现成资料可查遇到问题不至于卡死。MyBatis-Plus的LambdaQueryWrapper写条件查询非常舒服分页插件一接就完事这是写数据CRUD效率很高的原因。JWT做登录拦截也免去了传统Session分布式问题的考虑毕设场景下完全够用。2. 系统架构与数据库设计先画好图纸再动手2.1 整体架构与模块划分系统按角色划分我是分了四类角色管理员、社区工作人员、老人家属、服务商。老人端负责查看健康报告、发起服务预约社区工作人员负责审核预约、登记健康数据、处理预警服务商负责接单和完成服务管理员管系统配置和用户管理。四类角色对应下来权限控制的粒度就很好划分。从功能模块看这套系统的核心模块包括老人信息档案管理含基础信息、紧急联系人、病史记录健康数据管理支持手动录入和批量导入记录血压、血糖、心率、体温异常预警模块根据阈值判断生成预警记录并推送通知服务预约模块家政服务和医疗保健服务两条业务线服务订单管理订单状态从提交到审核、接单、完成、评价通知公告模块支持站内信和短信接口预留数据统计分析用图表展示老人健康趋势和服务订单量模块与模块之间是有依赖关系的。健康数据先录入才能触发预警老人先有档案才能发起服务预约服务预约要有服务类型、服务商的信息支撑所有业务流程跑完后统计分析才有数据可用。设计的时候把这套依赖理顺后面编码就不会出现做着做着发现缺字段的情况。2.2 数据库核心表设计数据库设计是一个毕设项目能不能顺利开展的关键。表之间关联关系如果从一开始没理顺后面改表结构代价很高。这里给一份我实际用过的表设计参考共13张表基本覆盖了整个系统需求老人、用户与权限相关表elder老人信息表id、name、gender、age、id_card、phone、address、emergency_contact、emergency_phone、disease_history、create_timesys_user系统用户表id、username、password、real_name、phone、role_id、statussys_role角色表id、role_name、role_code、description老人和系统用户是分开的。老人在系统里只是一个被管理对象他的健康数据、服务订单都挂在elder表下系统用户才是真正登录系统的人。这样设计的好处是老人不需要登录由社区工作人员或家属代为管理权限边界很清晰。健康与预警相关表health_record健康记录表id、elder_id、blood_pressure_high、blood_pressure_low、blood_sugar、heart_rate、temperature、record_date、record_byhealth_alert预警记录表id、elder_id、alert_type、alert_value、threshold_value、alert_level、status、handle_result、create_time健康记录和预警记录分开存。体检或测量数据先落health_record程序检查到异常后再生成一条health_alert。这样做的好处是原始数据完整可追溯预警规则后续调整也不需要改动历史数据。服务相关表service_type服务类型表id、type_name、type_codeHOME_CARE/MEDICAL_CARE、descriptionservice_item服务项目表id、type_id、item_name、price、duration、description、statusservice_order服务订单表id、order_no、elder_id、item_id、provider_id、appointment_time、address、status、cancel_reason、create_time、finish_timeservice_provider服务商表id、provider_name、contact、phone、type_id、service_region、status家政服务和医疗保健服务都属于服务这个大概念服务类型表区分大类服务项目表挂具体细项服务商表标明属于哪个类型订单表再串联起来。查询某位老人的订单记录一条SQL就能处理业务扩展性也好。其他辅助表notice通知公告表id、title、content、publish_by、publish_time、statusmessage站内消息表id、send_to、title、content、is_read、create_timeoperation_log操作日志表id、user_id、operation、method、params、ip、create_timeblood_pressure_standard血压标准表id、age_min、age_max、systolic_min、systolic_max、diastolic_min、diastolic_max血压标准表单独建一张是一个容易被忽视但实际很有用的设计。老人年龄不同血压正常范围不一样预警判断不能用一个固定阈值。把标准范围做成表规则调整不需要改代码改数据就行答辩时讲出来也是加分项。表关联的重点就一句话老人是业务核心用户是操作主体所有业务表都挂elder_id所有操作行为都记录user_id。这两条主线理清楚了写Mapper和Service层就快很多。3. 核心模块实现细节3.1 健康档案模块CRUD里的隐藏难点健康档案模块看起来就是增删改查但有两个隐藏难点。第一个是病史数据的设计老人的既往病史是记录病史类型和确诊时间的关联结构不能只用一个varchar字段塞一段文字。第二个是健康记录的趋势展示页面折线图需要的是按时间排序的连续数据不是零散的查询结果。先解决病史结构。我是设计了一张elder_disease关联表elder_id对应disease_id再挂一个确诊年份字段。查询时用MyBatis-Plus的查询构造器一次性把关联数据取回来前端用标签展示。这样设计的好处是后续如果要做患高血压老人数量统计直接用SQL按关联关系统计就行不用做字符串模糊匹配。再说健康记录录入。血压、血糖、心率、体温这些指标我是全部列在health_record单表里录入时只填当天测得的数值。健康记录查询接口的核心代码如下public PageResultHealthRecordVO getHealthRecordPage(Long elderId, String dateFrom, String dateTo, int page, int size) { LambdaQueryWrapperHealthRecord wrapper new LambdaQueryWrapper(); wrapper.eq(HealthRecord::getElderId, elderId) .ge(StringUtils.hasText(dateFrom), HealthRecord::getRecordDate, dateFrom) .le(StringUtils.hasText(dateTo), HealthRecord::getRecordDate, dateTo) .orderByDesc(HealthRecord::getRecordDate); PageHealthRecord recordPage healthRecordMapper.selectPage(new Page(page, size), wrapper); // 转换为VO返回 return convertToVO(recordPage); }单表不带关联查询走索引快分页也简单。实际开发时你会发现去重、排序、默认值处理这些隐藏逻辑比CRUD本身更耗时。比如同一个老人同一天有两条记录页面显示时要合并还是分开展示我是保留了两条记录因为早晚各测一次的血压本来就不一样只要前端按时间排序展示用户自己就能看出来是两次测量结果。3.2 服务预约流程状态机的设计与推进服务预约是系统里业务逻辑最复杂的模块。一个订单从创建到完成要经历待审核、已接单、进行中、已完成、已取消、已退款这些状态。我直接在代码里定义了订单状态枚举public enum OrderStatus { PENDING(0, 待审核), ACCEPTED(1, 已接单), IN_PROGRESS(2, 进行中), COMPLETED(3, 已完成), CANCELLED(4, 已取消), REFUNDED(5, 已退款); private final int code; private final String desc; }状态流转不能乱跳。待审核只能流向已接单或已取消已接单只能流向进行中或已取消进行中只能流向已完成完成后可以申请退款。这一套状态机的判断逻辑放在Service层里每个状态改变方法的第一步就是校验当前状态是否允许流转。预约同时要处理冲突问题。同一个服务商上午10点接了A老人的家政订单就不要再接B老人同时间段的家政订单。解决方式是在查询服务商时间表时做一个时间区间重叠判断// 校验服务商在指定时间段是否已有订单 private boolean isProviderBusy(Long providerId, LocalDateTime startTime, LocalDateTime endTime) { LambdaQueryWrapperServiceOrder wrapper new LambdaQueryWrapper(); wrapper.eq(ServiceOrder::getProviderId, providerId) .in(ServiceOrder::getStatus, Arrays.asList(OrderStatus.ACCEPTED.getCode(), OrderStatus.IN_PROGRESS.getCode())) .and(w - w.between(ServiceOrder::getAppointmentTime, startTime, endTime) .or().between(ServiceOrder::getFinishTime, startTime, endTime)); return serviceOrderMapper.selectCount(wrapper) 0; }这个校验逻辑虽然简单但确实是服务类系统比纯信息管理多出来的核心工作量。毕设答辩时把时间冲突避免讲出来会让人觉得你的业务思考是完整的。订单状态变更时还要记录操作日志。我在订单模型的status_change_log字段里存了一段JSON格式是这样的[{from:PENDING,to:ACCEPTED,operator:李某某,time:2025-06-10 14:23:00}]每次状态变更就append一条记录。这样既不用单独建日志表又能完整追溯订单状态流转过程排查问题时能看到整个操作轨迹。3.3 异常预警与通知机制定时任务和规则引擎异常预警是系统里看起来高级、实现起来也顺的模块。核心逻辑是每次健康数据录入后立刻根据指标值判断是否触发预警同时定时任务每天固定时段扫描所有老人的最新健康记录发现异常就生成预警。这里牵扯到一个批量扫描的写法。如果是逐条处理全部老人量大了会很慢。我的做法是先查所有老人ID再用IN查询把每位老人的最新记录捞出来在内存里做规则判断最后统一批量插入预警记录public void scanAbnormalHealthData() { ListElder elders elderMapper.selectList(null); LocalDate today LocalDate.now(); ListHealthAlert newAlerts new ArrayList(); for (Elder elder : elders) { HealthRecord latest healthRecordMapper.selectLatestRecord(elder.getId()); if (latest null) continue; ListAbnormalItem abnormalItems alertRuleEngine.checkAbnormal(latest); if (!abnormalItems.isEmpty()) { HealthAlert alert new HealthAlert(); alert.setElderId(elder.getId()); alert.setAlertContent(abnormalItems.toString()); alert.setAlertTime(today); newAlerts.add(alert); } } if (!newAlerts.isEmpty()) { healthAlertMapper.insertBatch(newAlerts); } }预警规则判断的代码我是抽了一个AlertRuleEngine类单独处理。这样做的好处是规则判断逻辑和业务代码彻底解耦以后想加规则只改引擎类不动其他Service。通知机制这块简单的做法是生成预警后往站内消息表插一条记录老人端登录就能看到复杂点的做法是预留短信接口的位置用一个messageProvider接口抽象短信发送逻辑并在配置里做一个开关控制启用与否。毕设阶段做到站内信接口预留就够了说明你有扩展意识。定时任务的实现方式是用Spring内置的Scheduled注解Component public class HealthAlertTask { Scheduled(cron 0 30 8 * * ?) // 每天8:30执行 public void executeDailyScan() { alertService.scanAbnormalHealthData(); } }注意在启动类上要加EnableScheduling这是很多新手第一次跑定时任务没生效的原因卡在这里大半天才发现是少了这个注解。4. 关键代码与实操过程4.1 项目搭建与工程结构结构上我习惯按Controller、Service、Mapper、entity、VO、common这样划分。工程结构清楚评阅老师读代码也轻松。com.homecare ├── controller // 接口层 │ ├── ElderController.java │ ├── HealthRecordController.java │ ├── ServiceOrderController.java │ └── SystemController.java ├── service // 业务层实现 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── vo // 视图对象含分页返回体 ├── dto // 请求参数对象 ├── config // JWT、CORS、MyBatis-Plus配置 ├── security // JWT拦截器、权限注解 ├── common // 统一返回体、异常处理、工具类 └── task // 定时任务项目的依赖用Spring Initializr生成核心依赖就这几个dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.11.5/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency注意MySQL 8.0以后驱动类名变成了com.mysql.cj.jdbc.Driver配置里的driver-class-name不要写旧的com.mysql.jdbc.Driver新版驱动已经不带这个类了。这是一些同学在部署阶段经常遇到启动报错的元凶。配置方面开发环境和生产环境我分了两个配置文件spring: datasource: url: jdbc:mysql://localhost:3306/elder_care?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 username: root password: ${DB_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver密码用环境变量引用部署时在服务器上单独设置环境变量避免把真实密码硬编码提交到代码仓库里。虽然是毕设但这个习惯养成了比较好。4.2 登录鉴权与权限控制实现登录用的是JWT方案。用户登录成功后生成一个Token后续所有请求在Header里带上Token后端过滤器负责解析并校验身份。JWT生成的核心代码public String generateToken(SysUser user) { MapString, Object claims new HashMap(); claims.put(userId, user.getId()); claims.put(roleCode, user.getRole().getRoleCode()); return Jwts.builder() .setClaims(claims) .setSubject(user.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 24 * 60 * 60 * 1000L)) .signWith(secretKey, SignatureAlgorithm.HS256) .compact(); }拦截器做一个JwtInterceptor继承HandlerInterceptorAdapter或者实现HandlerInterceptor。只拦截除登录接口以外的所有请求解析不出合法Token的就直接返回401。这里有个容易踩的坑Token过期时间不要设置太短不然用户用着用着莫名被登出一开始没意识到是Token过期问题排查了半小时。毕设场景24小时过期完全够答辩演示时不会突然掉链子。除了登录鉴权角色权限同样要用拦截器或者注解做控制。我是用了自定义注解RequireRole(ADMIN)加上AOP切面来实现的方法执行前先判断当前用户角色是否满足注解要求。这样Controller方法上标一个注解就能控制权限代码写起来非常干净。4.3 前端页面的落地思路前端如果用Vue3 Element UI页面数量大概在10到12个之间登录页、首页看板、老人档案列表和详情、健康数据录入和趋势图、预警记录列表、服务项目管理、订单列表和详情、用户管理和角色管理。前台老人端页面偏轻查询为主、表单为辅。后台管理端要放各类筛选条件所以统一做一个列表页组件模板支持表格展示、分页、搜索、批量操作这些通用能力再通过配置项控制列显示。这样后端的十来个列表页面就不用逐一从头写。健康趋势图建议用ECharts。核心配置就一个option { xAxis: { type: category, data: recordDates }, yAxis: { type: value, name: 血压值(mmHg) }, series: [{ name: 收缩压, type: line, data: systolicData, smooth: true, connectNulls: true, lineStyle: { width: 2 } }] }前端对接后端时最大的问题不是图表怎么写而是日期和时间格式的统一。后端返回的LocalDateTime默认格式是一长串带T的字符串前端解析起来很别扭。解决方式是在配置文件里加一个统一的Jackson序列化配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8如果发现格式化没生效检查一下前端拿到对象的属性名和后端VO是不是一致经常会出现后端返回的是recordDate前端用record_time接收结果数据全是空的还以为是接口出问题了。5. 毕设常见问题与排查技巧实录5.1 高频问题速查表问题现象大概率原因排查手段启动报错Failed to configure DataSource数据库连接配置错误或mysql-connector版本和MySQL不匹配核对URL、用户名密码确认驱动坐标启动报错FileNotFound / ClassNotFound打包时resources没有被编译进去检查pom的resources标签配置接口返回数据为null但数据库有数据实体类属性没有加TableField或驼峰映射没开启确认map-underscore-to-camel-case: trueLocalDateTime返回数组而不是字符串SpringBoot版本高JS无法解析LocalDateTime默认格式加全局Jackson格式配置上传文件后找不到文件文件写到临时目录了配置文件存储路径为绝对路径分页查询total总是0MyBatis-Plus分页插件没配置注入MybatisPlusInterceptor并添加PaginationInnerInterceptor定时任务不执行启动类没加EnableScheduling检查启动类注解JWT的Token在网关/拦截器解析失败比较时用了不同的key或算法统一签名key和算法MyBatis-Plus分页插件配置这块确实要单独列出来。只引入了依赖但没在配置类里注入分页拦截器写好的selectPage会变成全表查询返回的total永远是0。这是MyBatis-Plus使用里最容易踩的坑之一Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(); paginationInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }5.2 部署上线的经验部署环节是很多同学的弱项。明明开发环境跑得好好的一到Linux服务器就各种报错核心原因是环境不一致。我建议用Docker来做部署虽然不是毕设的强制要求但会Docker部署确实是加分项。本地开发时的MySQL和服务器上的MySQL版本是同一个就用同一个避免一些莫名其妙的字符集问题。数据库导出时用mysqldump或Navicat的转储SQL文件导入服务器后需要确认SQL里没有包含本地独有的绝对路径。后端打包成jar部署的常用命令mvn clean package -DskipTests如果打包后运行报错no main manifest attribute说明缺少Spring Boot的maven插件在pom里加一下即可plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin打包完的jar放在服务器上用nohup启动nohup java -jar elder-care-system.jar app.log 21 日志输出到app.log方便后面排查。启动后访问端口确认接口能通再用Nginx配置前端静态页面和反向代理。前端构建产物直接放到Nginx的html目录配置一个server块把/api开头的请求转发到后端。依赖的部署顺序也要注意先装好MySQL并导入初始数据再启动后端jar等后端完全启动成功后再启动Nginx。很多同学把顺序搞反后端没起来前端页面打开自然全是接口报错。5.3 答辩演示注意事项答辩演示时最容易翻车的场景就是数据不够看。界面上只显示两三行空荡荡的记录老师一眼看过去感受不到系统的完整度。建议初始数据至少要有5位老人的完整档案、每人10条以上的健康记录、5个以上不同状态的服务订单、若干条预警记录。如果数据量太少页面上的图表、列表、统计卡片都是空白的演示效果会大打折扣。演示的流程提前顺着系统逻辑走一遍登录管理员账号→老人档案列表→打开老人详情看健康趋势图→录入一条异常健康数据→看到预警记录生成→老人端发起家政预约→后台审核通过→订单状态流转到已完成→首页报表数据更新。一条业务完整链路走下来系统能力就全展示了。最后分享两个加分项第一健康数据可视化。如果你的系统只是把数据列表呈现出来那是管理信息系统把数据做成趋势图、把服务订单做成月度统计、把预警做成立体报表那才叫健康管理。用ECharts做几个图表页面成本不高答辩加分很可观。第二考虑接入一些简单的设备数据模拟接口。不用真的接硬件做一个模拟设备上报的后台接口往健康记录表插入数据用来演示自动采集和手动录入两条途径。这在答辩时讲出来会显得你的系统不只是一个人工维护的台账而是有真实场景接入能力的。做这个项目我自己的体会是社区服务类系统的难点不在某个单独的技术而在于把身份权限、业务状态、数据流转这三条线穿起来。老人档案、健康记录、服务订单看似是三个独立模块实际运行起来环环相扣这个扣的过程才是真正锻炼工程能力的地方。如果你的时间紧张先用上面这套表结构和代码骨架搭起来剩下的细节在跑数据的过程中再慢慢补比对着空项目从零构思要快得多。
网站建设高端定制企业官网