从零搭一个养老院健康管理系统:SpringBoot + Vue3 落地实录
养老院健康管理系统,本质上就是一个“医疗信息化+机构管理”的复合型项目。这两年养老行业数字化需求涨得很快,很多做Java后端的朋友拿到这类需求时,第一反应是“这不就是个普通的CRUD管理系统吗”,真正动手才发现,健康数据、权限划分、设备对接、异常预警这些东西远比想象中复杂。
我去年接手了一个养老院的健康管理系统开发,技术栈定的是SpringBoot + Vue3,最近刚做完二期交付。这一路踩了不少坑,也沉淀了一些经验。这篇文章不聊泛泛的理论,就围绕这个项目,把整体设计、核心模块、实操过程、排查技巧一条线讲清楚,希望能给正在做类似系统的朋友提供一些实际参考。
1. 项目整体设计与技术选型
1.1 为什么是 SpringBoot + Vue3,而不是其他组合
先说说技术选型的思路。养老院健康管理系统这种项目,有它自己的特殊性:一是业务逻辑不算特别复杂,但涉及的领域多——健康档案、护理任务、用药提醒、餐饮管理、家属沟通、设备数据接入,全都要覆盖;二是客户对部署和运维的要求往往很朴素,一套东西拿过去能跑起来,最好别搞太重的中间件;三是开发周期一般都比较紧,团队配合上要尽量降低沟通成本。
SpringBoot在这个场景下几乎是标准答案。它对复杂配置的封装做得足够好,内嵌容器、自动装配、监控端点这些能力开箱即用,团队里不管是谁,拉下来代码跑起来基本没有环境层面的障碍。对于这种业务系统,稳定、快速、好招人,这三个优势比技术上的花哨要重要得多。
前端选Vue3而不是Vue2,坦白说有一部分原因是新项目没必要用旧框架,但更关键的是组合式API带来的代码组织方式变化。健康管理系统的页面交互其实比一般后台系统复杂:健康趋势图、护理任务时间线、异常数据实时刷新、移动端平板适配,这些场景下组合式API的复用性明显更好。后来我们用Vue3的composition API把健康数据图表、任务卡片封装成几个通用组件,在不同页面里复用,开发效率确实上来了。
1.2 系统整体架构与模块划分
先把这个系统的整体架构画在脑子里,再谈细节。系统分三层:前端Vue3单页应用,后端SpringBoot微服务(按模块拆分成几个子服务),底层是MySQL存储业务数据、Redis承担缓存和分布式锁、EMQ X负责接收智能设备的推送数据。
从业务模块来看,我们一期规划了九个模块:
- 长者档案管理:基本信息、入住记录、家属联系人、既往病史
- 健康监测管理:生命体征(心率、血压、血氧、体温)、每日健康记录
- 护理任务管理:护理计划、任务派发、执行记录
- 用药管理:用药计划、服药提醒、用药记录
- 餐饮管理:配餐方案、饮食禁忌、特殊餐食
- 异常预警:体征数据越界、护理任务超时、紧急呼叫
- 家属服务:健康报告查看、在线探视预约
- 系统管理:用户、角色、菜单、操作日志
- 设备管理:智能手环、床垫监测仪等接入设备的维护与数据采集
这个模块划分不是拍脑袋定的,而是调研了几家养老机构和护理站的日常工作流程后梳理出来的。比如异常预警这个模块,最初客户的需求描述是“老人出问题我们要能及时知道”,后来细化成体征越界、任务超时、主动呼叫、离床监测四类场景,每个场景对应不同的处理流程,这样系统的落地性才有了。
1.3 数据库设计的几个关键决策
数据库设计上,有几点经验值得分享。
第一,健康数据表要按天分表或者加归档策略。生命体征数据是高频写入的,一个中等规模的养老院,按100位老人、每小时采集一次算,一天就是2400条记录,再加上设备上报,量会更大。如果所有历史数据都放在一张主表里,三个月后查询性能会明显下滑。我们最终采用按月分区的方案,热数据存在当前分区,过期数据自动归档到历史库。
第二,涉及金额和健康数据的表,设计时一定要加审计字段。健康数据的修改要留痕,不能是护工随手改一下就没了记录。我们在健康记录表里加了create_by、update_by、review_status这些字段,配合操作日志表,能做到全程可追溯。
第三,家属和长者的关系是典型的多对多。一位老人可能有多个子女,一个子女也可能关联多位老人(比如父母都在同一家养老院),所以中间表必不可少,而且联系人角色字段要有区分——紧急联系人、日常联系人、费用支付人,处理紧急情况时系统要先找紧急联系人。
2. 核心模块的设计与实现
2.1 长者健康档案:数据结构与生命周期管理
健康档案是整个系统的数据基石。在设计档案表时,我特别留意了“过敏史”和“既往病史”这两个字段。实际业务中,这两项信息往往是家属或者老人自己提供的,准确性没法保证,所以我们在录入流程里加了一个“来源与核实状态”的标记,区分“待核实”“已核实”“有出入”三种状态。这个是和养老院医务室沟通后加的需求,后来发现特别有用——急救场景下,护士能快速判断过敏史信息是否可靠。
档案管理的另一个重点是状态流转。老人从入住、在院、外出就医、转院到退住,整个生命周期都有状态变化。不同状态下,健康档案相关的操作权限是不一样的:比如出院外出期间,护理任务自动挂起,但健康监测设备仍然记录数据,回来后这些数据要标记为“院外数据”还是“院内数据”,方便后续分析。
2.2 生命体征监测与异常预警机制
生命体征模块是整个系统最具技术含量的部分,因为它涉及物联网设备对接、实时数据处理和预警判断。
设备接入这块,我们用的是EMQ X作为MQTT消息中间件。智能手环定时上报心率、血压、血氧、体温、步数等数据,也支持长者主动按键发起紧急呼叫。设备端以5分钟为周期上报一次数据,JSON格式,直接推到MQTT的topic上,后端服务通过订阅topic实时消费数据流。
刚开始是用简单的Java MQTT客户端直连EMQ X,后面发现设备多了以后连接管理很麻烦。改造后引入Spring Integration MQTT,声明式地定义消息通道和消息处理器,代码整洁了很多,重连机制、QoS级别管理这些都交给框架处理了。
预警规则设计上,不是简单写死一个阈值,而是采用“分级预警+动态规则”的设计。比如血压,收缩压高于180mmHg触发红色预警,高于160mmHg触发黄色预警;低于90mmHg触发红色预警。心率低于50次/分或高于120次/分都要触发预警。体温超过38.5℃触发黄色预警,超过39.5℃触发红色预警。贯穿着的一个设计原则是:宁可多报,不能漏报,但多报的信息要有合并策略,避免连续预警把护理人员“轰炸”到麻木。
2.3 护理任务派发:从计划到执行的闭环
护理任务这块,一开始我们想得太简单了,以为是简单的“创建任务-指派-完成”三步走。真正深入业务后发现,护理工作有很强的周期性、依赖性和不确定性:喂药要在饭后半小时内执行,翻身要每隔两小时做一次,不同老人的护理等级决定任务优先级,临时请假、换班都会影响任务派发。
所以护理任务模块最终设计成了这样:
- 护理计划模板:管理员(一般是护士长)先配置护理计划,比如“三级护理每日任务”,包含哪些护理项目、执行时间、间隔周期
- 自动生成实例:系统根据计划模板,结合老人的入住日期和护理等级,自动生成每日的任务实例
- 派发策略:默认按护理区域自动派发给负责该区域的护理人员,支持手动调整
- 执行闭环:护理人员完成一项任务后,要在移动端确认并填写执行情况,未按时完成的会自动进入待办提醒队列
- 交接班设计:每班次结束时生成交接清单,下一班次接班时需确认任务交接情况
2.4 用药管理:复杂规则下的提醒与记录
用药管理是我个人认为最容易踩坑的模块。药品的服用频次、服用时机、剂量、疗程、停药条件,这些规则组合起来非常多。有些药是“一天三次”,有些是“隔天一次”,有些是“第一周每日一次、之后每周一次”,有些要和食物同服,有些要空腹。
刚开始试着把所有规律塞进一张表里,很快就发现查询复杂、维护困难。最后拆成了两张表,一张存用药方案主档(老人、药品、剂量、开始日期、结束日期、状态),另一张存具体的服药计划(每天的执行时间点),由方案在创建时根据规则生成未来30天的计划。
服药提醒通过WebSocket实时推送提醒消息给移动端,护理人员收到提醒后点击“确认执行”,系统记录确认时间和执行人。如果超时未确认,系统每15分钟重复推送一次,超过1小时仍未确认的,自动升级为异常事件通知护士长。
2.5 家属服务与在线健康报告
家属服务是养老院选择这个系统时很看重的一个模块——某种程度上,它甚至可能是影响签约的关键功能。系统为每位长者关联1到3位家属,家属登录后可以看到老人的健康趋势、护理记录、用药情况、费用明细。
这里有一个政治敏感度不低的技术点,涉及隐私与数据授权。我们做了双层的权限控制:第一层,家属角色只能访问与自己关联的老人数据;第二层,隐私数据(比如精神状况记录)需要单独授权,不是所有家属都能看到。技术上就是Spring Security的权限体系 + 数据级授权相结合,线下签署的授权书是业务前提。
健康报告生成是Vue3前端负责的,引用了一个开源图表库,把心率、血压等数据按时段展示成趋势图。刚开始用的是ECharts,后来因为包体太大,换成了轻量级的Apache ECharts按需引入,体积从800多KB压缩到300多KB。
3. 实操过程与核心环节实现
3.1 后端工程搭建与统一响应封装
后端工程搭建,我用的是Spring Boot 2.7.18版本。特意提一下版本,是因为之前踩过坑——Spring Boot 3.x推出来以后,很多老项目的依赖还没适配,尤其是若依这类快速开发框架,3.x的改动会导致不少兼容问题。做这类业务系统,我的原则是:选稳定版本,而不是追新版本。
工程结构采用多模块Maven项目,按业务领域拆包:
health-admin // 系统管理模块 health-elderly // 长者档案模块 health-monitor // 健康监测模块 health-nursing // 护理任务模块 health-medication // 用药管理模块 health-alert // 异常预警模块 health-common // 公共工具与统一响应 health-gateway // API网关统一响应类是必做的,不能每个Controller返回不同类型。我们的响应结构是code、message、data三段式,code为200表示成功,其他为业务错误码。另外,分页查询有一个固定的返回结构(records、total、current、size),用MyBatis-Plus的分页插件即可实现。
3.2 认证与权限设计:Spring Security + JWT的落地配置
认证授权这块,采用的是Spring Security + JWT的方案。为什么不用Session?因为这个系统需要同时支撑Web端、移动端(护工用pad)、家属小程序端,Session在多端场景下扩展性差,JWT无状态、跨端友好,更适合微服务架构。
权限模型上做了“用户-角色-菜单-权限点”四层设计。JWT的payload里只存userId和tokenId,不直接存角色信息——角色是动态变化的,如果写进token里,角色变更后只能等token过期,这点和若依框架的思路不一样,踩过坑后的经验之谈。
这里给一段核心配置代码:
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/login", "/api/auth/refresh").permitAll() .antMatchers("/api/**").authenticated() .anyRequest().permitAll() .and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler(customAccessDeniedHandler); // 添加JWT过滤器 http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); }JWT的过期时间设计的陷阱比较多,不要设太长也不要太短,Web端是2小时,移动端是7天。另外刷新token的接口是必须的。实际使用中,如果前端发请求时发现token过期(后端返回特定错误码),就用refreshToken去换取新的token,用户无感续期。
3.3 Vue3前端工程:Vite + Pinia + Element Plus的集成方案
前端用Vite搭建工程,这个几乎没什么悬念。Vue3 + Vite的体验比Vue2 + Webpack强太多了——冷启动速度、热更新速度都是质的提升,开发时几乎感受不到等待。
状态管理用的是Pinia而不是Vuex。Pinia对TypeScript的支持更好,API设计更简洁,去掉了mutations,直接在store里定义actions。健康管理系统的全局状态不算特别多,主要存用户信息、权限点、系统配置、消息通知,用Pinia管理清爽不少。
UI组件库选了Element Plus。对比过Ant Design Vue、Naive UI和Element Plus,最终选Element Plus的核心原因:一是社区资料多,遇到问题好查;二是表单组件、表格组件和我们的业务场景贴合度高;三是和Vue3的配合成熟度高。也有一个小坑是表格样式部分浏览器渲染有差异,特别是Edge浏览器下偶发列宽异常,查了一圈发现是Element Plus的样式和默认的flex布局在某些版本上有冲突,升级版本后解决。
前端路由设计上,采用动态路由注册方案,根据后端返回的菜单权限动态生成路由表。这样做的直接效果是:不同角色登录后看到的菜单天然是不一样的,不需要前端去写一堆v-if/v-show判断。
3.4 数据可视化:健康趋势图表的实现
健康趋势图这部分,用ECharts是实现,但需要注意几个细节。
第一,数据粒度。前端拿到的原始设备数据可能是5分钟一条的,直接画到图表上会很密,视觉上全是毛刺,看不清趋势。我们会在后端做一次降采样处理,按小时取均值,前端拿到的是每小时一个点,画出来的图平滑清晰。
第二,异常点高亮。血压图中,高于正常区间的点用红色标记,低于区间的用蓝色标记,配合的参考线(135/85mmHg)也画出来。这样护理人员一眼就能看到哪个时间段的体征异常,不用自己去对比数值。
第三,大屏适配。养老院的护士站一般会挂一个电视屏幕显示全院的数据看板,这个页面的分辨率是1920x1080,但有些老旧电视可能只支持到1366x768。我们做了一个基于rem的响应式方案,图表根据容器自适应,效果还不错。
3.5 移动端适配:护工平板端的关键设计
护工日常干活基本不用PC,而是用平板和手机。所以我们的前端除了PC管理后台,还有一个移动端H5应用,跑在平板上。
移动端的页面设计和PC端有很大区别。护理任务列表不是传统的表格,而是卡片式的滑动列表——每个任务卡片显示老人姓名、房号、任务类型、预计完成时间,卡片颜色根据优先级不同。执行任务时,拍照留痕是必须的,这里用了前端压缩 + 后端校验的方案,照片大小控制在500KB以内,避免占用太多带宽和存储。
平板端的网络环境需要考虑离线情况。养老院的网络覆盖不一定好,尤其是走廊尽头和户外活动区。我们做了一个轻量级的离线缓存方案:任务列表拉到本地,执行结果先存在localStorage,网络恢复后再批量同步。这部分的坑主要在处理“任务已在他处被取消”的并发冲突,后端通过任务版本号解决。
4. 常见问题与避坑指南
4.1 体检报告PDF生成:模板渲染的坑
健康管理系统必然涉及体检报告、健康评估报告的生成和导出。我们用了多套方案对比,最终还是选了模板渲染PDF——先由美工设计Word模板,再用Java代码填充数据后转换成PDF。这个方案的优点是排版美观,客户满意度高;缺点是服务器需要安装字体库,中文字体缺失会出现乱码。
常见问题是服务器部署到Linux后,PDF输出的中文全是豆腐块。排查半天,最后确认是系统没安装中文字体。解决方法是把中文字体文件复制到服务器的字体目录,执行fc-cache刷新字体缓存,重启服务就好。
4.2 大屏展示数据实时刷新
护士站大屏需要一个实时刷新的数据看板,展示当前在院人数、今日护理任务完成率、异常预警数量、各区域老人分布情况等指标。
第一版做法是前端每10秒轮询一次后端接口,实现是实现了,但这个方案有两个毛病:一是10秒的延迟对“紧急呼叫”这类实时性要求高的场景不够用;二是轮询产生大量无效请求,高峰期后端压力不小。
后来改成WebSocket长连接 + Redis发布订阅。后端收到新的预警事件、呼叫事件时,先写入MySQL持久化,再发布消息到Redis频道,每个服务实例通过订阅频道收到消息后,推送到各自连接的WebSocket客户端。这套改造做完后,大屏的实时性从10秒提升到1秒以内,后端请求量下降了80%以上。
4.3 历史健康数据的归档策略
随着系统运行时间增长,健康监测数据会快速增长。三个月后,主表的单月数据量可能超过百万条,查询健康趋势时响应会变慢。
我们的做法是按月分区 + 归档任务。
ALTER TABLE health_record PARTITION BY RANGE (TO_DAYS(record_time)) ( PARTITION p202401 VALUES LESS THAN (TO_DAYS('2024-02-01')), PARTITION p202402 VALUES LESS THAN (TO_DAYS('2024-03-01')), PARTITION pmax VALUES LESS THAN MAXVALUE );配合一个定时任务,每月初把上上个月的数据从主表迁移到历史库,主表始终保持最近两个月左右的热数据。这样查询热数据时,即使索引完整,数据量也控制在一个合理的范围内,SQL效率高了很多。
4.4 部署环境的兼容性
这套系统部署时,遇到过几个环境相关的坑,列一个速查表供参考:
| 问题 | 排查方法 | 解决方案 |
|---|---|---|
| Linux下Excel导出乱码 | 字体缺失 | 安装中文字体,执行fc-cache |
| Redis连接超时 | 防火墙或配置错误 | 检查6379端口连通性、密码是否正确 |
| 定时任务不执行 | 时区不对 | 设置JVM时区:-Duser.timezone=Asia/Shanghai |
| MySQL连接数打满 | 连接池配太大 | 根据并发调整HikariCP的maximumPoolSize |
| 前端白屏 | 路由history模式部署问题 | 配置Nginx,将非API请求转发到index.html |
4.5 浏览器兼容性
前端开发时发现的Edge浏览器兼容问题也值得记录一下。系统在Edge浏览器下使用tabs标签页时,偶尔出现无法正常切换或者样式错乱的问题。排查后定位到是Element Plus的tabs组件在动态渲染tab-pane时和浏览器的渲染机制有冲突,把tabs组件的type属性从默认值改成更简单的样式类型后解决。
还有一个小众但容易忽略的问题:自动填充。养老院的管理员电脑一般都比较老旧,部分浏览器对日期选择器(input type=date)的兼容性有差异,有的显示不了日历控件。后来我们把所有日期组件都换成了Element Plus的DatePicker,从根源上解决了这个问题。
5. 经验总结与运维心得
整个项目做完两期,前前后后花了大约五个月时间。回过头看,有几个认知值得分享:
第一,需求沟通要走到业务现场。整个项目过程中,最有价值的一次决策,就是在正式开发前,团队花了一周时间驻点在合作的养老院,观察护理人员的日常工作流程。很多需求如果只坐在办公室听客户描述,一定会做偏。比如护理任务的“交接班”逻辑,如果不去现场看,根本不会理解为什么会有那么复杂的交接规则。
第二,技术选型要克制。做这个项目时,团队里有同事提过要不要上微服务全套、引入分布式事务。我评估后觉得完全没必要——业务规模、数据量、团队配置都不支撑那么重的架构。最终是模块化单应用 + 可选的服务拆分边界,既保证了代码边界清晰,又避免了过度设计带来的运维负担。SpringBoot + Vue3这套组合在这个体量的项目中,就是最稳的选择。
第三,做养老行业项目,要把“安全”刻在骨子里。这里的安全不只是网络安全,更是业务安全——老人的健康数据准确性能决定生死,系统的异常预警哪怕慢一分钟,都可能造成严重后果。所以在设计预警模块时,我们宁可多一点误报,也不放过任何一个真实异常。功能上线前,团队内部做了好几轮模拟演练,专门测试“心电异常”“跌倒检测”“呼叫无响应”这些极端场景。后来养老院那边反馈,正因为系统对这些异常情况响应及时,他们才放心让家属安装和使用这个系统。
另外一个小技巧,也是我认为最实用的:把项目文档当作代码一样对待。这个系统迭代了十几个版本,如果没有完整的接口文档、数据库说明、部署手册,后面的维护成本会翻好几倍。我们用的方式是接口文档同时维护Swagger注解和项目wiki,数据库变更通过Flyway做版本管理。新同事上手,照着文档就能把整套系统跑起来,这就是最大的效率。
如果后续要做二期扩展,我认为这几个方向值得考虑:对接更丰富的智能硬件(比如智能床垫、防走失手环)、引入AI辅助健康风险评估、做多机构SaaS化改造、增加语音交互方便老人使用。每个方向都有实际需求支撑,就看业务运营节奏怎么安排了。