☰
SpringBoot社区智慧养老系统开发实战:从数据库设计到权限与告警实现
2026/10/11 16:46:23 网站建设 项目流程

这两年我前前后后做过几个基于 SpringBoot 的管理系统,但真正让我觉得"这系统做出来是给人用的",还是这个社区智慧养老系统。起因很朴素:有次我陪家里长辈去社区服务站办高龄津贴,看到工作人员还在用 Excel 翻老人的健康档案,几万个格子,眼睛都快看瞎了。旁边一台血压计测完数据,全靠手抄,家属打电话问老人今天血压怎么样,对方得翻五分钟纸质记录才能答上来。

回来后我就想,能不能用 SpringBoot 把这事串起来:老人档案、健康数据、服务工单、告警通知全部数字化。于是就有了这套"基于 SpringBoot 的社区智慧养老系统"。它会解决社区养老服务站最头疼的三件事:老人底数不清、服务过程无记录、健康异常靠人盯。适合刚学完 SpringBoot 想拿真实项目练手的人,也适合正在构思毕业设计的同学,以及想了解这类系统怎么从需求落到代码的开发者。这篇就把完整的技术选型、数据库设计、核心模块实现和上线前踩过的坑,一条条讲清楚。

1. 社区养老的业务盘子究竟有多大:需求剖析与模块边界

想写代码之前,先把业务看清楚。很多做管理系统的人犯的第一个错,就是上来就建表,结果表建了二三十张,业务逻辑却是乱的。智慧养老系统看起来是"管理老人信息",实际上它是一个典型的社区级综合业务平台,涉及的人员、事项、流程相当繁杂。

1.1 系统到底要管哪些事

我先后走访了几个社区服务站,跟一线社工聊完,把业务归纳为四条主线:

第一,老人全生命周期档案。这个不只是姓名、电话、住址,还包含健康等级评估(自理、半失能、失能)、是否独居、紧急联系人、补贴类型、既往病史、用药情况等。这些字段不建好,后面服务派单、告警判断都无从谈起。

第二,健康数据采集与风险预警。社区里有一部分老人配备了智能手环、血压计、血糖仪,设备会定时把数据传到平台。平台要做的不只是"存下来",而是根据阈值判断是否异常,异常了通知谁、以什么方式通知、多长时间没响应要升级处理。

第三,服务工单流转。助餐、助浴、保洁、陪诊、代买药,这些服务的预约、派单、执行、回访、评价,要形成一个完整闭环。这个环节最考验数据库状态设计,因为工单状态一旦设计乱了,统计报表全是错的。

第四,补贴与费用管理。高龄津贴、护理补贴、服务费用减免,牵扯到审批流程。这部分业务规则因地区而异,所以系统设计上要留扩展位,不能写死。

四条主线理完之后,核心用户角色也浮现出来了:系统管理员(管账号和配置)、社区工作人员(社工/网格员,日常操作主体)、服务人员(承接工单的人)、老人家属(查看老人情况、接收告警)、老人本人(通过终端/小程序简易交互)。家属这个角色最容易漏掉,但恰恰是养老系统最有价值的服务对象之一。

1.2 一期功能边界:哪些必须做,哪些放二期

需求调研出来,能列的功能两三页纸都写不完。如果全做,工期会失控,所以我做了一期和二期切割。

一期必须做的是闭环核心:老人档案管理、健康档案管理、服务工单管理、告警管理、账号权限管理、数据统计看板。这六个模块能支撑社区服务站跑通"建档—监测—服务—回访"的完整链路。

二期再考虑:家属/老人移动端(小程序、App,用来查档案、接收消息)、IoT 设备自动接入与心电图等多维数据、政务对接与第三方支付对账。这些东西不是不重要,而是可以等业务跑起来之后,用实际数据反推需求再设计,比一次性做一堆没人用的功能强得多。

这里我总结了一张角色功能矩阵,设计菜单和权限时直接照着分:

功能模块系统管理员社区工作人员服务人员老人家属
老人档案查看/导出新增/编辑/查看仅查看被服务对象仅查看本人亲属
健康档案与告警查看全部查看/处理告警不涉及接收通知/查看
服务工单查看/统计创建/派单/回访接单/提交完成查看进度
账号权限完整管理查看本人不涉及不涉及
统计看板全部数据本网格数据不涉及不涉及

模块边界清晰之后,建表和写接口才有了依据。接下来就是技术选型的问题。

2. 技术选型不是越新越好:基于 SpringBoot 的这套组合为什么够用

