社区智慧养老是个这两年需求特别密集的方向我前后帮朋友和客户落地过两套基于 SpringBoot 的养老管理系统一套偏机构内部运营一套偏社区网格化服务。这两套做下来最大的感受是技术本身不复杂复杂的是把“老人档案、健康数据、服务工单、家人告警”这些散落的东西串成一条能跑的链路。这篇文章就把第二套社区智慧养老系统的完整落地过程拉出来聊聊从需求拆解到数据模型再到 SpringBoot 项目骨架和几个让我印象深刻的大坑给准备做同类项目的朋友一份可以直接抄作业的参考。我默认的读者是会用 SpringBoot 写 CRUD、知道 MyBatis-Plus 大概怎么用但还没完整想过“养老系统这种业务型项目到底该怎么设计”的人。文章里所有代码都给精简可运行版本配置也贴出来了重点讲清楚每一步为什么要这么做而不是只给结论。1. 社区智慧养老系统整体设计与需求拆解1.1 社区智慧养老系统到底解决什么问题社区养老和机构养老最大的区别是老人不住在院里而是分散住在各个小区服务得靠“网格员上门 社区中心集中活动 家人远程监护”三条线同时跑。这就带来三个核心痛点第一老人的基础信息极度分散。社区可能管着几千个老人每个人的健康档案、家属联系方式、补贴记录、上门服务历史都散落在不同表格甚至纸质台账里想统计个“80岁以上独居老人有多少”都要靠人工翻半天。第二服务过程无法追踪。网格员上门做助餐、助浴、健康测量做完就做完了没有记录、没有回访、没有服务质量的验证。一旦老人家属问“上周到底来没来人”社区很难拿出可信证据。第三异常情况响应太慢。老人突发状况、SOS 呼叫、设备数据异常如果靠人工盯微信群漏掉是大概率事件。所以社区智慧养老系统的核心价值就是把这三条线收拢到一个平台上老人档案统一管、服务工单全程留痕、异常事件自动告警并推送给家属和网格员。这也是我在设计系统时最先想清楚的事——不要一上来做一堆花哨功能先把“档案、工单、告警”这个铁三角立住再做延伸功能。1.2 技术选型思路为什么是 SpringBoot选 SpringBoot 不意外但我要说说为什么在这个场景下它比别的方案更合适。养老系统的使用者是社区工作人员、网格员和运营方管理员他们的使用场景是典型的“内网PC 后台少量移动端”并发量不会很高但业务规则变化频繁——今天要求加一个补贴类型明天要求改一个工单状态流转。这种项目最怕“改业务要改代码、改代码要重启”。SpringBoot 在这类项目里的优势有几个生态成熟招人容易。社区类项目的开发团队往往不大SpringBoot MyBatis-Plus 是国内 Java 开发者的基本功后续维护不会依赖某个特定的人。自动装配机制让集成成本很低。Redis、MQ、定时任务、文件存储引入依赖加配置就能用恰好匹配“快速迭代业务”的节奏。部署方式友好。单机 Jar 包部署就行了不需要复杂的微服务架构一台低配服务器就能撑起一个街道级别的养老系统。设计模式规范。Service 层、Controller 层、Mapper 层的分层在 Spring Boot 里是肌肉记忆这对“人员流动频繁的项目”特别重要后来的人接代码不会骂娘。做个对比我见过有人用 Node.js 写这种系统开发确实快但后续做复杂报表、对接政务接口时反而费劲也有人硬上 Spring Cloud 微服务把几个 CRUD 模块拆成服务纯粹是给自己找麻烦。社区智慧养老系统这种体量单体 SpringBoot 就是性价比最优解。2. 核心业务模块与数据模型设计2.1 老人档案与健康数据管理老人档案是整个系统的地基设计得好不好直接决定后面所有模块的复杂度。我这里的核心表设计是elder_info字段覆盖三个层面身份信息姓名、身份证号、住址、家属联系方式、健康状况慢病标签、失能等级、过敏史、紧急联系人、服务属性补贴类型、监护人授权状态、是否独居、是否愿意接受上门服务。这里有一个特别容易踩的坑身份证号加密存储。老人档案涉及隐私数据明文存身份证号是不可接受的。我建议用 MyBatis-Plus 的TypeHandler做字段级加解密持久层自动加密查询时自动解密业务代码里完全无感知。这个方案比在 Service 层手动加密解密要干净得多。健康数据这块我设计了独立的health_record表记录每次测量的血压、血糖、心率、血氧以及测量方式上门测量、自助设备上传、手动录入。为什么要独立成表而不是在 elder_info 里加字段因为健康数据是时间序列数据老人每次测量都会产生一条新记录如果把这些数据塞进主表表会越来越宽查询效率下降而且没法做趋势图表。独立成表后按老人 ID 时间倒序查询做折线图非常方便。2.2 服务工单与调度流转服务工单模块是这个系统里业务逻辑最重的部分。社区养老的服务类型五花八门助餐、助浴、助洁、助医、精神慰藉、代办采购每种服务的流程还不完全一样。我最后用了一张通用工单表 一张服务类型字典表来解决。工单表service_order核心字段包括工单编号、老人 ID、服务类型、网格员 ID、计划时间、实际完成时间、状态、评分、备注。状态流转我设计成固定枚举待派单 → 已接单 → 服务中 → 已完成 → 已回访 → 已取消。调度逻辑是重点。我做了个“自动派单 人工兜底”的策略新工单创建后优先派给服务区域匹配、且当日工单数最少的网格员如果网格员 30 分钟内未接单系统自动推送提醒并在 2 小时后升级为管理员人工干预。这个逻辑用 SpringBoot 的Scheduled定时任务 Redis 过期事件实现不复杂但能切切实实解决“工单没人接、管理员不知道”的问题。2.3 告警与消息通知链路告警模块是家属最关心的也是这个系统区别于普通管理系统的亮点。告警来源有两个一是老人主动按 SOS 设备触发紧急告警二是系统监测健康数据异常自动触发比如血压连续三次超过阈值、设备长时间无心跳。告警产生后写入alert_record表然后通过消息队列异步通知家属短信、网格员站内信短信、管理员大屏弹窗。为什么这里要引入消息队列而不是直接在业务代码里调短信接口因为告警是强实时场景而短信服务商接口不稳定是常态。用 MQ 做异步削峰和失败重试业务主流程不会被短信超时拖死。如果是小项目不想上 MQ也可以用 SpringBoot 的Async加本地表做重试效果类似但 MQ 更规范。3. 实操过程从零搭建可运行的项目骨架3.1 项目初始化与基础配置先用 Spring Initializr 创建一个 SpringBoot 项目。我常年使用的稳定组合是SpringBoot 2.7.18 JDK 8 MyBatis-Plus 3.5.3 MySQL 5.7。这套组合为什么稳因为 SpringBoot 3.x 要求 JDK 17而很多社区的服务器上还是 JDK 8强行上新版本会让部署环境很难受。如果你是从零开始并且服务器环境允许 JDK 17那直接用 SpringBoot 3.2 MyBatis-Plus 3.5.5 也行但这篇文章的配置我按 JDK 8 来写兼容性最强。核心 pom 依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent properties java.version1.8/java.version mybatis-plus.version3.5.3/mybatis-plus.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这里要给一个忠告MyBatis-Plus 的mybatis-plus-boot-starter版本要和 SpringBoot 版本匹配否则容易出现Invalid bound statement或者分页插件不生效的诡异问题。2.7 的 SpringBoot 用 3.5.x 的 MP 都没问题但如果用 SpringBoot 3.x就要用 MP 3.5.4 以上版本因为底层包路径改了。3.2 集成 MyBatis-Plus 与数据库设计落地配置application.yml我习惯把环境相关的配置拆出来方便切换开发和生产环境server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/community_elder?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0逻辑删除这个配置强烈建议加上。老人档案和服务工单都是需要留痕的数据物理删除会导致历史记录断层审计的时候非常麻烦。接下来是核心表的建表 SQL 示例我直接贴elder_info表CREATE TABLE elder_info ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 老人姓名, id_card varchar(64) NOT NULL COMMENT 身份证号(加密), gender tinyint(4) DEFAULT NULL COMMENT 性别 1男 2女, birthday date DEFAULT NULL COMMENT 出生日期, address varchar(200) DEFAULT NULL COMMENT 居住地址, community_id bigint(20) DEFAULT NULL COMMENT 所属社区, live_status tinyint(4) DEFAULT NULL COMMENT 居住状态 1独居 2与子女同住 3养老机构, care_level tinyint(4) DEFAULT NULL COMMENT 照护等级 1-5, chronic_diseases varchar(255) DEFAULT NULL COMMENT 慢病标签逗号分隔, emergency_contact varchar(50) DEFAULT NULL COMMENT 紧急联系人, emergency_phone varchar(20) DEFAULT NULL COMMENT 紧急联系电话, subsidy_type varchar(50) DEFAULT NULL COMMENT 补贴类型, deleted tinyint(1) DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_community (community_id), KEY idx_live_status (live_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT老人档案表;在实体类上我配合 MyBatis-Plus 的注解实现了插入自动填充时间Data TableName(elder_info) public class ElderInfo { TableId(type IdType.AUTO) private Long id; private String name; private String idCard; private Integer gender; private LocalDate birthday; private String address; private Long communityId; private Integer liveStatus; private Integer careLevel; private String chronicDiseases; private String emergencyContact; private String emergencyPhone; private String subsidyType; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; TableLogic private Integer deleted; }同时写一个MetaObjectHandler的实现类统一处理 createTime 和 updateTime 的自动填充。这一步解决了所有表的公共字段维护问题不用每个 Mapper 手动 set。3.3 核心接口与安全认证的实现Controller 层设计我遵循一个原则瘦 Controller 胖 Service。Controller 只做参数接收和结果包装所有业务逻辑都下沉到 Service 层。这样做的好处是业务规则变化时Controller 不用动测试也好写。工单派发接口的简化代码如下PostMapping(/order/dispatch) public RVoid dispatch(RequestBody Valid DispatchRequest request) { // 自动派单策略 orderService.dispatch(request.getOrderId()); return R.ok(); }真正核心的自动派单逻辑在 Service 里public void dispatch(Long orderId) { ServiceOrder order orderService.getById(orderId); if (order null) { throw new BizException(工单不存在); } // 1. 查询服务区域内在线网格员 ListGridWorker workers gridWorkerService.list( new LambdaQueryWrapperGridWorker() .eq(GridWorker::getCommunityId, order.getCommunityId()) .eq(GridWorker::getStatus, 1) ); // 2. 按当日工单数排序选出最空闲的网格员 workers.sort(Comparator.comparingInt(w - orderService.count(new LambdaQueryWrapperServiceOrder() .eq(ServiceOrder::getWorkerId, w.getId()) .eq(ServiceOrder::getStatus, 已完成) .ge(ServiceOrder::getCreateTime, LocalDate.now().atStartOfDay())) )); // 3. 派单并记录 String workerId workers.get(0).getId(); order.setWorkerId(workerId); order.setStatus(待接单); orderService.updateById(order); }安全认证上社区类项目适合轻量级方案。我用了 Sa-Token 而不是 Spring Security原因是这个项目的权限模型很简单管理员、网格员、家属三种角色Sa-Token 的开箱即用程度比 Spring Security 高很多多租户级的复杂权限根本不需要。集成方式是在拦截器里做登录校验和角色鉴权配合注解SaCheckLogin和SaCheckRole(admin)就能覆盖大部分场景。如果团队对 Spring Security 很熟用它也没问题。但我个人觉得在人员不熟悉 Security 源码的情况下Sa-Token 能把“登录、鉴权、踢人下线”这些功能从配置地狱里解放出来对养老这种业务密集型的项目更划算。4. 常见问题与排查技巧实录4.1 SpringBoot 版本陷阱与依赖冲突我在开发过程中遇到最大的版本坑有两个这里展开说说。第一个是 SpringBoot 2.7 升级到 3.x 后javax包全部变成了jakarta如果项目里直接用了javax.servlet.http.HttpServletRequest这类 API升级后编译直接报错。社区养老系统这种长期维护的项目升级成本很高所以我建议在一开始就锁定 SpringBoot 2.7.x 版本除非确实有 JDK17 的硬性需求。第二个是 MyBatis-Plus 和 PageHelper 混用导致的There is no getter for property named pageNum问题。很多人习惯在 MyBatis-Plus 项目里继续用 PageHelper 的PageHelper.startPage()但这两个插件都通过拦截器实现分页一起用就会互相干扰。解决方法很粗暴只用 MyBatis-Plus 自带的分页插件。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }setMaxLimit(500L)这行是防止有人调接口时传一个size100000把数据库打爆属于防御性编程的经典操作。4.2 MyBatis-Plus 分页与多表联查的坑MyBatis-Plus 的分页插件有个隐蔽坑如果查询 SQL 里带LEFT JOIN分页 count 语句可能会报错或者返回错误总数。我排查过一次发现原因是多表联查时主表有group by子句自动生成的 count 语句没处理好这一层。解决方案是多表联查的复杂查询不依赖自动 count而是手写 count 语句select idselectOrderPage resultTypecom.example.vo.OrderVO SELECT o.*, e.name AS elder_name, w.name AS worker_name FROM service_order o LEFT JOIN elder_info e ON o.elder_id e.id LEFT JOIN grid_worker w ON o.worker_id w.id where if teststatus ! null and status ! AND o.status #{status} /if /where ORDER BY o.create_time DESC /select另一个常见问题是字段自动映射失效。当查询结果集里有elder_name这种别名时MyBatis-Plus 的map-underscore-to-camel-case只会处理下划线不会自动把elder_name映射到elderName属性上。解决办法是在查询列上显式加别名e.name AS elderName或者给 VO 字段加JsonProperty注解。我自己更推荐前者因为 SQL 一眼能看明白。4.3 定时任务与部署调优记录系统里两个定时任务帮我省了很多心一个是每晚自动汇总当天服务完成率并生成报表另一个是每 10 分钟扫描一次超时未接单的工单并升级处理。SpringBoot 的定时任务默认是单线程执行的如果项目里有多个Scheduled任务它们会排队执行。这个坑我踩过报表任务跑 5 分钟期间所有告警短信任务全被堵着告警延迟直接导致一次服务投诉。解决方法是注入一个带线程池的TaskSchedulerConfiguration public class SchedulingConfig { Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); scheduler.setThreadNamePrefix(scheduled-task-); scheduler.initialize(); return scheduler; } }部署这块我踩过更深的坑是内存配置。社区服务器普遍配置不高一开始给 JVM 默认参数跑结果跑了三天就 OOM。后来发现是因为代码里有个查询会把整个月的健康记录一次性加载到内存做统计。优化思路改成按天分批查询再合并同时把 JVM 参数调成-Xms512m -Xmx512m -XX:UseG1GC稳定跑了大半年没再出问题。还要特别提醒SpringBoot 项目打包部署后日志一定要落文件。用logback-spring.xml配置按天滚动归档保留 30 天。这个习惯在养老系统这种长期运行的项目里尤其关键没日志排查生产问题就像瞎子摸象。5. 对同类型项目的参考建议这个社区智慧养老系统做完我的整体感受是“重业务、轻技术”。我的建议是第一功能边界要克制。别想着把视频通话、AI 问诊、智能语音全塞进去先把老人档案、服务工单、告警通知、统计报表这四件事做扎实系统就有八十分了。后续需求来了再迭代远比一开始堆砌功能容易维护得多。第二数据安全这根弦要绷紧。老人信息属于敏感数据身份证号、联系方式、健康记录都应该加密存储接口层面做好权限校验敏感接口的操作日志要留全。这是养老系统的底线要求不是可选项。第三系统的可扩展性要从表设计上留余地。比如老人档案里我预留了community_id、care_level这几个扩展维度字段后面接绩效考核模块、补贴发放模块时都是直接用这些现有字段关联不用重新改造老表。设计表结构时多问一句“这个字段未来可能怎么用”能省很多返工时间。我在实际排查中发现最容易出问题的不是代码反而是生产环境配置。比如 MySQL 连接串里的serverTimezoneAsia/Shanghai忘写存进去的时间就差了 8 小时导致定时任务重复执行和告警时间错乱。这种问题排查了半天最后发现只是配置少了一段想起来又气又好笑。希望后来的人看到这篇文章能少踩一个是一个。这套系统后续我还打算做两件事一个是把健康设备的接入改成标准化的 IoT 协议让更多设备能直接上报数据不用再手工录另一个是给家属端做一个随时查看工单和告警记录的 H5 页面毕竟子女能看到老人在社区里被照顾得好不好这才是智慧养老最核心的价值所在。