ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue3社区智慧养老监护平台开发实战全解析

SpringBoot+Vue3社区智慧养老监护平台开发实战全解析 说个真事儿。今年年初接手了一个社区智慧养老监护管理平台的开发项目技术栈正好就是标题里这套Java SpringBootVue3MyBatisMySQL前后端分离。做这种项目跟做普通的管理后台不一样它牵扯到的不仅仅是老人信息的增删改查还有健康设备的数据接入、实时告警、工单流转、家属端查询这些环环相扣的东西。如果只是照着“CRUD生成器”的思路糊一个系统出来后续接设备、接大屏、接告警规则的时候一定会返工。这篇文章我把整个项目从选型到落地的核心细节全部拆开讲一遍包括表结构怎么设计、MyBatis 怎么写复杂查询、Vue3 端的监控大屏怎么做、前后端联调有哪些坑希望对正在做类似社区服务平台的兄弟有帮助。1. 项目整体设计与技术选型思路1.1 这类监护平台到底要解决什么核心问题社区智慧养老说白了就是居家养老和机构养老之间的过渡形态老人住在自己家或者社区日间照料中心社区服务中心统一管理健康数据、对接紧急求助、安排护工上门服务。所以系统的核心不只是一个“通讯录”而是三条业务主线老人档案与家属绑定每个老人对应一到多个家属账号家属可以远程查看健康数据。设备数据采集与异常告警手环、血压计、血氧仪等设备定时上报数据系统判定异常后生成告警工单。服务工单与处置闭环告警产生后需要派单给护工护工上门处理完回填结果形成完整记录。明确了这几条主线技术选型就不会跑偏。你需要的不是一个臃肿的微服务架构而是一个稳定、能快速迭代、维护成本足够低的单体应用加合理分模块。1.2 为什么选 SpringBoot Vue3 MyBatis MySQL 这套组合先说 SpringBoot。现在 Java 圈子做这种业务系统SpringBoot 依然是默认选项原因很朴素约定优于配置内嵌 Tomcat打一个 jar 包就能跑社区护工端哪怕部署在老旧电脑上也没有压力。版本我选的是 SpringBoot 2.7.x搭配 JDK 8主要考虑到部分现场环境的兼容性。如果你是新项目且确定 JDK 17 没负担直接上 SpringBoot 3.x 也没问题只是 MyBatis 的 starter 要注意用适配版本。再说 MyBatis 而不是 MyBatis-Plus。这个选择可能有人不理解我讲一下真实原因这个项目里有大量的多表关联查询和动态条件筛选比如“查某个社区所有老人的最新一条健康记录”这种 SQL 用 MyBatis 手写可以精确控制每一行逻辑SQL 执行效率也一目了然。MyBatis-Plus 虽然开发快但遇到复杂查询时容易生成冗余条件反而增加排查成本。团队里如果全是新手手写 SQL 还能逼着他们吃透业务。前端用 Vue3 Vite配合 Element Plus 做后台界面。Vue3 的组合式 API 写业务逻辑比 Options API 舒服太多尤其监控大屏这种需要大量响应式数据的页面逻辑复用非常方便。Vite 开发时的热更新速度比 Webpack 快得多实测启动一个几十个页面的工程只要一两秒开发体验完全不一样。数据库用 MySQL 8.0主要是社区项目数据量不会爆炸单库单表足够支撑InnoDB 的事务能力和行级锁在工单流转、健康数据写入这些场景下非常可靠。字符集统一 utf8mb4避免生僻字和 emoji 符号写入报错。1.3 前后端分离架构在社区项目里的落地方式前后端分离在这个项目里带来的实际收益有两个第一后端接口和前端页面可以并行开发互不阻塞第二部署灵活前端打包出静态文件扔到 Nginx后端 jar 包独立运行后续如果要在现场部署边缘计算网关前端页面依然能访问远端服务。但分离架构也会带来两个必须处理的问题一个是跨域一个是鉴权。跨域我在后端用 CorsFilter 统一处理允许指定来源地址比如前端部署的 Nginx 域名而不是直接allowCredentials(true)allowedOriginPatterns(*)全放开避免安全隐患。鉴权用 JWT登录成功后前端把 token 存到 localStorageAxios 请求拦截器统一带上 Authorization 头。提示JWT 适合这种短时效业务系统但注意密钥要放到配置文件里不要硬编码在代码中token 有效期建议设 8-12 小时现场护工的登录频次不算高长一点反而体验更好。2. 后端核心模块设计与 MyBatis 数据层实战2.1 SpringBoot 项目结构怎么划分最清晰这种项目千万别按“Controller/Service/Mapper”三层一刀切时间一长就是大杂烩。我采用的是按业务模块分包每个模块内部再分 mvc 子层。大致结构如下com.community.care ├── common │ ├── config // 跨域、JWT、MyBatis、Redis配置 │ ├── constant │ ├── exception // 全局异常处理器 │ ├── result // 统一返回结构 │ └── utils ├── system // 用户、角色、权限 │ ├── controller │ ├── service │ ├── mapper │ └── entity ├── elder // 老人档案与家属管理 ├── device // 设备管理与数据接入 ├── health // 健康记录与指标分析 ├── alert // 告警与工单 └── dashboard // 大屏统计接口统一返回结构我用的ResultT包装包含 code、message、data 三个字段。前端 Axios 响应拦截器统一判断 code 是否为 0非 0 弹错误提示。这样接口层不会各写各的返回姿势后面对接小程序端也省事。全局异常处理是必须做的。业务异常用自定义 BizException参数校验异常、SQL 异常、未知异常都分别处理。特别是数据库异常不要直接把完整异常栈抛给前端统一转成“系统繁忙请稍后重试”避免泄露表结构信息。2.2 MyBatis 核心配置与手写 SQL 的关键点MyBatis 的配置看起来简单但几个细节直接决定开发效率。我放一个最核心的 mybatis-config 配置思路mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.community.care.*.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl cache-enabled: falsemap-underscore-to-camel-case必须打开数据库字段create_time才能自动映射到createTime。日志我这里开了 stdout 输出开发环境非常有用的可以看到每条 SQL 执行时传入的参数和影响行数一旦查询结果和预期不一致先看输出的 SQL 再调不要瞎猜。然后在 pom 里引入 mybatis-spring-boot-starter版本选和 SpringBoot 2.7 匹配的 2.3.x不要去引 3.x 版本否则会出现BindingException: Invalid bound statement这类问题。XML 文件里写 SQL 时两个最容易踩的坑我提醒一下动态 SQL 中的号要转义成lt;比如查询近 7 天数据时create_time #{startTime}没问题但create_time lt; #{endTime}必须转义否则 XML 解析直接报错。批量插入健康数据时用foreach拼接 values注意 MySQL 连接串要加allowMultiQueriestrue或者用 rewriteBatchedStatements否则批量操作性能上不去。复杂查询我举一个真实的例子监控大屏需要显示“每个社区今日告警数量”和“每个护工今日已处理工单数”。这用 MyBatis 写统计 SQL 再合适不过select idcountTodayAlertByCommunity resultTypemap SELECT c.community_name AS communityName, COUNT(a.id) AS alertCount FROM alert_record a LEFT JOIN elder_info e ON a.elder_id e.id LEFT JOIN community c ON e.community_id c.id WHERE a.alert_time gt; DATE_FORMAT(CURDATE(), %Y-%m-%d 00:00:00) AND a.alert_time lt; NOW() GROUP BY c.id, c.community_name ORDER BY alertCount DESC /select这种写法执行计划清晰也方便在数据库客户端里直接验证。2.3 MySQL 表结构设计从业务出发而不是照搬模板表设计是这种项目最容易返工的点。我的核心体会是不要一张大表搞定所有字段把“基础档案”“状态信息”“绑定关系”“流水数据”分开建表。老人基础表elder_info的核心字段字段类型说明idbigint主键family_user_idbigint绑定的家属账号namevarchar(32)老人姓名gendertinyint1男 2女id_cardvarchar(18)身份证号birth_datedate出生日期community_idbigint所在社区room_novarchar(32)房间号/住址health_leveltinyint1健康 2重点关注 3高风险emergency_contactvarchar(32)紧急联系人emergency_phonevarchar(20)紧急联系电话statustinyint1正常 2已注销健康数据单独建表health_record核心字段为心率、收缩压、舒张压、血氧、体温、设备编码、采集时间。这里要特别强调健康数据是典型的时序数据按时间查是最高频操作所以recorded_time必须建索引最好和elder_id建联合索引。数据量大以后可以按月分表但社区项目前期单表加索引足够。告警表alert_record一定要保留alert_source字段标明告警是来自规则引擎自动判定还是家属手动上报这直接关系到后续责任追溯。工单表task_order用order_no做唯一业务号规则建议是yyyyMMddHHmmss 4位随机数。2.4 用 TypeHandler 处理数据库 JSON 字段健康指标里有些数据是结构化的比如手环上报的位置信息包含经度和纬度护工上门服务的结果包含多项评分。这种情况用单独的字段存 JSON 字符串最省事但 Java 实体类里想直接操作对象就得用 TypeHandler。我写了一个通用的 JacksonTypeHandler继承 BaseTypeHandlerObject在setNonNullParameter里把对象序列化成 JSON 字符串存入数据库在getNullableResult里把 JSON 反序列化成对应的对象类型。实体类字段上标注TableField(typeHandler JacksonTypeHandler.class) private LocationInfo locationInfo;这里要注意XML 查询时如果返回类型是实体类且包含 JSON 字段resultMap 里对应的字段也必须显式指定 typeHandler否则反序列化不生效前端拿到的可能是 null排查半天才发现是 resultMap 漏了配置。3. Vue3 前端架构与核心页面实现3.1 用 Vite 搭建工程并初始化前端基础设施前端这块我直接推荐用 Vite 的官方脚手架npm create vitelatest care-web -- --template vue-ts项目结构用 TypeScript 还是纯 JavaScript 视团队情况我这边是纯 JS因为现场维护的同事更熟悉 JS 语法。初始化后第一步是装依赖核心库就五样vue-router、pinia、axios、element-plus、echarts。Element Plus 引入方式我建议全量引入不要为了省那几百 KB 的体积在社区项目里做按需加载全量引入的好处是团队新增成员时不用学习额外的 unplugin 配置开发效率优先。路由守卫必须写。系统里分成管理员、护工、家属三种角色需要通过路由的 meta 字段控制页面访问权限。具体写法很简单router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })页面级的按钮权限可以在指令v-permission里做但社区项目不建议过度设计菜单控制到角色即可。3.2 监控大屏的数据展示与实时刷新监控大屏是这个项目最出效果的页面也是前端代码里最值得打磨的部分。大屏我拆成了三个区域顶部是社区总览指标卡老人总数、今日告警数、待处理工单数、在线设备数中间是健康告警滚动列表右侧是社区分布统计图表。实时刷新这里有个关键选择用轮询还是 WebSocket。我的结论是社区监护场景下轮询足够10 秒一次的频率在现场完全可以接受。WebSocket 虽然响应更及时但需要维护连接状态、断线重连、心跳保活复杂度高一个量级。项目上线第一版用 Axios 轮询等业务稳定后再升级 WebSocket 也不迟。滚动列表我用 Vue3 的 TransitionGroup 做进入动画老数据往下推新告警从顶部插入。ECharts 初始化用ref拿到容器节点注意在onBeforeUnmount里调用chart.dispose()释放实例否则多切换几次 Tab 浏览器内存就涨上去了。3.3 Axios 封装与后端联调的规范Axios 封装有几个细节直接影响联调体验。请求拦截器统一加 token响应拦截器统一处理业务码和 401 跳转。超时时间我设置为 30 秒健康数据导出接口偶尔会有几秒钟的耗时太短会误报超时。文件导出这块单独提一下后端返回的是文件流不能用普通的 response 拦截器直接 JSON.parse要设置responseType: blob拿到 Blob 后自己创建下载链接。我最初的版本漏掉这个设置导出 Excel 一直显示乱码排查了半天才发现是响应类型没有对齐。export function exportAlertList(params) { return request({ url: /alert/export, method: get, params, responseType: blob }) }另外从 3.0 开始 Axios 的 params 序列化对数组的默认处理是a[]1a[]2后端 SpringBoot 接收这种格式会报错建议后端接口用 List 接收时配上RequestParam ListLong ids或者前端设置paramsSerializer。4. 关键业务场景的落地细节4.1 老人健康档案与家属绑定流程老人建档是整个系统的入口建档信息不可潦草。流程是管理员录入老人基本信息系统生成老人编号然后家属手机号接收验证码完成绑定。绑定关系核心在elder_family_rel表一个老人可以绑定多个家属一个家属也可以关注多个老人比如子女同时照顾两位老人。绑定时必须校验手机号是否已注册否则会出现家属账号存在但无法绑定的尴尬。健康等级的初始判定在提交建档时根据慢病列表自动计算有心脑血管疾病史的自动标为“重点关注”如果同时有高血压和糖尿病直接标为“高风险”。这里的分级规则后续上线后一定会反复调整所以规则不要写死在 SQL 里我放到了配置文件并预留了规则表后续方便运营人员自己调整阈值。4.2 告警工单的完整闭环告警和工单是这个项目里唯一带状态机的业务模型状态流转必须设计清楚。告警状态有待处理、处理中、已完成、已忽略。工单状态有待派单、已接单、已完成、已取消。这里我踩过一个坑最初告警表和工单表共用一段状态字段结果告警明明还没处理工单却先完单了前端页面展示的逻辑越写越乱。后来彻底拆开告警和工单通过alert_id关联各自维护自己的状态。派单成功后同时更新两边状态工单完成后反向更新告警状态这个联动就清晰了。护工端的工单详情页要显示老人基础信息、告警原因、紧急联系人电话以及一键拨号入口。注意一键拨号在 PC 端不是很好实现当前项目主要面向 PC 后台接单操作还是靠电话沟通后手动点击确认。4.3 健康趋势分析与数据导出健康趋势分析是一个容易被低估的功能。很多系统只做数据采集不做趋势展示结果家属端打开页面看到一堆数字完全无感。我用 ECharts 的折线图展示 7 天、30 天的心率趋势并在异常点上方用特殊标记标出告警时刻前端看到图就能直观理解“昨晚凌晨 3 点心率过高触发了告警”。数据导出我用的 Apache POI 生成 xlsx导出范围控制在一个月以内数据量太大容易响应超时。导出文件名用LocalDateTime.now()拼接yyyyMMddHHmmss后缀防止重复。如果现场有文件存储需求可以考虑把导出的临时文件扔到 Minio 并生成短时下载链接不要用本地磁盘临时文件目录重启服务后文件还在但链接失效的问题很恶心。5. 常见问题排查与避坑指南5.1 数据库连接与时间相关问题MySQL 8.0 连接串必须显式指定时区否则部署到现场 Windows 服务器上数据库报错信息里经常出现 “The server time zone value” 的异常。连接串上加serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue就稳了。allowPublicKeyRetrieval 这个参数经常被忽略8.0 默认使用 caching_sha2_password 认证首次连接需要获取公钥不加这个参数驱动可能直接拒绝连接。另外 SpringBoot 2.7 默认的数据库连接池 HikariCP 参数需要调一下最大连接数maximum-pool-size建议 20连接超时connection-timeout建议 30000 毫秒。社区项目并发不高但现场如果有多个服务同时连同一个库连接池太小会明显拖慢接口响应。5.2 MyBatis 常见坑缓存与映射问题MyBatis 的二级缓存我默认是关闭的。这个项目涉及健康数据、告警信息对实时性要求高缓存带来的性能提升在这类写入频繁的场景下收益有限反而容易出现“数据更新了但查询结果还是旧值”的灵异问题。开发环境开启本地缓存作为 SQL 调优参考可以生产环境建议关闭。还有一个映射问题非常隐蔽实体类字段如果有is前缀比如isAbnormalMySQL 字段如果命名为is_abnormal开启驼峰映射后 MyBatis 会自动映射成isAbnormal看着没问题但 Lombok 生成的 getter 方法名是getIsAbnormal而不是isAbnormal某些框架反射取属性时会不一致。解决方式是实体类里避免is开头的布尔字段改成abnormalFlag一类的命名一了百了。5.3 Vue3 响应式与组件通信的实战坑Vue3 的reactive有一个限制很多人不知道直接解构对象会丢失响应性。比如从 store 里取用户信息时写const { name, role } store.userInfo页面里修改 store.userInfo.name 后这个解构出来的 name 不会自动更新。解决方式是用storeToRefs或者干脆在模板里直接引用 store 对象。组件通信上我踩过最烦的一个坑是ECharts 图表在 el-tab-pane 里初始化时宽度经常是 0导致图表被压缩成竖线。原因是 Tab 切换后隐藏的容器没有宽度信息。我的解决方式是图表初始化前判断容器宽度如果为 0等 Tab 切换激活事件后再调用nextTick初始化。还有一个很常见的告警前端开发环境请求接口报 CORS 错误后端明明加了跨域配置还是报。检查一下是不是通过 Vite 的 proxy 代理如果用了 proxy后端就不要再配允许跨域了代理转发属于同源请求双重配置反而会在生产环境出奇奇怪怪的问题。5.4 部署环境与版本兼容性问题清单现场部署最容易出问题的就是环境版本不一致。我整理过一张对照表组件版本说明JDK1.8 或 17SpringBoot 2.7 用 1.83.x 用 17Maven3.6打包时 skiptestsNode.js16Vite 5 需要 Node 18 以上MySQL8.0字符集 utf8mb4Nginx1.22前端静态托管 反向代理前端打包后如果出现路由刷新 404是 Nginx 没有配try_files导致的加上try_files $uri $uri/ /index.html;解决。后端接口用/api前缀并统一代理到 jar 服务端口这样前端资源与接口域保持一致避免生产环境额外的跨域配置。6. 实操心得与经验收尾这项目我从开发到上线稳定运行最大的体会是社区类管理系统的成败不在技术多炫而在数据链路是否闭环。健康数据从设备上报经过接口落库再到前端展示最后触发告警、生成工单、处理回填每一步都不能断。所以做的时候多花时间梳理状态流转比多写几千行代码更有价值。另外一个小建议用 SpringBoot Vue3 做这类系统时接口命名和字段命名一定要从第一天就统一规范。比如时间字段统一叫xxxTime不用xxxDate状态字段统一用数字0/1而非字符串enable/disable。前期不规范后期改起来牵一发动全身尤其是前端页面已经写死字段名的时候改一个字段名要动七八个文件。最后我再分享一个小技巧健康数据的采集接口一定要做幂等。手环设备因为网络原因可能重复上报同一条数据我在接口里通过device_code recorded_time的唯一索引去重重复请求直接返回成功但不实际插入。这种细节网上教程几乎不会提但现场设备一旦上线这就是保命的设计。项目源码整体打包后就可以直接部署前端 Nginx 托管、后端 jar 服务化运行给现场运行人员一份部署文档基本上就是一键启动。这套结构后续要扩展 IoT 设备接入或者小程序家属端也都有现成的扩展位。
返回列表