技术选型这件事,我的态度一直很务实:先看项目规模和运行环境,再决定用什么。社区智慧养老系统的典型部署环境是:街道/社区的一台普通服务器,预算不高,网络环境一般,并发量不大(一个街道几百个并发就到顶了),但业务逻辑复杂、角色权限多、后续可能有移动端和 IoT 设备接入。这种场景下,SpringBoot 单体应用就是最合适的形态。

2.1 框架选型的取舍逻辑

我用的版本组合是SpringBoot 2.7.18 + JDK 1.8 + Maven 3.8。有人会问,都什么年代了还用 JDK 8?原因很实在:很多社区机房里还有老系统,运维方的 JDK 环境不一定是新的,JDK 8 的兼容性最稳。而且 SpringBoot 2.7 是 2.x 生命周期里最后一个支持 JDK 8 的稳定版本,社区资料多,遇到问题一搜全有答案。

如果你是用 JDK 17 起步,那可以直接上 SpringBoot 3.x,但注意 MyBatis-Plus、某些短信 SDK 的兼容版本要对应升级,不然启动直接报错。我个人建议,除非你明确知道要在新环境下部署,否则SpringBoot 2.7.x 是最保守稳妥的选择。

持久层我选了 MyBatis-Plus,不是 JPA。理由有三点:

  • 社区养老系统有大量多表联查和部分字段更新的操作,MyBatis-Plus 的 LambdaQueryWrapper 写起来直观,复杂 SQL 又可以手写,灵活度高。
  • 团队里新人上手快,MyBatis 系是国内 Java 开发者的基本功底。
  • 分页插件非常成熟,列表页几乎都要分页,用它省很多事。

JPA 在简单的 CRUD 上确实优雅,但在复杂查询和多人协作时,Hibernate 的实体映射和懒加载问题容易变成坑。这个项目业务规则复杂、报表查询多,选 MyBatis-Plus 更合适。

2.2 权限框架、缓存与其他组件

权限这块我用的是Spring Security + JWT。系统里有五类角色,菜单权限、按钮权限、数据权限都要控制,Spring Security 的过滤器链机制能很好地承担这个职责。JWT 做无状态登录,接口返回一个 token,前端统一携带,不需要在服务端维护 session,比较省事。

Redis 在这个系统里不是必需品,但用了之后体验提升明显。我主要拿它做两件事:一是缓存菜单权限和老人档案热点数据,减少数据库压力;二是做接口防重提交的分布式锁(后面详细讲)。如果服务器实在紧张,2G 内存的机器也能跑,Redis 和数据都在一台机器上,配置好 JVM 参数即可。

MySQL 用的 5.7,原因是老服务器上现成的版本就是 5.7,够用。字段全部用 utf8mb4,不然手机端家属名字带个生僻字就存不进去,这种事我踩过。

文件存储方面,一期我没有搞对象存储,先做成本地磁盘存储,配置一个 upload 目录映射为静态资源。如果以后部署到云上,再切换到 OSS,接口层预留了 FileService 接口,切换成本很低。

最终选型清单如下:

技术点选型选型理由
开发框架SpringBoot 2.7.18稳定成熟、兼容 JDK8、资料丰富
JDKJDK 1.8兼容老环境,部署风险最低
持久层MyBatis-Plus 3.5.x灵活、易上手、分页插件好用
权限Spring Security + JWT多角色菜单+按钮+数据权限都够用
缓存Redis 5.x热点缓存 + 防重锁
数据库MySQL 5.7现有环境、稳定够用
接口文档knife4j前后端联调方便、生成的文档可以给社区方留档
部署方式单机 JAR + systemd运维简单,升级只需要换包重启

这里多说一句:有些团队一上来就想上 Spring Cloud Alibaba、Nacos 注册中心、网关网关的,一个社区项目完全没必要。微服务解决的是协作和扩容问题,不是业务复杂度问题。单体应用把模块边界划清楚,后面真要拆,也是水到渠成的事。

3. 数据库模型设计:老人档案、服务工单与健康数据的关联玩法

数据库是整个系统的地基。我在设计表结构时,核心思路是:档案表做全、业务表做净、关联表做稳。档案表把该有的信息都放进去,业务表只管业务字段,关联关系用外键逻辑管理(实际建表不一定要物理外键,但逻辑关系必须明确)。

3.1 核心表结构拆解

先看老人档案表。这个表我命名为elderly_info,字段设计是踩过几轮坑之后定下来的:

