SpringBoot与Vue驱动的养老服务平台架构实践
项目背景与行业价值
当前社会结构呈现显著老龄化特征,传统养老机构在资源调配、服务响应等方面存在明显短板。借助信息化手段重构养老服务流程,已成为提升机构运营效能、改善长者生活质量的关键路径。本平台通过整合物联网感知、实时数据分析和移动端交互技术,构建覆盖健康监测、护理调度、家属联动的全场景数字化管理体系。
整体技术架构
平台采用经典的分层架构设计,服务端以SpringBoot为核心承载业务逻辑,前端基于Vue3实现响应式界面,数据层通过关系型数据库与缓存中间件协同保障访问性能。
服务端技术选型
- 核心框架:SpringBoot 3.x(JDK17+),内置Tomcat容器
- 安全机制:Spring Security 6.x配合OAuth2.1协议,支持多因素认证
- 持久化方案:MyBatis-Plus 3.5+,支持动态SQL与分页插件
- 数据存储:MySQL 8.0主库 + Redis 7.x缓存集群
- 消息中间件:RabbitMQ实现异步解耦,如告警通知、数据同步
前端技术选型
- 框架版本:Vue 3.3+(Composition API + <script setup>语法)
- 语言支持:TypeScript 5.x严格模式
- UI组件库:Element Plus 2.x,自定义主题适配无障碍设计
- 状态管理:Pinia 2.x,模块化管理用户、设备、告警状态
- 构建工具:Vite 4.x,支持按需加载与热更新
核心模块实现
长者信息域模型
采用DDD思想设计实体关系,区分值对象与聚合根:
@Data
@TableName("senior_resident")
public class SeniorResident {
@TableId(type = IdType.AUTO)
private Long residentId;
private String fullName;
private LocalDate birthDate;
private Integer gender;
private String idCardNo;
private String contactPhone;
private String emergencyContact;
private String emergencyPhone;
private Integer careLevel;
private LocalDateTime checkInTime;
private Integer accountStatus;
@TableField(exist = false)
private List<HealthMetric> recentMetrics;
}健康数据实时采集
通过MQTT协议接入智能穿戴设备,采用WebSocket向前端推送关键指标:
@Component
public class VitalSignsHandler {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Autowired
private AlertRuleEngine ruleEngine;
@KafkaListener(topics = "device-telemetry", groupId = "health-monitor")
public void processTelemetry(DevicePayload payload) {
VitalSigns signs = parsePayload(payload);
// 持久化原始数据
vitalSignsRepository.save(signs);
// 触发规则引擎检测异常
List<AlertEvent> alerts = ruleEngine.evaluate(signs);
alerts.forEach(alert -> {
alertService.createAlert(alert);
messagingTemplate.convertAndSend("/topic/alerts/" + alert.getResidentId(), alert);
});
}
}前端实时告警组件
使用Vue3组合式API封装WebSocket连接,实现告警消息的自动订阅与展示:
<template>
<div class="alert-panel">
<transition-group name="alert-fade">
<div v-for="alert in activeAlerts" :key="alert.alertId"
:class="['alert-card', severityClass(alert.level)]">
<div class="alert-header">
<span class="resident-name">{{ alert.residentName }}</span>
<span class="alert-time">{{ formatTime(alert.triggerTime) }}</span>
</div>
<p class="alert-desc">{{ alert.description }}</p>
<div class="alert-actions">
<el-button size="small" @click="acknowledge(alert.alertId)">确认</el-button>
<el-button size="small" type="primary" @click="dispatchCare(alert)">派单</el-button>
</div>
</div>
</transition-group>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { useAlertStore } from '@/stores/alert'
import type { AlertEvent } from '@/types/alert'
const alertStore = useAlertStore()
const socket = ref<WebSocket | null>(null)
const activeAlerts = computed(() => alertStore.pendingAlerts)
const severityClass = (level: number) => {
const map: Record<number, string> = { 1: 'critical', 2: 'warning', 3: 'notice' }
return map[level] || 'default'
}
const connectWebSocket = () => {
const wsUrl = import.meta.env.VITE_WS_BASE + '/ws/alerts'
socket.value = new WebSocket(wsUrl)
socket.value.onmessage = (event) => {
const alert: AlertEvent = JSON.parse(event.data)
alertStore.pushAlert(alert)
notifySound(alert.level)
}
}
const acknowledge = async (id: string) => {
await alertStore.resolveAlert(id)
}
const dispatchCare = (alert: AlertEvent) => {
// 调用护理调度服务
}
onMounted(connectWebSocket)
onUnmounted(() => socket.value?.close())
</script>护理工单智能调度
基于贪心算法实现护理人员的动态任务分配,考虑位置距离、技能匹配、工作负荷等多维度因素:
@Service
public class CareDispatchService {
public DispatchResult assignTask(CareTask task) {
List<StaffProfile> candidates = staffRepository
.findAvailableStaff(task.getRequiredSkills(), task.getLocationZone());
Optional<StaffProfile> optimal = candidates.stream()
.map(staff -> Map.entry(staff, calculateScore(staff, task)))
.max(Comparator.comparingDouble(Map.Entry::getValue))
.map(Map.Entry::getKey);
return optimal.map(staff -> {
task.assignTo(staff.getStaffId());
taskRepository.updateStatus(task.getTaskId(), TaskStatus.ASSIGNED);
notificationService.pushToStaff(staff.getDeviceToken(), task);
return DispatchResult.success(staff);
}).orElse(DispatchResult.failed("无可用人员"));
}
private double calculateScore(StaffProfile staff, CareTask task) {
double distanceScore = 1.0 / (haversine(staff.getCurrentLocation(), task.getLocation()) + 0.1);
double loadScore = 1.0 / (staff.getPendingTasks() + 1);
double skillScore = staff.getSkillMatchRate(task.getRequiredSkills());
return 0.4 * distanceScore + 0.3 * loadScore + 0.3 * skillScore;
}
}数据库关键设计
| 表名 | 核心字段 | 设计要点 |
|---|---|---|
| senior_resident | resident_id, care_level, room_id | care_level建立B-tree索引,支持分级查询 |
| vital_signs | record_id, resident_id, metric_time | 按metric_time分区,保留90天热数据 |
| care_task | task_id, assignee_id, status, deadline | 复合索引(status, deadline)优化待办查询 |
| iot_device | device_sn, resident_id, online_status | device_sn唯一约束,关联Redis在线状态 |
性能优化策略
针对高频查询场景实施专项优化:
- 健康数据查询:采用时间分区表 + 预聚合视图,将7日趋势查询从秒级降至毫秒级
- 实时告警推送:Redis Stream作为消息缓冲,削峰填谷保障WebSocket稳定性
- 移动端接口:启用Gzip压缩 + 字段裁剪,减少弱网环境下的传输耗时
- 报表生成:异步任务队列处理,结果落库后通过SSE通知前端下载
部署架构
生产环境采用Docker Compose编排,核心服务定义如下:
version: '3.8'
services:
api-gateway:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
app-server:
build: ./backend
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_HOST=mysql-cluster
- REDIS_HOST=redis-sentinel
depends_on:
- mysql-cluster
- redis-sentinel
vue-admin:
build: ./frontend/admin
depends_on:
- app-server
ws-server:
build: ./websocket-service
ports:
- "8088:8088"
environment:
- REDIS_HOST=redis-sentinel该架构通过Nginx实现反向代理与负载均衡,WebSocket服务独立部署避免长连接阻塞HTTP请求,MySQL采用主从复制保障读性能,Redis哨兵模式确保缓存高可用。