1. 智慧社区居家养老健康管理系统概述
在人口老龄化日益加剧的背景下,智慧社区居家养老健康管理系统应运而生。这个基于Java Web技术栈开发的系统,旨在为社区老年人提供全方位的健康管理和生活服务支持。系统采用前后端分离架构,后端使用SpringBoot2框架,前端采用Vue3实现,数据库选用MySQL8.0,通过MyBatis-Plus简化数据访问层开发。
这个系统最核心的价值在于将传统的社区养老服务数字化、智能化。通过技术手段,我们可以实时监测老人的健康状况,及时预警潜在风险,同时为老人提供便捷的生活服务。系统通常包含健康数据监测、用药提醒、紧急呼叫、社区服务预约等功能模块,形成一个完整的居家养老解决方案。
2. 技术栈选型与架构设计
2.1 SpringBoot2后端框架优势
选择SpringBoot2作为后端框架主要基于以下几个考虑:
- 快速开发:SpringBoot的自动配置和起步依赖大大减少了配置工作
- 内嵌服务器:简化部署流程,可以直接打包成可执行JAR
- 丰富的生态系统:与MyBatis-Plus等常用ORM框架无缝集成
- 健康检查:提供/actuator端点,方便监控系统运行状态
在实际开发中,我们通常会按照以下结构组织代码:
code复制src/main/java
├── config # 配置类
├── controller # 控制器层
├── service # 业务逻辑层
├── dao # 数据访问层
├── entity # 实体类
├── dto # 数据传输对象
├── vo # 视图对象
└── Application.java # 启动类
2.2 Vue3前端框架特性
Vue3相比Vue2有几个显著改进:
- Composition API:更好的逻辑复用和代码组织
- 性能提升:更快的渲染速度和更小的包体积
- TypeScript支持:更好的类型推断和开发体验
- Teleport组件:更灵活的DOM结构控制
在养老系统前端开发中,我们特别利用了Vue3的这些特性:
javascript复制// 使用Composition API组织健康数据监控逻辑
import { ref, onMounted } from 'vue'
import { getHealthData } from '@/api/health'
export default {
setup() {
const healthData = ref([])
const loading = ref(false)
const fetchData = async () => {
loading.value = true
try {
healthData.value = await getHealthData()
} finally {
loading.value = false
}
}
onMounted(fetchData)
return { healthData, loading }
}
}
2.3 MyBatis-Plus高效数据访问
MyBatis-Plus在原生MyBatis基础上提供了更多便利功能:
- 通用CRUD操作:内置常用方法,减少重复代码
- 条件构造器:流畅的API构建复杂查询
- 分页插件:简化分页逻辑实现
- 乐观锁:便捷处理并发更新
在养老系统中,我们这样使用MyBatis-Plus:
java复制// 老人健康数据Mapper接口
public interface ElderlyHealthMapper extends BaseMapper<ElderlyHealth> {
// 自定义复杂查询
@Select("SELECT * FROM elderly_health WHERE age > #{age} ORDER BY check_date DESC")
List<ElderlyHealth> selectByAge(@Param("age") int age);
}
// 服务层使用示例
@Service
public class HealthServiceImpl implements HealthService {
@Autowired
private ElderlyHealthMapper healthMapper;
public Page<ElderlyHealth> getHealthPage(int pageNum, int pageSize) {
Page<ElderlyHealth> page = new Page<>(pageNum, pageSize);
return healthMapper.selectPage(page,
Wrappers.<ElderlyHealth>lambdaQuery()
.orderByDesc(ElderlyHealth::getCheckDate));
}
}
2.4 MySQL8.0数据库设计
针对养老系统的特点,我们设计了以下核心表结构:
- 老人基本信息表(elderly_info)
sql复制CREATE TABLE `elderly_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`gender` tinyint NOT NULL COMMENT '1-男,2-女',
`birth_date` date NOT NULL,
`id_card` varchar(18) NOT NULL,
`address` varchar(200) NOT NULL,
`contact_phone` varchar(20) NOT NULL,
`emergency_contact` varchar(50) NOT NULL,
`emergency_phone` varchar(20) NOT NULL,
`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_id_card` (`id_card`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
- 健康数据表(health_data)
sql复制CREATE TABLE `health_data` (
`id` bigint NOT NULL AUTO_INCREMENT,
`elderly_id` bigint NOT NULL,
`check_date` datetime NOT NULL,
`heart_rate` int DEFAULT NULL COMMENT '心率',
`blood_pressure_high` int DEFAULT NULL COMMENT '高压',
`blood_pressure_low` int DEFAULT NULL COMMENT '低压',
`blood_oxygen` decimal(5,2) DEFAULT NULL COMMENT '血氧饱和度',
`temperature` decimal(3,1) DEFAULT NULL COMMENT '体温',
`weight` decimal(5,2) DEFAULT NULL COMMENT '体重',
`remark` varchar(500) DEFAULT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_elderly_id` (`elderly_id`),
KEY `idx_check_date` (`check_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3. 核心功能模块实现
3.1 健康数据监测与分析
健康数据监测是系统的核心功能,主要包括:
- 数据采集:通过IoT设备或手动录入方式获取健康指标
- 数据存储:持久化到MySQL数据库
- 数据分析:计算趋势、识别异常
- 预警通知:当指标超出阈值时触发告警
后端接口实现示例:
java复制@RestController
@RequestMapping("/api/health")
public class HealthController {
@Autowired
private HealthService healthService;
@PostMapping("/upload")
public Result uploadHealthData(@RequestBody HealthDataDTO dto) {
// 数据校验
if (dto.getHeartRate() < 40 || dto.getHeartRate() > 150) {
return Result.fail("心率数据异常");
}
// 保存数据
boolean success = healthService.saveHealthData(dto);
// 检查是否需要预警
healthService.checkHealthWarning(dto);
return success ? Result.success() : Result.fail("数据保存失败");
}
@GetMapping("/trend/{elderlyId}")
public Result getHealthTrend(@PathVariable Long elderlyId,
@RequestParam String type,
@RequestParam String period) {
List<HealthTrendVO> trend = healthService.getHealthTrend(elderlyId, type, period);
return Result.success(trend);
}
}
3.2 用药提醒功能实现
用药提醒功能需要考虑以下关键点:
- 用药计划设置:支持多种用药频率(每日、隔日、每周等)
- 提醒时间计算:根据计划生成具体的提醒时间点
- 多渠道通知:APP推送、短信、电话等
- 用药记录:记录老人是否按时服药
我们使用Quartz实现定时提醒:
java复制public class MedicationReminderJob implements Job {
@Autowired
private MedicationService medicationService;
@Autowired
private NotificationService notificationService;
@Override
public void execute(JobExecutionContext context) {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
Long reminderId = dataMap.getLong("reminderId");
MedicationReminder reminder = medicationService.getReminderById(reminderId);
// 发送提醒
notificationService.sendMedicationReminder(reminder);
// 记录提醒日志
medicationService.recordReminderLog(reminderId);
}
}
// 设置定时任务
public void scheduleReminder(MedicationReminder reminder) {
JobDetail job = JobBuilder.newJob(MedicationReminderJob.class)
.withIdentity("reminder_" + reminder.getId())
.usingJobData("reminderId", reminder.getId())
.build();
// 根据用药频率构建Cron表达式
String cronExpression = buildCronExpression(reminder);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger_" + reminder.getId())
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.scheduleJob(job, trigger);
}
3.3 紧急呼叫与响应
紧急呼叫是养老系统的关键安全功能,实现要点包括:
- 一键呼叫:老人可快速触发紧急呼叫
- 位置信息:自动附带老人当前位置
- 多级响应:同时通知家属、社区工作人员和急救中心
- 呼叫记录:完整记录事件处理过程
前端紧急呼叫组件实现:
vue复制<template>
<div class="emergency-call">
<button
@touchstart="startHold"
@touchend="endHold"
@mousedown="startHold"
@mouseup="endHold"
class="emergency-button"
>
按住3秒紧急呼叫
</button>
<div v-if="holding" class="hold-progress">
<div class="progress-bar" :style="{width: holdProgress + '%'}"></div>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import { sendEmergency } from '@/api/emergency'
export default {
setup() {
const holding = ref(false)
const holdProgress = ref(0)
let holdTimer = null
const startHold = () => {
holding.value = true
holdProgress.value = 0
holdTimer = setInterval(() => {
holdProgress.value += 10
if (holdProgress.value >= 100) {
triggerEmergency()
endHold()
}
}, 300)
}
const endHold = () => {
holding.value = false
if (holdTimer) {
clearInterval(holdTimer)
holdTimer = null
}
}
const triggerEmergency = async () => {
try {
// 获取当前位置
const position = await getCurrentPosition()
await sendEmergency({
lat: position.coords.latitude,
lng: position.coords.longitude,
timestamp: new Date().getTime()
})
// 显示呼叫成功提示
} catch (error) {
// 错误处理
}
}
return { holding, holdProgress, startHold, endHold }
}
}
</script>
4. 系统部署与性能优化
4.1 生产环境部署方案
推荐的生产环境部署架构:
code复制前端部署:
- Nginx作为静态资源服务器
- 配置gzip压缩
- 开启HTTP/2
- 配置缓存策略
后端部署:
- JDK11+
- 使用Docker容器化部署
- 配置JVM参数(-Xms, -Xmx)
- 多实例负载均衡
数据库部署:
- MySQL8.0主从复制
- 配置合理的innodb_buffer_pool_size
- 定期备份策略
Docker部署示例:
dockerfile复制# 后端Dockerfile
FROM openjdk:11-jre
WORKDIR /app
COPY target/elderly-care-system.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]
4.2 性能优化实践
- 数据库优化:
sql复制-- 为常用查询添加合适索引
ALTER TABLE health_data ADD INDEX idx_elderly_check (elderly_id, check_date);
-- 优化查询语句
EXPLAIN SELECT * FROM health_data
WHERE elderly_id = 123
AND check_date BETWEEN '2023-01-01' AND '2023-12-31'
ORDER BY check_date DESC LIMIT 100;
- 缓存策略:
java复制// 使用Spring Cache缓存健康数据
@Cacheable(value = "healthData", key = "#elderlyId + '_' + #type + '_' + #period")
public List<HealthTrendVO> getHealthTrend(Long elderlyId, String type, String period) {
// 数据库查询逻辑
}
// 缓存配置
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.disableCachingNullValues()
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
- 接口性能监控:
java复制// 使用Micrometer监控接口性能
@RestController
@RequestMapping("/api")
public class HealthController {
private final Timer healthTimer;
public HealthController(MeterRegistry registry) {
this.healthTimer = Timer.builder("api.health")
.description("健康数据接口计时")
.register(registry);
}
@GetMapping("/health/{id}")
public Result getHealthData(@PathVariable Long id) {
return healthTimer.record(() -> {
// 业务逻辑
return healthService.getHealthData(id);
});
}
}
5. 安全防护与数据隐私
5.1 系统安全防护措施
- 认证与授权:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/health/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public JwtFilter jwtFilter() {
return new JwtFilter();
}
}
- 敏感数据加密:
java复制// 使用AES加密敏感信息
public class CryptoUtils {
private static final String SECRET_KEY = "your-256-bit-secret";
private static final String SALT = "fixed-salt";
public static String encrypt(String data) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(SECRET_KEY.toCharArray(), SALT.getBytes(), 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] iv = cipher.getIV();
byte[] cipherText = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
byte[] combined = new byte[iv.length + cipherText.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
return Base64.getEncoder().encodeToString(combined);
} catch (Exception e) {
throw new RuntimeException("加密失败", e);
}
}
}
5.2 隐私数据保护方案
- 数据脱敏处理:
java复制// 身份证号脱敏
public static String maskIdCard(String idCard) {
if (idCard == null || idCard.length() < 8) {
return idCard;
}
return idCard.substring(0, 3) + "********" + idCard.substring(idCard.length() - 4);
}
// 手机号脱敏
public static String maskPhone(String phone) {
if (phone == null || phone.length() < 7) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(7);
}
- 数据库字段加密:
java复制// 使用JPA属性转换器实现字段级加密
@Converter
public class CryptoConverter implements AttributeConverter<String, String> {
@Override
public String convertToDatabaseColumn(String attribute) {
return attribute == null ? null : CryptoUtils.encrypt(attribute);
}
@Override
public String convertToEntityAttribute(String dbData) {
return dbData == null ? null : CryptoUtils.decrypt(dbData);
}
}
// 实体类中使用
@Entity
@Table(name = "elderly_info")
public class ElderlyInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Convert(converter = CryptoConverter.class)
private String idCard;
@Convert(converter = CryptoConverter.class)
private String contactPhone;
// 其他字段...
}
6. 实际开发中的经验与教训
6.1 跨域问题解决方案
在前后端分离架构中,跨域是常见问题。我们的解决方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://your-domain.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
同时,Nginx也需要相应配置:
nginx复制location /api/ {
proxy_pass http://backend-server;
add_header 'Access-Control-Allow-Origin' 'https://your-domain.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
6.2 事务管理最佳实践
在养老系统中,事务管理尤为重要:
java复制@Service
public class MedicationServiceImpl implements MedicationService {
@Transactional(rollbackFor = Exception.class)
public void processMedicationOrder(Long orderId) {
// 1. 查询原始订单
MedicationOrder order = orderMapper.selectById(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 2. 更新订单状态
order.setStatus(OrderStatus.PROCESSING);
orderMapper.updateById(order);
// 3. 检查库存
checkInventory(order);
// 4. 扣减库存
reduceInventory(order);
// 5. 生成配送任务
createDeliveryTask(order);
// 6. 更新订单状态为已完成
order.setStatus(OrderStatus.COMPLETED);
orderMapper.updateById(order);
}
// 其他方法...
}
6.3 日志记录与问题排查
完善的日志系统对问题排查至关重要:
java复制@Aspect
@Component
@Slf4j
public class LoggingAspect {
@Around("execution(* com.example.elderlycare..*.*(..))")
public Object logMethodCall(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("方法调用: {}.{}() 参数: {}", className, methodName, Arrays.toString(args));
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
log.info("方法完成: {}.{}() 耗时: {}ms 结果: {}",
className, methodName, elapsedTime,
result != null ? result.toString() : "null");
return result;
} catch (Exception e) {
log.error("方法异常: {}.{}() 异常: {}", className, methodName, e.getMessage(), e);
throw e;
}
}
}
同时配置Logback日志策略:
xml复制<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/elderly-care.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/elderly-care.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