CREATE TABLE `elderly_info` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `elderly_no` VARCHAR(32) NOT NULL COMMENT '老人编号,业务唯一', `name` VARCHAR(64) NOT NULL COMMENT '姓名', `id_card` VARCHAR(18) NOT NULL COMMENT '身份证号,加密存储', `gender` TINYINT NOT NULL COMMENT '性别 1男 2女', `birthday` DATE NOT NULL COMMENT '出生日期', `phone` VARCHAR(20) DEFAULT NULL COMMENT '本人电话', `address` VARCHAR(255) DEFAULT NULL COMMENT '居住地址', `emergency_contact_name` VARCHAR(64) DEFAULT NULL COMMENT '紧急联系人姓名', `emergency_contact_phone` VARCHAR(20) DEFAULT NULL COMMENT '紧急联系人电话', `relation` VARCHAR(20) DEFAULT NULL COMMENT '与老人关系', `health_level` TINYINT DEFAULT 1 COMMENT '健康等级 1自理 2半失能 3失能', `live_status` TINYINT DEFAULT 1 COMMENT '居住情况 1独居 2与家人同住 3其他', `subsidy_type` VARCHAR(100) DEFAULT NULL COMMENT '补贴类型,逗号分隔', `medical_history` TEXT COMMENT '既往病史', `medication_info` TEXT COMMENT '用药情况', `area_code` VARCHAR(20) DEFAULT NULL COMMENT '所属网格/片区编码', `status` TINYINT DEFAULT 1 COMMENT '状态 1正常 0注销', `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_elderly_no` (`elderly_no`), KEY `idx_area_code` (`area_code`), KEY `idx_health_level` (`health_level`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='老人档案表';

身份证号是敏感信息,生产环境我做了AES 加密后再入库,查询时按需解密展示,而不是明文存储。手机号同样处理。这个细节如果漏掉,等安全审查的时候会被打回来重做。

健康档案表health_record是这样设计的:

CREATE TABLE `health_record` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `elderly_id` BIGINT NOT NULL COMMENT '老人ID', `record_type` VARCHAR(20) NOT NULL COMMENT '指标类型:blood_pressure/blood_sugar/heart_rate', `systolic_pressure` INT DEFAULT NULL COMMENT '收缩压(血压)', `diastolic_pressure` INT DEFAULT NULL COMMENT '舒张压(血压)', `blood_sugar_value` DECIMAL(5,1) DEFAULT NULL COMMENT '血糖值', `heart_rate_value` INT DEFAULT NULL COMMENT '心率', `measure_time` DATETIME NOT NULL COMMENT '测量时间', `source` TINYINT DEFAULT 1 COMMENT '来源 1设备 2手动录入', `device_no` VARCHAR(50) DEFAULT NULL COMMENT '设备编号', `is_abnormal` TINYINT DEFAULT 0 COMMENT '是否异常 1是 0否', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_elderly_time` (`elderly_id`, `measure_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康指标记录表';

这里的is_abnormal字段是后端计算后落库的,不是查询时即时判断。原因有两个:一是告警逻辑随时间会调整,落库保留当时判断结果,后续统计口径一致;二是列表页按异常筛选走索引,不用每次联表算。

3.2 服务工单的状态流转设计

服务工单表service_order是这个系统里业务最复杂的表。我设计的字段大致是:老人ID、服务类型ID、服务人员ID、预约时间、完成时间、费用、状态、评价内容、创建人、派单时间、回访时间、回访结果。

关键是状态字段。我定的状态枚举为:0待派单 → 1待服务 → 2服务中 → 3已完成,另有4已取消和5已驳回两个终态。流转规则如下:

  • 创建工单后状态为待派单;
  • 工作人员指派服务人员时变为待服务;
  • 服务人员接单(可选,如果强制派单则跳过)后进入待服务;
  • 服务人员点击"开始服务"变为服务中;
  • 完成服务变为已完成;
  • 创建人可取消未开始的单(待派单/待服务状态),服务人员也可申请驳回(要有驳回原因字段);
  • 已完成之后由工作人员回访,回访结果放在单独的visit_record表,不改工单主状态。

状态流转必须由代码控制,不允许直接 UPDATE 状态。我封装了一个状态校验方法,每次更新前检查当前状态是否在合法流转路径上。这里有一个很容易犯的错:允许从任意状态改成已完成。这样最后统计服务完成率的时候,数据会莫名其妙地好,但实际上是脏数据。

另外一个容易忽略的点是工单号。工单号要具有可读性,比如FW20240512001,前缀 FW + 日期 + 当天自增序号。千万别用自增主键直接展示给用户,否则社区的工作人员在微信里对工单号的时候,根本记不住也看不出是哪天的单子。

4. 后端关键模块的实现细节:从登录鉴权到健康告警

骨架搭好了,接下来是血肉。这一节我挑三个最有代表性的模块讲实现:多角色登录与权限控制、健康数据异常判定与告警推送、服务工单闭环流转。这三个模块几乎覆盖了系统一半的逻辑复杂度。

4.1 多角色统一登录与权限控制的落地

系统有五类角色,登录入口是统一的。登录成功之后,后端返回 JWT token,token 里只放用户ID、用户名、角色编码三个核心信息,不放大对象。

JWT 工具类这样写:

@Component public class JwtUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expire}") private Long expire; public String generateToken(Long userId, String username, String roleCode) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("username", username) .claim("role", roleCode) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expire)) .signWith(SignatureAlgorithm.HS256, secret.getBytes(StandardCharsets.UTF_8)) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret.getBytes(StandardCharsets.UTF_8)) .parseClaimsJws(token) .getBody(); } }

