做智慧养老云服务平台这个项目,我在真正动手之前一直以为它就是给养老院做一个信息管理系统。等到后面把需求摸透、把代码写完、再跟着实际业务跑了一轮之后,才意识到这个系统真正难的不是CRUD,而是怎么把线下那套繁琐的照护流程、异常告警、家属同步、机构管理全部串起来。如果你正打算用Vue3加Spring Boot做类似的平台,或者已经接到了相关需求,这篇文章应该能帮你少走很多弯路。我尽量把设计思路、核心实操、遇到的坑都讲清楚,内容会比较长,但每一段都是实际经验。
1. 项目定位与整体设计思路
1.1 智慧养老平台到底在解决什么问题
刚开始接触这个项目需求时,需求文档里写了一大堆功能,护工排班、老人档案、健康监测、家属App、工单管理,看着特别热闹。但我和对接的养老机构负责人聊完之后发现,真正要解决的其实是三类问题。
第一类是照护过程线上化。以前护工做了什么全凭手写记录,老人几点吃药、几点测血压、今天有没有出去活动,很多信息晚上才补录。平台要做的就是把排班、工单、护理记录、打卡全部变成线上闭环。第二类是异常可观测。老人独自在家、或者机构里出现跌倒、心率异常、长时间没有活动,这些不能等出了问题才被发现。系统必须要有实时告警和通知机制。第三类是服务可追踪。家属想知道老人今天过得怎么样,机构管理者需要知道每个护工的工作量,监管方要能审计服务记录。平台本质上是在中间串联起老人、家属、护工、管理者这几类角色。
所以这个项目虽然技术上是典型的前后端分离应用,但业务设计上不能按普通管理系统的方式做。我之前见过有的团队把老人档案表设计成简单的人员表,结果后面扩展健康数据和工单时到处改表,非常痛苦。这个教训后面我会展开讲。总之,第一步一定要把业务角色和核心流程梳理清楚,再去想技术拆分。
1.2 技术栈选型的关键决策
这个项目的技术栈基本是标题里定好的,Vue3加Java,后端用Spring Boot。但在具体版本和组件选型上,还是有几个地方值得细说。
后端我用的是Spring Boot 3.x,JDK 17,这算目前比较稳妥的组合。Spring Boot 3相比2.x最大的变化是Javax变Jakarta,如果你之前写过2.x版本,迁移的时候要注意导入包的路径。ORM层我选择MyBatis-Plus,虽然JPA也可以,但在复杂查询和动态SQL上,MyBatis-Plus在团队协作中更好控制,尤其分页插件和逻辑删除非常省事。数据库用MySQL 8.x,缓存用Redis,实时消息推送用了WebSocket,另外给大屏和看板接了一层ECharts。
前端用Vue3加Vite,UI框架选了Element Plus。这里多说明一句,Vue3搭配TypeScript是更好的选择,但如果你团队对TS不熟,纯JavaScript也能跑,只是后续维护稍微累一点。移动端我是通过H5方式兼容的,小程序没有完整重写,而是让接口层直接服务多个终端,这样工作量可控。
几个关键选型对照如下:
| 技术点 | 方案 | 选型理由 |
|---|---|---|
| 后端框架 | Spring Boot 3.x | 生态成熟,社区资料多,快速搭建 |
| JDK | 17 | LTS版本,长期维护,满足Boot 3要求 |
| ORM | MyBatis-Plus | 查询灵活,分页方便,代码量低 |
| 数据库 | MySQL 8.x | 稳定,部署简单,业务量级完全够用 |
| 缓存 | Redis | 热点数据、分布式锁、Token存储都能用 |
| 前端框架 | Vue3 + Vite | 编译快,组合式API清晰 |
| UI组件 | Element Plus | 后台管理类项目最成熟的组件库 |
| 实时通信 | WebSocket | 告警推送、工单状态变化的实时性要求 |
选型的核心逻辑不是我偏执于某个框架,而是整个团队能最快上手、出现问题最好排查。尤其是养老平台这种业务逻辑复杂、角色权限多、状态流转多的项目,技术应该服务于稳定性和可维护性。
1.3 整体模块划分与业务边界
我把平台拆成六个核心模块:老人管理、照护工单、健康监测、家属互联、系统管理、数据看板。
老人管理是基础档案,包括老人基本信息、家属联系人、健康档案、能力评估、用药计划。照护工单是日常运营的核心,所有护工任务最终都落到工单上。健康监测负责对接智能设备数据,同时提供人工录入入口,异常数据触发告警。家属互联解决的是家属的知情需求,通过绑定老人档案,查看动态和接收消息。系统管理包含用户、角色、菜单权限。数据看板面向机构运营者,汇总当日工单量、异常事件、护工工作量等核心指标。
这种划分方式的优势在于,业务边界非常清楚。比如健康监测和照护工单之间通过老人ID关联,但彼此不直接操作对方的数据表,后期不管是拆服务还是加新终端都方便。如果你把逻辑都堆在Service层互相调用,前期倒是快,后期加一个需求能改出一堆Bug。
2. 核心业务细节与数据设计
2.1 老人档案:一人一档一码
老人档案是整个平台的数据地基。我一开始设计的表结构特别简单,就是一张老人基本信息表,后来真正接入业务时发现完全不够用。
一个完整的老人档案至少需要基础信息子表、能力评估子表、用药计划子表、健康指标子表、家属关系子表和居住地址子表。基础信息表负责身份类、联系人、紧急电话;能力评估表记录ADL评分、失能等级;用药计划表记录每天吃什么药、什么时间吃、是否已执行;健康指标表存血压、心率、血糖等连续记录;居住地址表负责机构和居家两种场景的地址信息。
这里有一个很关键的设计,就是档案快照。老人的能力评估不是永远不变的,机构每隔一段时间会重新评测。如果把每次评估结果都直接覆盖到主档案上,历史变化就彻底丢了。我在设计时增加了评估记录表,每次生成一份评估快照,当前状态只取最新记录。这样既能做趋势对比,也能出阶段报表。
一人一档还要配一码。我给每个老人生成一个唯一二维码,打印后贴在床头或者老人随身携带的卡片上。护理员扫码能够快速调出档案,家属扫码绑定关系。二维码里只直接放一个老人ID,不能放敏感信息,扫码之后再通过Token换取访问权限,否则二维码一泄露,隐私就全没了。
2.2 工单调度与状态机的设计
工单是平台里最复杂的部分,因为它不只是增删改查,还有状态流转。我设计的工单状态包括待分配、待开始、进行中、待评价、已完成、已取消、超时未开始。这么多状态必须用状态机来控制,不允许随意跳转。
比如待分配的工单只能流转为待开始或者已取消,待开始只能流转为进行中或者超时未开始,进行中只能流转为待评价。如果前端不小心发起了一个非法跳转,后端必须拦截。我在代码里写了一套状态流转校验逻辑,前端表单的按钮按状态显隐,后端Service层再判断一次,双重保障。
关于自动分配,我实现了一个简化版本:新工单生成后,系统会根据护工技能标签、当天已有工单数、距离三个维度做一个加权计算,选择最合适的护工。技能标签在创建护工账号时维护;工作量统计当天已有任务数;距离则依赖机构范围内的定位数据。算完之后,工单进入待分配列表,人工也可以手动调整。
插单和紧急单的情况很常见。我加了一个紧急级别字段,紧急单可以插到某个护工待开始列表的最前面,并且触发WebSocket通知。工单流转过程中还要记日志,每一条状态变更都写在工单操作记录表里。表结构简单,但谁在什么时间做了什么操作,后面扯皮查得清清楚楚。
2.3 健康监测数据与异常告警的处理
健康监测是一个看起来简单、做起来细节极多的模块。设备端比如手环、血压计,我统一走HTTP协议上报,JSON格式传老人ID、设备类型、指标编码、数值、采集时间。后端收到数据后先做基础校验,比如老人是否存在、数值范围是否合理,再落入健康数据表。
健康数据表我建议按时间序列设计,不要按指标类型横向建一堆字段。因为每类设备上报的指标不同,今天血压计传血压,明天血糖仪传血糖,如果字段写死,后面加设备会非常痛苦。通用结构是老人ID、设备编码、指标编码、数值、单位、采集时间。查询时按指标编码过滤即可。
异常告警是健康监测的核心。我设计了三个层次:阈值异常、趋势异常、缺失异常。阈值异常就是数值超过高低限,比如收缩压超过180;趋势异常指同一指标连续几次持续走高;缺失异常指例如手环超过2小时没有上报数据。三个层次触发不同的告警等级。
告警产生的具体流程是:健康数据入库后,处理器判断是否异常,如果是,就去查老人档案绑定的紧急联系人,再根据告警等级走不同触达通道。一般等级发送站内信和App推送,严重等级直接触发短信和语音电话。这里必须做好防抖和幂等。比如老人有一次血压异常波动,不能一分钟内给家属发10条短信。我加了冷却逻辑,同一个老人同类型告警15分钟内只发一条。
实际运行中我发现,设备数据经常有误报,比如手环佩戴位置偏移导致心率异常。所以不能全信设备,告警后系统会生成一条待确认任务,交给护理员在15分钟内复核,只有护理员确认异常才升级为正式告警。这样家属收到的通知可信度高很多。
2.4 家属互联与消息触达机制
家属互联模块的重点是数据权限和消息触达。一个老人可以绑定多个家属,一个家属也绑定了多个老人。绑定关系不能自己随意建立,我设计的是老人档案的紧急联系人发起绑定邀请,家属端输入手机号加验证码,同时机构人员在后台审核。审核通过后,家属账号只能看到自己绑定老人的数据,不能看其他人。
消息触达类型包括告警通知、工单完成通知、健康日报、服务提醒。这些消息必须走固定的内容模板,所有触达行为记录在消息日志表里。日志记录接收人、消息类型、通道、内容、发送时间、发送结果,不然出了问题无法追溯。
这里要特别注意频率限制。比如健康日报每天最多一次,工单完成通知每次工单结束发一条,告警通知走冷却周期。否则家属在凌晨收到一堆App通知,第一反应不是觉得平台智能,而是把通知权限直接关掉。那整个系统就白做了。
3. 实操过程:从Spring Boot后端到Vue3前端
3.1 后端公共能力搭建
这部分我就直接按照实际项目里搭工程的过程来讲。我习惯先建一个Spring Boot项目,用Maven管理依赖,然后搭建公共代码层。
工程结构大致是controller、service、mapper、entity、dto、vo、config、common。entity对应数据库表,dto是入参,vo是出参,这套分层虽然看起来繁琐,但项目一大就体现出好处了。比如老人档案接口的入参可能在管理端和移动端不一样,相同entity接不同dto可以非常自然地处理。
统一返回体我用的泛型类,所有接口都返回标准结构。示例代码如下:
public class Result<T> { private int code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.code = 200; result.message = "success"; result.data = data; return result; } public static <T> Result<T> error(int code, String message) { Result<T> result = new Result<>(); result.code = code; result.message = message; return result; } }全局异常处理用RestControllerAdvice,所有业务异常统一抛自定义BusinessException,由全局处理器转成统一返回体。这样前端只需处理标准结构,不用为每个接口定制返回格式。
配置方面,MySQL连接、Redis连接、MyBatis-Plus分页插件在启动类或者配置类中注册。特别提醒一下,MyBatis-Plus的逻辑删除配置要注意全局生效,否则删除操作会变成物理删除,真删了数据找不回来。
3.2 登录鉴权与权限控制
登录鉴权是平台安全的第一道门。我用Spring Security加JWT实现。用户的密码用BCrypt加密存储,登录成功后签发Access Token,前端在后续请求中放到Authorization头里。
JWT的设计上有几个要点:token有效期设短一些,比如2小时;加上Refresh Token机制,Access Token过期后用Refresh Token换取新Token,避免频繁登录。某些团队为了省事把Token有效期设置成7天,一旦泄露风险很大,我强烈不建议。
权限控制方面,我使用了基于角色加菜单权限的方案。用户登录后,后端返回用户信息以及可访问的菜单和按钮权限码。前端拿权限码控制页面元素显示,后端用Spring Security的@PreAuthorize注解校验接口权限。这里有一个关键点:前端隐藏按钮只是用户体验,后端才是安全边界,每个敏感接口都必须有注解校验。
数据权限方面,比如家属只能看自己绑定的老人数据,我用AOP切面实现。在查询老人列表、工单列表时,切面根据当前用户角色自动拼接数据过滤条件。管理端查询全量数据,家属端自动追加老人ID的IN条件。这个方案侵入性低,Service层的代码不用写一堆冗长的权限判断逻辑。
3.3 核心接口开发与实操记录
我以工单流转为例,展示整个开发过程。
第一步是实体设计,工单表包含工单编码、老人ID、工单类型、护工ID、计划开始时间、计划结束时间、紧急级别、状态、内容描述等字段。第二步是创建工单接口,入参里带老人ID、内容、紧急级别,Service层先校验老人是否存在且状态正常,再生成唯一的工单编码。
工单编码我做成规则:机构编码加年月日加当天流水号。比如某机构的编码是SO1,那20250210当天第1单就是SO120250210001。这个规则看着简单,但是后续按编码搜索、按天统计都很方便。
第三步是分配工单,入参是工单ID和护工ID,Service层校验工单状态必须为待分配,然后执行状态流转。
public void assign(Long orderId, Long workerId) { WorkOrder order = workOrderMapper.selectById(orderId); if (order == null) { throw new BusinessException("工单不存在"); } if (!OrderStatus.PENDING_ASSIGN.equals(order.getStatus())) { throw new BusinessException("当前状态不允许分配"); } order.setWorkerId(workerId); order.setStatus(OrderStatus.PENDING_START); workOrderMapper.updateById(order); // 写入操作日志 orderLogService.record(orderId, "ASSIGN", userId); }第四步是开始工单,护工扫码或者点击开始,接口校验当前时间与计划时间的差距。这一步有一个容易被忽略的点:开始时间不能早于计划开始时间,否则护工提前开始会导致后续排班冲突。第五步是完成工单,护工填写服务记录、上传照片或者签名,系统自动生成一条待评价任务,同时通知家属。
接口写完后,我用Swagger在浏览器里把每个接口的参数和返回结构都过了一遍。这里有个建议,写接口时返回的VO字段一定不要直接复用Entity,尤其是涉及密码、身份证等敏感字段的实体,一旦不小心序列化出去就麻烦了。
3.4 Vue3前端工程搭建与核心页面实现
前端工程我用Vite创建,项目结构大概是views、router、store、api、components、utils。
先做axios封装。拦截器里自动携带Token,遇到401时跳登录页,遇到业务错误码时弹统一提示。这个封装非常关键,不然每个页面里都要重复处理错误逻辑。
// utils/request.js import axios from 'axios'; import { useUserStore } from '@/store/user'; const request = axios.create({ baseURL: '/api', timeout: 10000, }); request.interceptors.request.use(config => { const userStore = useUserStore(); if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}`; } return config; }); request.interceptors.response.use( response => { const res = response.data; if (res.code !== 200) { ElMessage.error(res.message); return Promise.reject(new Error(res.message)); } return res.data; }, error => { if (error.response && error.response.status === 401) { // 跳转登录逻辑 } return Promise.reject(error); } );动态路由是前端一个关键点。不同角色登录后看到的菜单不同,我让登录接口返回菜单列表,前端用router.addRoute逐个注册。好处是不用的菜单不会出现在包里,用户也无法通过手输路径打开无权限页面。当然这只是前端层面的隐藏,后端权限校验才是真正的拦截器。
老人档案列表页是最常见的后台页面,我用Element Plus的表格组件加搜索表单,分页组件配合后端分页接口。表格列包括老人姓名、性别、年龄、失能等级、关联护工、最近一次健康评估时间。这个页面开发本身不难,但涉及字段多,建议列配置抽成一个文件,后面加需求改起来顺手。
健康监测大屏用的是ECharts。数据按天聚合,展示血压折线、心率趋势、告警数量柱状图。ECharts配置项很多,我的经验是先确定数据结构,再封装一个公共组件,把option作为props传进去,后面每个页面只需要关注自己的数据处理逻辑。
工单管理页面要用到WebSocket。护工端页面接上WebSocket后,新单分配时会收到实时通知。Vue3里在onMounted中建立连接,onUnmounted中关闭连接,注意页面切换时别让连接泄漏。
前端开发中最容易踩的坑是和后端的字段命名规范。后端如果用下划线命名,前端就必须用下划线取字段,或者后端转成驼峰。我建议统一用驼峰,并且让后端VO的计算字段在前面完成,前端不需要自己拼数据。
3.5 打包部署的常见做法
这个项目部署分成两部分,前端构建后是静态文件,后端是一个Spring Boot打包成的Jar包。我把前端构建后的dist目录放到Nginx里,并配置反向代理,凡是/api开头的请求都转给后端服务。
Nginx配置主要部分如下:
server { listen 80; server_name your-domain.com; location / { root /home/app/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }try_files配置是必须的,否则Vue Router的history模式刷新页面会出现404。生产环境MySQL和Redis建议单独部署。一些机构现场只有一台服务器的情况,也可以把MySQL、Redis、后端Jar放一台,Nginx放前端静态文件,项目初期完全够用。
数据库表结构变更我用脚本来管理,每个版本一个SQL文件,执行过的不允许回滚。这个习惯帮我避免了很多次改错表的尴尬。
4. 常见问题与排查技巧实录
4.1 联调阶段最容易踩的坑
前后端联调是问题最多的时候,我整理一下我经常遇到的情况。
第一个是时区问题。Java后端默认时区和MySQL时区不一致时,数据库里读出的时间相差8小时。解决办法是连接字符串加上serverTimezone=Asia/Shanghai,并且Java时间类型统一用LocalDateTime,前端展示时做格式化。如果时间字段存成了字符串,后续统计和排序都会很难受。
第二个是跨域问题。本地开发时前端和后端分属不同端口,必然跨域。Vite配置proxy代理是最简单的办法,走代理后浏览器看到的请求同源,不用后端处理跨域。上线之后有Nginx统一代理,也自然规避了跨域。
第三个是字段序列化问题。LocalDateTime默认序列化格式是2024-02-10T13:00:00,前端直接展示非常难看。我统一在配置里加了Jackson的全局格式化。
第四个是参数校验问题。后端接口开发时要大量使用@Valid和@NotNull,前端即使做了校验,后端也必须做。否则前端绕过页面直接调接口,没法保证数据安全。
联调阶段我还习惯把接口文档实时维护好,后端写完一个接口就同步更新,不让前端来猜。项目小的时候这可能无所谓,模块一多,文档不同步带来的沟通成本非常高。
4.2 数据权限越权的问题
权限问题最怕的就是只看菜单权限,忽略数据权限。菜单权限决定你能否进入某个页面,数据权限决定你在页面里能看到哪些数据。
比如家属登录后,理论上只能看到自己绑定的老人和相关工单。如果查询接口不带数据权限过滤,家属通过修改请求参数里的老人ID就能看到别的老人档案,这是严重的隐私事故。
我在项目里遇到过一次这个问题,排查起来是某个Service层方法没有走切面过滤逻辑,直接查了全量数据。后续优化是把数据权限过滤下沉到Mapper层,通过自定义拦截器统一处理,而不是散落在Service里。这样新同事写代码时也不容易漏掉。
4.3 大数据量下的性能优化
养老平台看似数据量不大,但健康数据是时间序列型数据,运行半年之后表记录数可能增长得非常快。
第一个问题是N+1查询。比如列表页查询老人列表后,又循环查询每位老人的最新健康评估和最新工单状态。这个问题可以通过聚合查询、关联查询或者提前批量查Map解决。
第二个问题是深分页。MyBatis-Plus分页到几百页之后,数据库偏移量大会导致查询变慢。我采取的做法是限制最大分页深度,超过500页的数据改用按时间范围或ID范围翻页。
第三个问题是热点数据缓存。老人档案、护工信息这种变化频率低的配置,在Redis里缓存一份,查询先走缓存。但是缓存失效时要防止缓存击穿,我用分布式锁控制回源查询。
4.4 定时任务与告警推送的可靠性
系统里有不少定时任务,比如生成健康日报、检测超时未开始工单、自动关闭超时未开始状态。这些任务最怕的就是多实例重复执行。
如果后端只部署一个实例,@Scheduled直接可用。一旦探活发现单实例有风险,部署了两个实例,每个定时任务就会执行两次。我采用Redis分布式锁解决,任务执行前先抢占锁,抢不到就直接跳过。
public void executeDailyReport() { String lockKey = "task:dailyReport"; boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.MINUTES); if (!locked) { return; } try { // 执行任务逻辑 } finally { redisTemplate.delete(lockKey); } }告警推送的可靠性上,我遇到的问题主要是大量重复推送。比如同一老人同一时间段,手环上报了多次异常,每次都触发推送逻辑。处理方式是在告警处理器里做了去重,以老人加上告警类型为维度,在Redis设置一个15分钟的键,键存在就直接忽略。推送成功后要记录日志,日志里保存了推送响应结果,方便排查第三方短信通道的故障。
另外要说的是,短信通道和App推送通道的切换,我在配置中心里做成可配置的开关。有些机构只需要App推送,不需要短信,有些则两个都要。做成开关后不用改代码。
最后分享一点我做这个项目最大的体会。智慧养老云服务平台的技术架构不算难,真正难的是状态流转和业务边界的处理。工单、告警、权限、消息,这些功能表面上互相独立,实际运行中紧密关联。写代码之前,建议花时间把流程图画清楚,把每个状态的合法流转写下来,代码实现时严格按流程推进。业务上出了偏差,技术解决不了;但技术上的坑,大多是提前设计就能避开的。希望这篇内容能给你做一个可落地的参考,如果后续有疑问,欢迎按这个过程一步步实践一遍。