简介:本资源为基于SpringBoot的社区老人健康信息管理系统毕业论文参考文档,面向计算机专业应届毕业生及需要完成毕设选题的学生,尤其适合选择Java Web方向、关注老龄化健康管理场景的开发者。论文围绕管理员与用户两类角色展开,涵盖就诊历史、健康信息、药品入库与使用、医保信息、医生预约、论坛公告等模块,并给出B/S与MVC架构、MySQL 5.7、MyBatis、Vue等技术选型说明,可帮助读者快速理解系统功能划分与数据库设计思路。资源包共1个docx文件,约1.44MB,内容为完整论文正文,包含摘要、目录、绪论及各章节论述,便于直接参考写作框架与需求分析表述。目前已有670人学习下载,适合作为毕设开题、论文撰写与系统功能梳理的参考素材。
1. 社区老人健康信息管理系统:从一张纸质档案表到可运行的 Spring Boot 服务
社区里管着几百位老人的健康档案,血压、血糖、用药记录、随访时间散落在纸质表格和几个 Excel 里,每次要找某位老人上个月的血糖趋势,得翻半天。这个场景不是个例,很多社区服务中心、养老驿站、基层卫生站都在用类似方式管数据。基于 Spring Boot 的社区老人健康信息管理系统,要解决的就是把这类分散、易丢、难统计的健康信息收拢到一个可查询、可录入、可预警的后台服务里。它适合有 Java Web 基础、想做一个完整业务系统的开发者,也适合需要给社区场景做信息化落地的技术负责人。核心难点不在框架本身,而在于老人健康数据的字段设计、随访周期管理和权限边界——这三块决定了系统能不能真正用起来,而不是一个演示 Demo。
2. 需求拆解与数据建模:老人健康档案到底该存哪些字段
2.1 从业务动作反推实体关系
做这类系统最容易翻车的地方,是一上来就建表,结果写到一半发现字段不够用。我一般的做法是先把社区工作人员的日常动作列出来:建档、录入体征、安排随访、记录用药、查看历史、导出报表。每个动作对应一组数据操作,再把这些操作归并成实体。
核心实体有四个:老人基本信息、健康体征记录、随访计划、用药记录。老人和体征是一对多,老人和随访计划是一对多,老人和用药也是一对多。这里有个容易忽略的点:随访计划本身有状态流转(待随访、已随访、逾期),所以它不能只是一张静态表,需要状态字段和计划时间字段配合。
-- 老人基本信息表 CREATE TABLE elder_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL COMMENT '姓名', id_card VARCHAR(18) UNIQUE COMMENT '身份证号,脱敏存储', birth_date DATE COMMENT '出生日期', gender TINYINT COMMENT '0未知 1男 2女', phone VARCHAR(20) COMMENT '联系电话', address VARCHAR(255) COMMENT '居住地址', chronic_disease VARCHAR(255) COMMENT '慢性病标签,逗号分隔', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT '老人基本信息'; -- 健康体征记录表 CREATE TABLE health_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, elder_id BIGINT NOT NULL, systolic INT COMMENT '收缩压 mmHg', diastolic INT COMMENT '舒张压 mmHg', blood_sugar DECIMAL(5,2) COMMENT '血糖 mmol/L', heart_rate INT COMMENT '心率 次/分', measure_time DATETIME NOT NULL COMMENT '测量时间', recorder VARCHAR(32) COMMENT '记录人', remark VARCHAR(255) COMMENT '备注', INDEX idx_elder_time (elder_id, measure_time) ) COMMENT '健康体征记录';上面两张表是最基础的。elder_info里chronic_disease用逗号分隔存标签,是为了查询方便,但如果要做慢性病统计,建议单独建一张关联表。health_record的联合索引idx_elder_time很关键,因为查历史趋势时几乎都是按老人加时间范围查,没有这个索引,数据量上去后查询会明显变慢。
2.2 随访计划的状态机设计
随访计划是这类系统里业务逻辑最重的一块。它不是简单的增删改查,而是有明确的状态流转:计划创建后是「待随访」,到了计划日期还没处理就变成「逾期」,工作人员完成随访后变成「已随访」。这个状态不能只靠前端判断,必须在服务端用定时任务或查询时动态计算。
// 随访计划实体 @Entity @Table(name = "follow_up_plan") public class FollowUpPlan { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "elder_id", nullable = false) private Long elderId; @Column(name = "plan_date", nullable = false) private LocalDate planDate; // 计划随访日期 @Column(name = "actual_date") private LocalDate actualDate; // 实际随访日期 @Column(name = "status", length = 16) private String status; // PENDING / DONE / OVERDUE @Column(name = "follow_type", length = 32) private String followType; // 电话随访 / 上门随访 / 门诊随访 @Column(name = "content", length = 500) private String content; // 随访内容记录 }状态字段status我建议用字符串而不是数字,因为排查问题时肉眼可读,日志里一眼就能看出问题。planDate和actualDate分开存,不要合并成一个时间字段,否则统计按时随访率时会很麻烦。逾期判断的逻辑放在 Service 层,每次查询待办列表时,把planDate < 今天且status = PENDING的记录标记为OVERDUE,同时写回数据库,避免每次都要重新计算。
2.3 权限模型:谁能看、谁能改
社区场景下的权限比一般后台系统更敏感。老人的健康数据属于隐私信息,不是所有工作人员都能看全部字段。常见的做法是分三级:管理员可以看所有数据和配置,医生或护士可以录入和查看自己负责的老人,普通工作人员只能看基本信息和随访状态,看不到具体体征数值。
// 基于 Spring Security 的方法级权限控制 @PreAuthorize("hasRole('ADMIN') or hasRole('DOCTOR')") @GetMapping("/health-record/{elderId}") public Result<List<HealthRecordVO>> getHealthRecords(@PathVariable Long elderId) { // 医生和管理员才能查看具体体征数据 return Result.ok(healthRecordService.listByElderId(elderId)); } @PreAuthorize("hasAnyRole('ADMIN', 'DOCTOR', 'WORKER')") @GetMapping("/elder/{id}/basic") public Result<ElderBasicVO> getElderBasic(@PathVariable Long id) { // 所有角色都能看基本信息,但返回的 VO 里不包含敏感字段 return Result.ok(elderService.getBasicInfo(id)); }这里的关键是返回对象要分 VO,不要直接把 Entity 丢给前端。ElderBasicVO里不包含身份证号、详细病史这些字段,只有姓名、年龄、随访状态。很多开发者图省事直接返回 Entity,结果前端一抓包什么都能看到,这在健康数据场景下是硬伤。
3. 用 Spring Boot 搭起可运行的后端骨架
3.1 项目结构与依赖选型
一个能跑起来的社区老人健康信息管理系统,后端不需要太花哨的组件。核心依赖就几个:Spring Boot Web、Spring Data JPA 或 MyBatis-Plus、MySQL 驱动、Spring Security、Lombok。如果要做定时任务标记逾期随访,再加一个 Spring Boot 自带的@Scheduled就够了,不用上 Quartz。
<!-- pom.xml 核心依赖 --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>选 JPA 还是 MyBatis-Plus,看团队习惯。JPA 的好处是实体和表结构绑定紧密,改字段时不容易漏;MyBatis-Plus 的好处是复杂查询写起来更直观。这个系统里统计类查询比较多,比如按月统计随访完成率,用 MyBatis-Plus 的自定义 SQL 会更顺手。我一般会选 MyBatis-Plus,但如果你更熟悉 JPA,用 JPA 也完全没问题,核心逻辑不变。
3.2 配置文件与数据库连接
application.yml里几个关键配置要写对,尤其是时区和连接池。健康数据里时间字段很多,时区不对会导致随访日期差一天,这种问题排查起来很费时间。
spring: datasource: url: jdbc:mysql://localhost:3306/elder_health?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: format_sql: true jdbc: time_zone: Asia/ShanghaiserverTimezone=Asia/Shanghai和hibernate.jdbc.time_zone这两个都要设,只设一个有时候还是会出问题。ddl-auto: update在开发阶段方便,但上线前一定要改成none或validate,否则哪天实体改错了,表结构被自动改掉,数据丢了连后悔药都没有。
3.3 健康记录录入接口的实现
录入体征数据是使用频率最高的接口。这里要注意两点:一是数值范围校验,血压、血糖都有合理区间,超出范围的要拦截;二是重复录入判断,同一老人同一时间点不应该有两条记录。
@RestController @RequestMapping("/api/health") @RequiredArgsConstructor public class HealthRecordController { private final HealthRecordService healthRecordService; @PostMapping("/record") public Result<Void> addRecord(@RequestBody @Valid HealthRecordDTO dto) { // 校验数值合理性 if (dto.getSystolic() != null && (dto.getSystolic() < 50 || dto.getSystolic() > 300)) { return Result.fail("收缩压数值超出合理范围"); } if (dto.getBloodSugar() != null && dto.getBloodSugar().doubleValue() > 33.3) { return Result.fail("血糖数值超出合理范围"); } healthRecordService.saveRecord(dto); return Result.ok(); } }@Valid配合 DTO 上的注解做基础校验,业务规则校验放在 Controller 或 Service 里。数值范围我一般会放在 Service 层,因为可能多个入口都要用。重复录入的判断用elder_id + measure_time做唯一约束,数据库层面加唯一索引,代码里捕获异常返回友好提示,比先查再插更可靠。
4. 随访提醒与数据统计:让系统真正被用起来
4.1 定时任务标记逾期随访
随访计划如果没人盯着,很容易过期。用 Spring Boot 的@Scheduled每天凌晨跑一次,把昨天之前还没完成的计划标记为逾期,同时可以生成一条提醒消息。
@Component @RequiredArgsConstructor public class FollowUpScheduler { private final FollowUpPlanRepository planRepository; // 每天凌晨1点执行 @Scheduled(cron = "0 0 1 * * ?") @Transactional public void markOverduePlans() { LocalDate today = LocalDate.now(); List<FollowUpPlan> overduePlans = planRepository .findByStatusAndPlanDateBefore("PENDING", today); for (FollowUpPlan plan : overduePlans) { plan.setStatus("OVERDUE"); } planRepository.saveAll(overduePlans); // 这里可以追加消息推送逻辑 } }cron表达式0 0 1 * * ?表示每天凌晨 1 点执行。注意方法要加@Transactional,否则批量更新时可能部分成功部分失败。如果数据量大,saveAll要分批处理,一次几千条没问题,上万条建议分页。
4.2 按月统计随访完成率的 SQL
统计类查询用原生 SQL 更直接。下面这条查的是每个月的随访计划总数和已完成数,用来算完成率。
SELECT DATE_FORMAT(plan_date, '%Y-%m') AS month, COUNT(*) AS total, SUM(CASE WHEN status = 'DONE' THEN 1 ELSE 0 END) AS done_count, ROUND(SUM(CASE WHEN status = 'DONE' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS rate FROM follow_up_plan WHERE plan_date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH) GROUP BY DATE_FORMAT(plan_date, '%Y-%m') ORDER BY month;DATE_FORMAT按月份分组,CASE WHEN统计已完成数量。ROUND保留两位小数。这个查询走plan_date索引,如果表数据量大,建议在plan_date和status上建联合索引。
4.3 体征趋势数据的接口设计
前端画趋势图需要按时间返回体征数据。接口设计上,不要一次返回全部历史,而是支持时间范围参数,默认返回最近 30 天。
@GetMapping("/trend/{elderId}") public Result<List<TrendVO>> getTrend( @PathVariable Long elderId, @RequestParam(defaultValue = "30") int days) { LocalDateTime end = LocalDateTime.now(); LocalDateTime start = end.minusDays(days); List<HealthRecord> records = healthRecordRepository .findByElderIdAndMeasureTimeBetweenOrderByMeasureTimeAsc(elderId, start, end); List<TrendVO> result = records.stream() .map(r -> new TrendVO(r.getMeasureTime(), r.getSystolic(), r.getDiastolic(), r.getBloodSugar())) .collect(Collectors.toList()); return Result.ok(result); }days参数默认 30,前端可以传 7、30、90。返回的TrendVO只包含图表需要的字段,不要把整个HealthRecord返回去。按measureTime升序排列,前端拿到就能直接画。
5. 避坑与排查:这类系统最容易翻车的五个地方
5.1 时间字段时区不一致导致随访日期差一天
现象是前端显示的随访日期和后端存的不一样,或者统计时某天的数据跑到前一天去了。原因是数据库连接时区、JVM 时区、Hibernate 时区三者不一致。解决方法是application.yml里serverTimezone和hibernate.jdbc.time_zone都设成Asia/Shanghai,同时服务器系统时区也确认一下。这个坑很隐蔽,开发阶段数据少不容易发现,上线后跨月统计时才会暴露。
5.2 直接返回 Entity 导致敏感字段泄露
现象是前端抓包能看到身份证号、详细病史等不该展示的字段。原因是 Controller 直接返回了 Entity 或包含了全部字段的 VO。解决方法是每个接口定义独立的 VO,只包含该接口需要的字段,敏感字段在 VO 里直接不定义。另外可以在 Entity 的敏感字段上加@JsonIgnore作为兜底,但不要只依赖这个。
5.3 随访状态只在查询时计算导致数据不一致
现象是列表页显示逾期,详情页显示待随访,或者统计数字对不上。原因是状态没有持久化,每次查询时动态判断,不同接口判断逻辑不一致。解决方法是用定时任务统一标记逾期状态,所有接口都读同一个status字段。如果确实需要动态判断,把判断逻辑抽成一个公共方法,所有地方都调它。
5.4 体征数值没有范围校验导致脏数据
现象是数据库里出现收缩压 500、血糖 100 这种明显不合理的值。原因是录入接口只做了非空校验,没有业务范围校验。解决方法是在 Service 层加数值范围判断,超出范围的直接拒绝并返回明确提示。范围值可以参考医学常识:收缩压 50-300,舒张压 30-200,血糖 1-33.3,心率 30-250。
5.5 批量操作没有事务导致部分成功
现象是批量标记逾期时,一部分记录更新了,一部分没更新,数据处于中间状态。原因是方法没有加@Transactional,或者异常被吞掉了。解决方法是批量写操作的方法统一加@Transactional,并且不要在循环里 catch 异常后不抛出。如果确实需要部分成功,那就拆成多次单条操作,明确记录哪些成功哪些失败。
6. 从能跑到好用:三个提升系统实用性的技巧
第一个技巧是给健康记录加一个「异常标记」字段。不是所有超出正常范围的值都需要报警,但需要让工作人员一眼看到。我一般会在 Service 层保存记录时,根据预设阈值自动计算一个abnormal_level字段,0 表示正常,1 表示偏高,2 表示偏高较多。前端列表里用不同颜色标出来,工作人员扫一眼就知道哪位老人需要重点关注。这个字段不需要实时计算,存下来查询更快。
private int calcAbnormalLevel(Integer systolic, Integer diastolic, BigDecimal bloodSugar) { int level = 0; if (systolic != null && systolic >= 140) level = Math.max(level, 1); if (systolic != null && systolic >= 160) level = Math.max(level, 2); if (diastolic != null && diastolic >= 90) level = Math.max(level, 1); if (bloodSugar != null && bloodSugar.doubleValue() >= 7.0) level = Math.max(level, 1); if (bloodSugar != null && bloodSugar.doubleValue() >= 11.1) level = Math.max(level, 2); return level; }阈值可以根据实际业务调整,这里给的是一般参考值。关键是这个逻辑要放在保存时执行一次,而不是每次查询时算。
第二个技巧是随访计划支持批量生成。社区里很多老人的随访周期是一样的,比如高血压患者每季度随访一次。与其一个个建计划,不如提供一个批量生成接口,选一批老人、选随访类型、选周期,自动生成未来一年的计划。
@PostMapping("/batch-generate") @Transactional public Result<Integer> batchGenerate(@RequestBody BatchPlanDTO dto) { List<FollowUpPlan> plans = new ArrayList<>(); for (Long elderId : dto.getElderIds()) { LocalDate date = dto.getStartDate(); for (int i = 0; i < dto.getMonths(); i++) { FollowUpPlan plan = new FollowUpPlan(); plan.setElderId(elderId); plan.setPlanDate(date.plusMonths(i)); plan.setStatus("PENDING"); plan.setFollowType(dto.getFollowType()); plans.add(plan); } } planRepository.saveAll(plans); return Result.ok(plans.size()); }months参数控制生成几个月,startDate是起始日期。这个方法一定要加@Transactional,否则生成到一半失败会留下不完整的计划。
第三个技巧是导出功能用流式查询。社区经常需要把老人健康数据导出成 Excel 给上级看。如果数据量大,一次性查出来再写 Excel 会内存溢出。用 MyBatis 的Cursor或者 JPA 的Stream做流式查询,边查边写,内存占用稳定。
@GetMapping("/export") public void export(HttpServletResponse response) throws IOException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=health_export.xlsx"); try (Stream<HealthRecord> stream = healthRecordRepository.streamAll(); ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) { // 分批写入,每 500 条刷一次 List<HealthRecord> batch = new ArrayList<>(500); Iterator<HealthRecord> it = stream.iterator(); while (it.hasNext()) { batch.add(it.next()); if (batch.size() >= 500) { writer.write(batch, buildSheet()); batch.clear(); } } if (!batch.isEmpty()) { writer.write(batch, buildSheet()); } } }流式查询要注意数据库连接不能提前关闭,所以整个导出过程要在一个事务里,或者用@Transactional(readOnly = true)包住。另外Stream用完后必须关闭,用 try-with-resources 最稳妥。
这三个技巧里,异常标记和批量生成是直接提升日常使用效率的,流式导出是数据量上来后的必备优化。我自己的习惯是先把异常标记做进去,因为这个功能上线后工作人员反馈最直接,他们会觉得系统「有用」,而不是一个填数据的地方。希望帮到你。
本文还有配套的精品资源,点击获取