注意secret不能写在代码里,放在 application.yml 中通过配置项读取,生产环境用环境变量注入。这个细节被安全工具扫描出来过,所以专门提醒。

权限控制我采用的是 Spring Security 过滤器链里加一个 JWT 认证过滤器,然后把动态菜单和按钮权限存到 Redis。每次请求进来,过滤器解析 token、加载用户权限,再通过 Spring Security 的@PreAuthorize注解做接口级权限校验。

实际项目中,很多开发者只做了"接口级权限"(谁能调这个接口),而忘了"数据级权限"(同一个接口,社工只能看到自己网格的老人)。这个坑我在下一节专门讲,这里先埋个伏笔。

4.2 健康数据异常判定与告警推送的实现

健康数据接入有两种方式:一是智能设备通过 HTTP 接口直接上报,二是社工手动录入。我设计了一个统一的HealthRecordService来做数据接收和异常判定:

@Service public class HealthRecordService { private static final Map<String, HealthRule> RULE_MAP = new HashMap<>(); static { RULE_MAP.put("blood_pressure", new HealthRule(90, 140, 60, 90)); RULE_MAP.put("blood_sugar", new HealthRule(3.9, 6.1, null, null)); RULE_MAP.put("heart_rate", new HealthRule(60, 100, null, null)); } @Transactional(rollbackFor = Exception.class) public void handleHealthData(HealthRecord record) { // 1. 查老人档案,确认老人存在且状态正常 ElderlyInfo elderly = elderlyInfoMapper.selectById(record.getElderlyId()); if (elderly == null) { throw new ServiceException("老人档案不存在"); } // 2. 根据指标类型做异常判定 HealthRule rule = RULE_MAP.get(record.getRecordType()); boolean abnormal = judgeAbnormal(record, rule); record.setIsAbnormal(abnormal ? 1 : 0); // 3. 保存健康记录 healthRecordMapper.insert(record); // 4. 如果异常,生成告警记录并触发通知 if (abnormal) { generateAlarm(record, elderly); } } private boolean judgeAbnormal(HealthRecord record, HealthRule rule) { switch (record.getRecordType()) { case "blood_pressure": return record.getSystolicPressure() > rule.getMaxHigh() || record.getSystolicPressure() < rule.getMinHigh() || record.getDiastolicPressure() > rule.getMaxLow() || record.getDiastolicPressure() < rule.getMinLow(); case "blood_sugar": return record.getBloodSugarValue() > rule.getMaxHigh() || record.getBloodSugarValue() < rule.getMinHigh(); case "heart_rate": return record.getHeartRateValue() > rule.getMaxHigh() || record.getHeartRateValue() < rule.getMinHigh(); default: return false; } } }

告警生成之后要处理两件事:一是写告警记录表,设置处理状态为"待确认";二是通过短信/公众号模板消息通知家属。这块我在做的时候学到的一个经验是:告警不是越快越好。如果老人血压 141 就立刻给家属发"血压偏高",家属一天能收到七八条,最后直接免疫了,真正严重的告警也被无视。更要命的是半夜给家属发"心率65正常"之类消息,就是骚扰。

所以我把告警分等级:数值偏离阈值但在安全区间内的,判为"观察提醒",只在系统内生成一条待回访记录;超过危险阈值的,才触发短信通知家属并转给社工处理。比如高压超过 170 才发短信,120~170 之间只是系统提醒。这个阈值规则我放在了数据库配置表里,方便业务人员调整,而不是写死在代码中。

4.3 服务工单闭环的完整实现

工单状态流转前面已经说清楚了,这里给一个核心代码片段。关键在于封装一个状态校验方法,让所有状态变更走同一入口。

@Service public class ServiceOrderService { @Autowired private ServiceOrderMapper serviceOrderMapper; private static final Map<Integer, Set<Integer>> TRANSITION_MAP = new HashMap<>(); static { TRANSITION_MAP.put(0, Set.of(1, 4)); // 待派单 -> 待服务/取消 TRANSITION_MAP.put(1, Set.of(2, 4, 5)); // 待服务 -> 服务中/取消/驳回 TRANSITION_MAP.put(2, Set.of(3)); // 服务中 -> 已完成 } @Transactional(rollbackFor = Exception.class) public void changeStatus(Long orderId, Integer targetStatus, Long operatorId) { ServiceOrder order = serviceOrderMapper.selectById(orderId); if (order == null) { throw new ServiceException("工单不存在"); } Set<Integer> allowed = TRANSITION_MAP.get(order.getStatus()); if (allowed == null || !allowed.contains(targetStatus)) { throw new ServiceException("非法状态流转: " + order.getStatus() + " -> " + targetStatus); } // 业务校验:取消要填原因,完成要校验预约时间、服务人等 order.setStatus(targetStatus); order.setUpdateTime(new Date()); serviceOrderMapper.updateById(order); } }

这段代码看着简单,但它解决了最头大的问题:并发状态下重复提交导致的脏数据。比如服务员手速快点两下"完成服务",如果没有状态校验,数据可能被更新两次,虽然结果一样,但操作日志会多出一条,审计的时候说不清。

另外,我在创建工单的接口上做了防重处理。思路是:前端生成一个requestId(UUID),后端在 Redis 里以这个 requestId 为 key 设置过期时间 10 秒的锁,如果 key 已存在,直接拒绝请求,返回"请勿重复提交"。这个方案比数据库唯一索引更灵活,因为工单本身没有业务唯一键,用 requestId 做防重最干净。

5. 真正上线前容易踩的坑:数据权限、接口幂等与部署细节

这段内容全部来自真实上线过程中踩过的坑。很多系统在 Demo 阶段一切正常,一上生产就各种问题,根源往往不在功能逻辑,而在这几类非功能性设计上。

5.1 数据权限:为什么光有角色认证还不够

社区场景下,每个社工只管自己网格的老人。如果账号权限只做到"社工可以访问老人档案接口",那么任何一个社工登录后,都能通过循环遍历 ID 查看全街道的老人数据。这在功能上没错,在数据安全和业务规则上是严重缺陷。

我的解决思路:在用户表加一个area_code(网格编码),老人档案表也加area_code。查询列表时,在 MyBatis-Plus 查询构造器里强制追加数据权限条件:

public void appendDataScope(LambdaQueryWrapper<ElderlyInfo> wrapper, LoginUser loginUser) { if (loginUser.isAdmin()) { return; // 管理员不过滤 } if ("community_worker".equals(loginUser.getRoleCode())) { wrapper.eq(ElderlyInfo::getAreaCode, loginUser.getAreaCode()); } // 服务人员只看到自己被服务对象 if ("service_person".equals(loginUser.getRoleCode())) { wrapper.in(ElderlyInfo::getId, getServedElderlyIds(loginUser.getUserId())); } }

这里有一个细节差点坑死人:服务人员权限有个特殊情况,工单创建人可以看全街道老人列表(因为要选服务对象),但只能看当前是"待派单"状态的工单。如果一刀切按 area_code 过滤,社工根本没法正常建单。所以数据权限要区分三个维度:菜单权限(能不能进)、接口权限(能不能调用)、数据范围(能看哪些行)。三者独立控制,才能灵活应对真实业务。

5.2 接口设计里的几个坑

第一个坑是时间字段的时区问题。服务器如果是 UTC 时区,数据库连接串没指定serverTimezone=Asia/Shanghai,查询出来的时间就会差 8 小时。健康数据差 8 小时意味着凌晨的告警记录被显示成上午,家属会误以为老人白天出事,直接打电话过来质问。解决方案:JDBC 连接串必须带上时区参数,应用层统一用LocalDateTime,前端展示时再格式化。

第二个坑是身份证号校验切忌只验长度。社区录入人员有时候会把 15 位老身份证号、身份证带 X 的都录进来,如果只按 18 位校验,一批老人都建档失败。我的做法是参照现行身份证编码规则写一个校验工具,兼容 15 位和 18 位,生日字段直接从身份证里提取,避免手工填错。18 位身份证最后一位是校验码,算法固定,网上有现成实现,别自己在纸上推,费时间还容易错。

第三个坑是导出功能导致服务器内存溢出。列表页+导出是标配,但直接用 POI 把几万条记录一次性查出来写入 Excel,老服务器的内存直接被打爆。我给导出功能加了一个限制:单次导出最多 5000 条,超过之后提示用户按条件筛选导出。同时用SXSSFWorkbook(流式处理),一边查一边写,内存占用从几百兆降到几十兆。这个优化做完,导出功能才算真正能用了。

5.3 打包部署与老社区网络环境的适配

部署环境通常是一台 2C4G 的云服务器或机房实体机,操作系统是 CentOS 7 这种老家伙。打包部署采用最直接的方案:Maven 打成可执行 JAR,配合 systemd 服务管理。JVM 参数我用的是一组保守配置:

java -Xms512m -Xmx1024m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \ -jar smart-elderly-care.jar --spring.profiles.active=prod &

这里要特别注意:MySQL 连接池、Redis 连接池的最大连接数不要按默认配。默认的 HikariCP 连接池最大连接数是 10,看起来够用,但社区场景会有集中录入的高峰(比如上午社工统一上传数据),连接会排队甚至超时。我把连接池最大设到 20,最小空闲 5 个,性能立刻改善。在 4G 内存的服务器上,这个值要配合 JVM 内存一起调,不能只追求大。

网络环境的适配也是坑。有的社区网络出口会有白名单策略,短信服务商的接口域名不一定开放,导致告警短信发不出去。我的建议是:提前与居委会 IT 确认好服务器出网策略和短信/公众号服务商的支持情况,并且做好公告集群发消息失败后的重试与记录,谁能看到错误信息,谁负责重试,都要在系统里留痕。

6. 从测试到上线:我在实际项目中沉淀的几个额外经验

系统开发完成只是第一步,真正让它跑起来、跑得好,靠的是上线前后的打磨。这部分我写几个不属于代码、但同样影响项目成败的经验。

第一,上线前一定要找真实社工试用,而不是自己拿测试数据自嗨。我当时的做法是找一个关系不错的社区,把系统部署到测试环境,让两位社工拿真实老人数据试了一周。结果是:她们一眼就发现了订单列表缺少"按时间倒序"排序,健康档案录入时缺少"最近一次测量时间"的快捷筛选,这些都是我坐在电脑前想象不出来的真实使用习惯。业务系统的交互细节,只有实际使用者能告诉你答案。

第二,迁移存量数据比开发系统更费时间。社区里有大量存量老人档案,纸质表格、Excel、政务系统导出的格式各不相同,身份证格式不统一、电话缺位、地址写法混乱。我当时安排了两周左右专门做数据清洗:写脚本批量校验、人工核对、按网格分批导入。数据质量决定系统初期体验,如果导入 5000 个老人,有 300 个身份证格式不对,社工一搜名字搜不到,信任感瞬间崩塌。

第三,预留一个扩展字段位。养老政策和社区服务方向变化很快,今天补贴类型有三种,明天就可能加一个新的。我在老人档案表里预留了ext_infoJSON 字段,系统级配置表留了config_key/config_value,这样政策调整时不用动表结构,改配置就能跟上。

第四,一定要有除草机制。智慧养老系统的核心数据是老人档案。老人去世、搬离辖区之后,status要由社工及时置为注销状态,而不是一直占用活动档案。我加了每月提醒功能:系统自动筛出一年没有健康记录、没有服务工单的老人,生成"活跃度预警"列表,让社工主动核实。这个功能对数据保鲜的帮助极大,也是这类系统区别于普通 CRUD 系统的价值所在。

最后再说一个理念层面的体会:社区智慧养老系统的难点,从来不是技术,而是对业务细节的捕捉和尊重。你要理解社工的一天是怎么度过的:她们早上要批量录入数据,中午要给老人打电话约服务,下午要上门回访,晚上可能还要处理一条健康告警。系统如果能把她们从反复抄写、到处翻数据里解放出来,这个 SpringBoot 项目就算真正做成了。技术再炫,最终都是为了一句话:让老人能得到更及时、更安心的照护。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询